fix: flatten the per-dragover cost in plugin-dnd - #3218
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: ee1bb62 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Bundle Stats✅ No significant changes. All scenario measurements (7)🗺️
Significant means at least 1.0 KB and 1% gzip, or at least 5 ms and 10% import time. |
The clearing behavior treated `drag.dragenter` as an end-of-interaction signal, but it is a carrier event: it precedes every `dragover` when the pointer crosses an element boundary. Each crossing therefore ran a clear+set cycle: the indicator unmounted and remounted, and the caret-color suppression on the editor root was restored and re-written, two inline style writes whose layout invalidation turns the next drag event's rect reads into forced reflows on large pages. Same mechanism class as the `drag.drag` clearing fixed before; `dragenter` was the remaining carrier in the guard. Exclude `drag.dragenter` from the clearing guard. `dragleave` fires on every genuine departure and still clears, so no stale indicator can survive leaving the editor. Pinned by a test sending a `dragenter` between two `dragover`s and asserting the drop position and the hidden drop caret survive it (red on the old guard: the clear restored the caret color synchronously).
f4868d4 to
03534a9
Compare
516a798 to
e9eeeee
Compare
e9eeeee to
d01bda7
Compare
…gover` The `dragover` guard re-derived drag-constant facts at pointer frequency: `getDragSelection`, `getSelectedBlocks`, a `getFocusBlock` resolution of the origin, and `isSelectingEntireBlocks`, each scanning the document, so the per-event cost grew with document size. Cache them in the plugin closure next to the drop-position store, keyed on the document value by identity (the value array is replaced on every applied operation) and on the drag origin by selection equality. Identity would be the cheaper origin key, but the origin object's identity does not survive the event pipeline, so an identity-keyed memo silently degrades to a per-event recompute; a probe counting recomputes caught exactly that. A mid-drag document change (a remote edit landing) invalidates through the value key, pinned by a test where deleting a character turns the same drag origin into an entire-blocks drag and the indicator must appear (red when the memo ignores the value). The guard's observable decisions are unchanged; the self-drop comparison now uses the cached key set and origin focus path instead of re-deriving both.
d01bda7 to
ee1bb62
Compare
Two per-tick costs remain in the drag path after #3214 and #3216; this PR removes both.
The clearing behavior still treated
dragenteras an end-of-interaction signal.dragenterprecedes everydragoverwhen the pointer crosses an element boundary, so each crossing ran a clear+set cycle: indicator unmount and remount, plus two caret-color writes on the editor root whose layout invalidation turns the next drag event's rect reads into forced reflows on large pages. It is the same carrier-event mistake #3214 fixed fordrag.drag;dragleavestill fires on every genuine departure and keeps clearing, so no stale indicator survives leaving the editor. Pinned by a test sending adragenterbetween twodragovers and asserting the position and the hidden drop caret survive; red on the old guard.The
dragoverguard also re-derived drag-constant facts at pointer frequency: the drag selection, the dragged block keys, the origin's focus block, and the entire-blocks check, each scanning the document. They are now derived once per (document value, drag origin) pair in the plugin closure next to the store. The value is keyed by identity; the origin by selection equality, because the origin object's identity does not survive the event pipeline, and an identity-keyed memo silently degrades to a per-event recompute (a probe counting recomputes caught exactly that). A mid-drag document change invalidates through the value key, pinned by a test where deleting a character turns the same drag origin into an entire-blocks drag and the indicator must appear; red when the memo ignores the value.Guard decisions are unchanged; all sixteen plugin tests pass.
Note
Low Risk
Performance and event-handling tweaks in the DnD plugin with behavior pinned by existing and new tests; no auth, data, or API surface changes.
Overview
Reduces drag-and-drop overhead in
@portabletext/plugin-dndby fixing two hot-path behaviors without changing drop-indicator rules.dragenterno longer clears the drop position. It is treated likedrag.dragas a carrier event (alongsidedragover), so crossing block boundaries does not run clear-then-set cycles that remount the indicator and flip the editor root’scaretColortwice per crossing—avoiding extra layout work on large pages. Clearing still runs ondragstart,dragend,dragleave, anddrop.Drag-origin facts are memoized once per drag instead of recomputed on every
dragover. Thedragoverguard now reuses cached drag selection derivatives (dragged block keys, focus block path, entire-blocks selection) keyed by documentvalueidentity and drag-origin selection equality; cache invalidates when the document changes mid-drag (e.g. remote edit), so indicators stay correct when a partial text drag becomes a full-block drag after an edit.New tests cover
dragenterpreserving position/caret and mid-drag document changes forcing recomputation.Reviewed by Cursor Bugbot for commit ee1bb62. Bugbot is set up for automated code reviews on this repo. Configure here.