From 709ae693aef6040d54b89a66918e06e408adb1b8 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Feb 2026 12:13:45 -0300 Subject: [PATCH] fix: preserve selection highlight when opening toolbar dropdowns SD-1905: The selection overlay was cleared whenever the editor lost focus, including when focus moved to toolbar dropdowns. Add an isOnEditorUi check so the overlay is preserved when the active element is inside [data-editor-ui-surface] or a Naive-UI portal (.v-binder-follower-content). --- .../presentation-editor/PresentationEditor.ts | 8 +++- ...lection-highlight-toolbar-dropdown.spec.ts | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/visual/tests/behavior/basic-commands/sd-1905-selection-highlight-toolbar-dropdown.spec.ts diff --git a/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts b/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts index 7a93018b02..6e875256b9 100644 --- a/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts +++ b/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts @@ -3662,7 +3662,13 @@ export class PresentationEditor extends EventEmitter { // Keep selection visible when context menu (SlashMenu) is open const slashMenuOpen = activeEditor?.state ? !!SlashMenuPluginKey.getState(activeEditor.state)?.open : false; - if (!hasFocus && !slashMenuOpen) { + // Keep selection visible when focus is on editor UI surfaces (toolbar, dropdowns). + // Naive-UI portals dropdown content under .v-binder-follower-content at level, + // so it won't be inside [data-editor-ui-surface]. Check both. + const activeEl = document.activeElement; + const isOnEditorUi = !!(activeEl as Element)?.closest?.('[data-editor-ui-surface], .v-binder-follower-content'); + + if (!hasFocus && !slashMenuOpen && !isOnEditorUi) { try { this.#clearSelectedFieldAnnotationClass(); this.#localSelectionLayer.innerHTML = ''; diff --git a/tests/visual/tests/behavior/basic-commands/sd-1905-selection-highlight-toolbar-dropdown.spec.ts b/tests/visual/tests/behavior/basic-commands/sd-1905-selection-highlight-toolbar-dropdown.spec.ts new file mode 100644 index 0000000000..0ec6db338f --- /dev/null +++ b/tests/visual/tests/behavior/basic-commands/sd-1905-selection-highlight-toolbar-dropdown.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from '../../fixtures/superdoc.js'; + +test.use({ config: { hideSelection: false } }); + +test('@behavior SD-1905 selection highlight preserved when focus moves to editor UI surface', async ({ superdoc }) => { + await superdoc.type('Select this text then open dropdown'); + await superdoc.waitForStable(); + + // Select all text via keyboard shortcut + await superdoc.selectAll(); + await superdoc.waitForStable(); + + // Verify selection overlay is rendered + const overlayChildCount = await superdoc.page.evaluate(() => { + const overlay = document.querySelector('.presentation-editor__selection-layer--local'); + return overlay ? overlay.children.length : -1; + }); + expect(overlayChildCount).toBeGreaterThan(0); + + await superdoc.screenshot('sd-1905-selection-before-ui-focus'); + + // Simulate focus moving to an editor UI surface (e.g. toolbar dropdown). + // This is what happens when a user clicks a toolbar dropdown — focus leaves + // the ProseMirror editor and moves to a UI element marked as editor UI. + await superdoc.page.evaluate(() => { + const btn = document.createElement('button'); + btn.setAttribute('data-editor-ui-surface', ''); + btn.textContent = 'Fake toolbar button'; + btn.id = 'sd-1905-test-ui-surface'; + document.body.appendChild(btn); + btn.focus(); + }); + await superdoc.waitForStable(); + + // Selection overlay should still be visible after focus moved to UI surface + const overlayAfterUiFocus = await superdoc.page.evaluate(() => { + const overlay = document.querySelector('.presentation-editor__selection-layer--local'); + return overlay ? overlay.children.length : -1; + }); + expect(overlayAfterUiFocus).toBeGreaterThan(0); + + await superdoc.screenshot('sd-1905-selection-with-ui-surface-focused'); + + // Clean up test element + await superdoc.page.evaluate(() => { + document.getElementById('sd-1905-test-ui-surface')?.remove(); + }); +});