Skip to content

[lexical-playground] Bug Fix: Render PageBreakNode as <hr> so Safari fires beforeinput after paste#8719

Merged
etrepum merged 3 commits into
facebook:mainfrom
mayrang:fix/pagebreak-safari-typing
Jun 19, 2026
Merged

[lexical-playground] Bug Fix: Render PageBreakNode as <hr> so Safari fires beforeinput after paste#8719
etrepum merged 3 commits into
facebook:mainfrom
mayrang:fix/pagebreak-safari-typing

Conversation

@mayrang

@mayrang mayrang commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Description

In Safari, copying a PageBreak and pasting it into another paragraph leaves the caret right after the pasted node with beforeinput events suppressed at the contentEditable root. Every keystroke is a no-op until the user clicks elsewhere and back. The pasted EditorState is fine (root ends in [..., page-break, page-break] with a RangeSelection at { key: root, offset: N, type: element }); the lazy paragraph-on-typing path in $transferStartingElementPointToTextPoint simply never runs because WebKit doesn't fire beforeinput when the caret sits at a root element point adjacent to a contenteditable="false" block-flow element like <figure>. A horizontal rule pasted the same way works in Safari because <hr> is a void element and WebKit treats the adjacency differently.

Switches PageBreakNode.createDOM from <figure type="page-break"> to <hr data-lexical-page-break="true">. The new tag name avoids WebKit's <figure> adjacency quirk, and the data-lexical-* attribute name follows the other playground decorators (Tweet, YouTube, Equation, Mention, etc.). CSS selectors and the import-rule matcher move to the new attribute. A legacy <figure type="page-break"> import rule is kept so older playground exports still round-trip into a PageBreakNode instead of being silently absorbed by the generic <figure> rule in ImagesExtension. The print-mode selector in PlaygroundEditorTheme.css (which hides the trailing page break between Pages) is updated to the new tag too.

Design notes

The same <figure contenteditable="false"> quirk could hit other root-level block decorators in principle. ImageNode in the playground also renders <figure> and has not been audited under this exact paste-then-type scenario. Two broader directions exist if you'd prefer:

  1. A lexical-core normalization that detects a root element point adjacent to a contenteditable="false" block decorator and pre-emptively inserts a trailing paragraph. The trigger has to fire before any keystroke since Safari never sends beforeinput here, so scope is meaningful.
  2. A playground-wide convention to render every block decorator with a void or replaced element. Scoped per-node but spans many nodes.

This PR keeps the change to PageBreak alone. The DOM swap follows the pattern already in HorizontalRuleNode (also <hr>), so the playground stays internally consistent without touching core.

Closes #8718

Test plan

  • pnpm vitest run --project unit -t "PageBreakNode" — round-trip test still passes against the new <hr data-lexical-page-break> shape.
  • pnpm tsc --noEmit -p tsconfig.json, prettier / eslint clean (via pre-commit hook).
  • Manual playground on macOS:
    • Safari: /page break → click node → Cmd+C → empty paragraph → Cmd+V → type — character lands, lazy paragraph is created (was a no-op pre-fix).
    • Chrome: same flow — unchanged, regression check.
    • Both: /page break menu insert shows the same visual (dashed border, "PAGE BREAK" label, scissors icon).
    • Both: PagesExtension print preview — the trailing page break between pages is hidden, regression check for the print selector update.

Before

See the reproduction video on the issue: #8718.

After

2026-06-18.2.28.54.mov
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 19, 2026 1:56am
lexical-playground Ready Ready Preview, Comment Jun 19, 2026 1:56am

Request Review

@potatowagon potatowagon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.

Assessment: LGTM

This PR changes PageBreakNode's DOM representation from <figure type="page-break"> to <hr data-lexical-page-break="true">, which fixes a real Safari bug where beforeinput events are not fired after pasting content that contains a <figure> element (Safari treats <figure> as a non-editable island in some contexts).

What I checked:

  1. Correctness of the DOM change: Using <hr> is semantically appropriate for a page break (thematic break), and data-lexical-page-break attribute cleanly distinguishes it from HorizontalRuleNode's <hr>. The attribute approach is cleaner than the old type attribute (which is not a valid HTML attribute on <figure>).

  2. Backward compatibility: A legacy import rule (PageBreakLegacyImportRule) preserves the old <figure type="page-break"> → PageBreakNode path, so existing exported HTML from older playground versions still round-trips correctly. This is explicitly tested.

  3. CSS selectors updated correctly: All 6 CSS selector references updated from [type='page-break'] to hr[data-lexical-page-break]. The .selected class ordering is also fixed (moved after element selector for proper specificity).

  4. Test coverage: New unit test verifies the legacy import path with ImagesExtension present (the conflict scenario where <figure> could be swallowed by the generic figure rule).

  5. Scope: Playground-only changes — no core library modifications, no www compat concerns. PageBreakNode is a playground DecoratorNode, not part of the published @lexical packages.

  6. No regressions: createDOM still sets pageBreakAfter: always for print behavior. The @__PURE__ annotations are preserved for tree-shaking. The updateDOM → false pattern is unchanged.

CI Status: Core tests all green (unit 22.x+24.x, browser, integrity). E2e canary chromium pending. CLA signed, Vercel previews deployed.

Ready to approve once e2e canary passes.

Comment thread packages/lexical-playground/src/plugins/PageBreakExtension/index.ts Outdated
@etrepum etrepum added this pull request to the merge queue Jun 19, 2026
Merged via the queue into facebook:main with commit e5af8fb Jun 19, 2026
42 checks passed
@etrepum etrepum mentioned this pull request Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

3 participants