diff --git a/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.test.ts b/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.test.ts index fd1ae02e1e..48826151b1 100644 --- a/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.test.ts +++ b/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.test.ts @@ -43,6 +43,9 @@ describe('ensureEditorNativeSelectionStyles', () => { expect(css).toContain('.superdoc-layout *::selection'); expect(css).toContain('.superdoc-layout *::-moz-selection'); expect(css).toContain('background: transparent'); + expect(css).toContain('.superdoc-layout .superdoc-header-editor-host *::selection'); + expect(css).toContain('.superdoc-layout .superdoc-footer-editor-host *::selection'); + expect(css).toContain('color: HighlightText'); }); }); diff --git a/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.ts b/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.ts index b3733f7ee1..05c07bcb33 100644 --- a/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.ts +++ b/packages/super-editor/src/editors/v1/core/presentation-editor/dom/EditorStyleInjector.ts @@ -30,6 +30,23 @@ const NATIVE_SELECTION_STYLES = ` .superdoc-layout *::-moz-selection { background: transparent; } + +/* Keep native selection visible inside live header/footer editors. + * Unlike the main document surface, header/footer editing uses a visible + * ProseMirror host. If we suppress native selection there, users can end up + * with no obvious selection feedback when the custom overlay is subtle or + * still syncing to the current drag gesture. */ +.superdoc-layout .superdoc-header-editor-host *::selection, +.superdoc-layout .superdoc-footer-editor-host *::selection { + background: Highlight; + color: HighlightText; +} + +.superdoc-layout .superdoc-header-editor-host *::-moz-selection, +.superdoc-layout .superdoc-footer-editor-host *::-moz-selection { + background: Highlight; + color: HighlightText; +} `; let nativeSelectionStylesInjected = false; diff --git a/tests/behavior/tests/headers/header-footer-selection-overlay.spec.ts b/tests/behavior/tests/headers/header-footer-selection-overlay.spec.ts new file mode 100644 index 0000000000..49ea916249 --- /dev/null +++ b/tests/behavior/tests/headers/header-footer-selection-overlay.spec.ts @@ -0,0 +1,79 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Locator, Page } from '@playwright/test'; +import { expect, test } from '../../fixtures/superdoc.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const DOC_PATH = path.resolve(__dirname, '../../test-data/pagination/longer-header.docx'); +const MOD_KEY = process.platform === 'darwin' ? 'Meta' : 'Control'; + +test.use({ config: { showSelection: true } }); + +test.skip(!fs.existsSync(DOC_PATH), 'Test document not available — run pnpm corpus:pull'); + +async function enterHeaderFooterEditMode( + page: Page, + surfaceSelector: string, + editorHostSelector: string, +): Promise { + const surface = page.locator(surfaceSelector).first(); + await surface.scrollIntoViewIfNeeded(); + await surface.waitFor({ state: 'visible', timeout: 15_000 }); + + const box = await surface.boundingBox(); + expect(box).toBeTruthy(); + await page.mouse.dblclick(box!.x + box!.width / 2, box!.y + box!.height / 2); + + const editorHost = page.locator(editorHostSelector).first(); + await editorHost.waitFor({ state: 'visible', timeout: 10_000 }); + + const pm = editorHost.locator('.ProseMirror'); + await pm.click(); + + return pm; +} + +async function assertSelectionOverlayRenders( + page: Page, + editor: Locator, + expectedSelectionText: string, +): Promise { + await editor.click(); + await page.keyboard.press(`${MOD_KEY}+A`); + + await expect + .poll(async () => page.evaluate(() => document.getSelection()?.toString().trim() ?? '')) + .toBe(expectedSelectionText); + + await expect.poll(async () => page.locator('.presentation-editor__selection-rect').count()).toBeGreaterThan(0); + + const selectionRect = page.locator('.presentation-editor__selection-rect'); + await expect(selectionRect.first()).toBeVisible(); +} + +test('layout engine renders selection rectangles while editing a header', async ({ superdoc }) => { + await superdoc.loadDocument(DOC_PATH); + await superdoc.waitForStable(); + + const editor = await enterHeaderFooterEditMode( + superdoc.page, + '.superdoc-page-header', + '.superdoc-header-editor-host', + ); + + await assertSelectionOverlayRenders(superdoc.page, editor, 'Generic content header'); +}); + +test('layout engine renders selection rectangles while editing a footer', async ({ superdoc }) => { + await superdoc.loadDocument(DOC_PATH); + await superdoc.waitForStable(); + + const editor = await enterHeaderFooterEditMode( + superdoc.page, + '.superdoc-page-footer', + '.superdoc-footer-editor-host', + ); + + await assertSelectionOverlayRenders(superdoc.page, editor, 'Footer'); +});