Skip to content

fix: guard drag and dragleave handlers without resolving an event position - #3216

Merged
christianhg merged 1 commit into
mainfrom
drag-event-guard
Sep 3, 2026
Merged

fix: guard drag and dragleave handlers without resolving an event position#3216
christianhg merged 1 commit into
mainfrom
drag-event-guard

Conversation

@christianhg

@christianhg christianhg commented Sep 3, 2026

Copy link
Copy Markdown
Member

During a drag, the editor runs a full getEventPosition for every drag and dragleave event: a caretPositionFromPoint hit-test plus first-block, last-block, and target rect reads. Both handlers use the result only as a bail-out guard; the forwarded drag.drag and drag.dragleave behavior events carry no position. The browser fires drag continuously on the drag source and dragleave on every element boundary crossed, so this is wasted work at pointer-move frequency on the same hot path #3214 fixed, and whenever layout is dirty (indicator commits, style writes), each rect read becomes a forced reflow that scales with page size.

The handlers now guard with the same checks getEventPosition front-loads (editor actor not setting up, DOMEditor.hasTarget on the event target) and skip the resolution. One narrow delta rides along: where position resolution would have failed beyond those checks (missing block DOM nodes mid-teardown, a failed hit-test), the behavior events now fire instead of being swallowed; they carry no payload a consumer could read stale data from.

Pinned by a browser test asserting both events reach behaviors and that dispatching them runs no caret hit-test; red on the old handlers, which make two caretPositionFromPoint calls. Full editor browser suite green (2040 passed).


Note

Low Risk
Localized drag-handler performance change with a narrow semantic shift (more behavior events in teardown/hit-test failure cases) and no position payload for consumers to misread.

Overview
drag and dragleave no longer call getEventPosition before forwarding drag.drag and drag.dragleave to behaviors. Those behavior events never included a caret position, but each DOM event was still paying for caretPositionFromPoint and block rect reads—expensive while the browser fires drag continuously and dragleave on every nested boundary.

The handlers now bail out only when the editor is still in setup or the event target is outside the editor (DOMEditor.hasTarget), matching the cheap checks getEventPosition already did up front. Edge case: if position resolution would have failed later (e.g. missing block DOM during teardown), the behavior events still fire instead of being dropped; the payload is unchanged (no position).

Adds a Vitest browser test that asserts both behaviors run and caretPositionFromPoint is never invoked for these events. Patch changeset for @portabletext/editor.

Reviewed by Cursor Bugbot for commit 3f623a2. Bugbot is set up for automated code reviews on this repo. Configure here.

… position

`handleDrag` and `handleDragLeave` ran `getEventPosition` on every
event and used the result only as a bail-out guard: the forwarded
`drag.drag` and `drag.dragleave` behavior events carry no position.
`getEventPosition` runs `caretPositionFromPoint` (a synchronous hit
test) plus first-block, last-block, and target `getBoundingClientRect`
reads, and the browser fires `drag` continuously on the drag source
and `dragleave` on every element boundary crossed, so the resolution
was pure per-pointer-move cost on the drag hot path. Layout dirtied
between events (indicator commits, style writes) turns each of those
reads into a forced reflow that scales with page size.

Guard with the same checks `getEventPosition` front-loads: skip while
the editor actor is setting up, and require
`DOMEditor.hasTarget(editorEngine, event.target)`. One narrow
behavioral delta rides along: in cases where position resolution
would have failed beyond those checks (missing block DOM nodes
mid-teardown, a failed caret hit-test), the behavior events now fire
instead of being swallowed; they carry no payload a consumer could
read stale data from.

Pinned by a browser test asserting both events reach behaviors and
that no caret hit-test runs while dispatching them (red on the old
handlers: two `caretPositionFromPoint` calls).
@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3f623a2

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

This PR includes changesets to release 14 packages
Name Type
@portabletext/editor Patch
@portabletext/plugin-character-pair-decorator Patch
@portabletext/plugin-dnd Patch
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-list-index Patch
@portabletext/plugin-markdown-shortcuts Patch
@portabletext/plugin-one-line Patch
@portabletext/plugin-paste-link Patch
@portabletext/plugin-sdk-value Patch
@portabletext/plugin-table 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

@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
portable-text-editor-documentation Ready Ready Preview Sep 3, 2026 7:37am UTC
portable-text-example-basic Ready Ready Preview Sep 3, 2026 7:37am UTC
portable-text-playground Ready Ready Preview Sep 3, 2026 7:37am UTC

Request Review

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Bundle Stats

✅ No significant changes.

All scenario measurements (7)

🗺️ @portabletext/editor / @portabletext/editor · @portabletext/editor / @portabletext/editor/behaviors · @portabletext/editor / @portabletext/editor/plugins · @portabletext/editor / @portabletext/editor/selectors · @portabletext/editor / @portabletext/editor/traversal · @portabletext/editor / @portabletext/editor/utils · @portabletext/markdown / @portabletext/markdown · Artifacts

Scenario Kind Bundle (raw / gzip) Gzip change Import time Import change
⚪ @portabletext/editor / @portabletext/editor export 1.09 MB / 253.7 KB -13 B, -0.0% 71 ms -1 ms, -0.7%
⚪ @portabletext/editor / @portabletext/editor/behaviors export 4.0 KB / 1.4 KB None 2 ms -0 ms, -10.0%
⚪ @portabletext/editor / @portabletext/editor/plugins export 5.1 KB / 1.8 KB None 7 ms -0 ms, -6.4%
⚪ @portabletext/editor / @portabletext/editor/selectors export 93.7 KB / 21.3 KB None 8 ms -1 ms, -6.6%
⚪ @portabletext/editor / @portabletext/editor/traversal export 41.7 KB / 10.8 KB None 6 ms -0 ms, -5.3%
⚪ @portabletext/editor / @portabletext/editor/utils export 33.1 KB / 8.7 KB None 6 ms -0 ms, -7.3%
⚪ @portabletext/markdown / @portabletext/markdown export 272.2 KB / 79.6 KB None 39 ms -2 ms, -4.4%

Significant means at least 1.0 KB and 1% gzip, or at least 5 ms and 10% import time.

@christianhg
christianhg marked this pull request as ready for review September 3, 2026 07:38
@christianhg
christianhg enabled auto-merge (rebase) September 3, 2026 07:43
@christianhg
christianhg merged commit bfda552 into main Sep 3, 2026
20 checks passed
@christianhg
christianhg deleted the drag-event-guard branch September 3, 2026 07:44
@ecoscript ecoscript Bot mentioned this pull request Sep 3, 2026
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