[lexical-playground] Bug Fix: Render PageBreakNode as <hr> so Safari fires beforeinput after paste#8719
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
eee51d5 to
78f9693
Compare
potatowagon
left a comment
There was a problem hiding this comment.
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:
-
Correctness of the DOM change: Using
<hr>is semantically appropriate for a page break (thematic break), anddata-lexical-page-breakattribute cleanly distinguishes it from HorizontalRuleNode's<hr>. The attribute approach is cleaner than the oldtypeattribute (which is not a valid HTML attribute on<figure>). -
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. -
CSS selectors updated correctly: All 6 CSS selector references updated from
[type='page-break']tohr[data-lexical-page-break]. The.selectedclass ordering is also fixed (moved after element selector for proper specificity). -
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). -
Scope: Playground-only changes — no core library modifications, no www compat concerns. PageBreakNode is a playground DecoratorNode, not part of the published @lexical packages.
-
No regressions: createDOM still sets
pageBreakAfter: alwaysfor print behavior. The@__PURE__annotations are preserved for tree-shaking. TheupdateDOM→ 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.
78f9693 to
7839401
Compare
…fires beforeinput after paste
7839401 to
e315ab5
Compare
Description
In Safari, copying a PageBreak and pasting it into another paragraph leaves the caret right after the pasted node with
beforeinputevents suppressed at the contentEditable root. Every keystroke is a no-op until the user clicks elsewhere and back. The pasted EditorState is fine (rootends in[..., page-break, page-break]with aRangeSelectionat{ key: root, offset: N, type: element }); the lazy paragraph-on-typing path in$transferStartingElementPointToTextPointsimply never runs because WebKit doesn't firebeforeinputwhen the caret sits at a root element point adjacent to acontenteditable="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.createDOMfrom<figure type="page-break">to<hr data-lexical-page-break="true">. The new tag name avoids WebKit's<figure>adjacency quirk, and thedata-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 aPageBreakNodeinstead of being silently absorbed by the generic<figure>rule inImagesExtension. The print-mode selector inPlaygroundEditorTheme.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.ImageNodein 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:contenteditable="false"block decorator and pre-emptively inserts a trailing paragraph. The trigger has to fire before any keystroke since Safari never sendsbeforeinputhere, so scope is meaningful.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)./page break→ click node → Cmd+C → empty paragraph → Cmd+V → type — character lands, lazy paragraph is created (was a no-op pre-fix)./page breakmenu insert shows the same visual (dashed border, "PAGE BREAK" label, scissors icon).Before
See the reproduction video on the issue: #8718.
After
2026-06-18.2.28.54.mov