Skip to content

Comments

Fix normalization performance and DOM error handling#2241

Draft
christianhg wants to merge 3 commits intomainfrom
fix/normalization-and-dom-fixes
Draft

Fix normalization performance and DOM error handling#2241
christianhg wants to merge 3 commits intomainfrom
fix/normalization-and-dom-fixes

Conversation

@christianhg
Copy link
Member

@christianhg christianhg commented Feb 23, 2026

Three fixes:

1. Respect suppressThrow in toSlatePoint findPath calls

When toSlatePoint is called with suppressThrow: true (e.g., from toSlateRange during selection change handling), internal findPath calls could still throw "Unable to find the path for Slate node" errors during component unmount when node references become stale. Wraps both calls in try-catch blocks that return null when suppressThrow is enabled.

2. Fix stale closure in focus/blur event listener

The useIsomorphicLayoutEffect that registers document-level focus/blur event listeners had an empty dependency array. The listener closure captures editor, so if the editor instance changes, the old closure becomes stale. Adds editor to the dependency array.

3. Optimize normalizeNode (6x speedup)

Full rewrite of normalizeNode with:

  • Split inline/block child handling into separate loops
  • Refetch element after each transform (fixes stale reference bugs)
  • Moved children coercion earlier (before the main loop)
  • Direct reference comparison instead of isEditor()

Merged with editor.createSpan() from PR #2223 for schema-aware span creation.

@vercel
Copy link

vercel bot commented Feb 23, 2026

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

Project Deployment Actions Updated (UTC)
portable-text-editor-documentation Ready Ready Preview, Comment Feb 23, 2026 1:36pm
portable-text-example-basic Ready Ready Preview, Comment Feb 23, 2026 1:36pm
portable-text-playground Ready Ready Preview, Comment Feb 23, 2026 1:36pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 23, 2026

🦋 Changeset detected

Latest commit: 018a41b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@portabletext/editor Patch
@portabletext/plugin-character-pair-decorator Patch
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-markdown-shortcuts Patch
@portabletext/plugin-one-line Patch
@portabletext/plugin-paste-link Patch
@portabletext/plugin-sdk-value Patch
@portabletext/plugin-typeahead-picker Patch
@portabletext/plugin-typography Patch
@portabletext/toolbar Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

When toSlatePoint is called with suppressThrow: true (e.g., from
toSlateRange during selection change handling), internal findPath calls
could still throw "Unable to find the path for Slate node" errors
during component unmount when node references become stale.

Wrap both findPath calls in try-catch blocks that return null when
suppressThrow is enabled, matching the existing error handling pattern
in the rest of the function.

Based on upstream slate-dom PR #6004.
The useIsomorphicLayoutEffect that registers document-level focus/blur
event listeners had an empty dependency array. The listener closure
captures editor, so if the editor instance changes, the old closure
becomes stale. Adding editor to the dependency array ensures the
listeners are re-registered with the current editor instance.

Based on upstream slate-react PR #6012.
…hing

6x overall speedup, 24x in normalizeNode itself when pasting 10,000
lines.

Key changes:
- Split inline/block child handling into separate loops instead of a
  single loop with complex nested conditions
- Refetch element after each transform operation (fixes stale reference
  bugs since Slate clones to new immutable references on mutation)
- Moved children coercion earlier (before the main loop)
- Replaced isEditor() check with direct reference comparison
- Removed isLast variable in favor of direct length comparison

Merged with our createSpan() calls (PR #2223) — uses
editor.createSpan() instead of { text: '' } for schema-aware span
creation in three places: empty element padding, inline-before padding,
and inline-after padding.

Based on upstream slate PR #5970.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant