fix: require a collapsed selection for the forward-delete empty-block hop#3008
Merged
Conversation
🦋 Changeset detectedLatest commit: d881f38 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📦 Bundle Stats —
|
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 807.5 KB | +78 B, +0.0% |
| Internal (gzip) | 155.1 KB | +13 B, +0.0% |
| Bundled (raw) | 1.42 MB | +79 B, +0.0% |
| Bundled (gzip) | 319.4 KB | +7 B, +0.0% |
| Import time | 100ms | -0ms, -0.4% |
@portabletext/editor/behaviors
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 467 B | - |
| Internal (gzip) | 207 B | - |
| Bundled (raw) | 424 B | - |
| Bundled (gzip) | 171 B | - |
| Import time | 2ms | +0ms, +2.2% |
@portabletext/editor/plugins
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 2.7 KB | - |
| Internal (gzip) | 894 B | - |
| Bundled (raw) | 2.5 KB | - |
| Bundled (gzip) | 827 B | - |
| Import time | 7ms | -0ms, -0.1% |
@portabletext/editor/selectors
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 82.7 KB | - |
| Internal (gzip) | 15.4 KB | - |
| Bundled (raw) | 78.4 KB | - |
| Bundled (gzip) | 14.3 KB | - |
| Import time | 8ms | +0ms, +1.3% |
@portabletext/editor/traversal
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 28.1 KB | - |
| Internal (gzip) | 5.6 KB | - |
| Bundled (raw) | 28.1 KB | - |
| Bundled (gzip) | 5.5 KB | - |
| Import time | 6ms | +0ms, +0.5% |
@portabletext/editor/utils
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 30.6 KB | - |
| Internal (gzip) | 6.4 KB | - |
| Bundled (raw) | 28.2 KB | - |
| Bundled (gzip) | 6.1 KB | - |
| Import time | 6ms | +0ms, +2.2% |
🗺️ . · ./behaviors · ./plugins · ./selectors · ./traversal · ./utils · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @portabletext/markdown
Compared against main (bec5ff08)
| Metric | Value | vs main (bec5ff0) |
|---|---|---|
| Internal (raw) | 53.8 KB | - |
| Internal (gzip) | 9.8 KB | - |
| Bundled (raw) | 348.9 KB | - |
| Bundled (gzip) | 96.3 KB | - |
| Import time | 39ms | -1ms, -3.1% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
christianhg
force-pushed
the
repro-empty-block-range-delete
branch
from
July 22, 2026 19:43
cc59bfb to
60af88b
Compare
christianhg
force-pushed
the
repro-empty-block-range-delete
branch
from
July 22, 2026 19:50
60af88b to
96f8975
Compare
christianhg
marked this pull request as ready for review
July 22, 2026 19:51
… hop Selecting a range that ends in an empty block (highlighting empty lines) and pressing Delete left the covered blocks standing: the abstract `delete` behavior that hops an empty focus block away on forward delete guarded only on `event.direction`, the focus block being an empty text block, and a next sibling existing. All three hold for an expanded selection ending at an empty block's start, so the behavior intercepted the event and replaced the range-delete with "delete the focus block, select the next block's start". Its sibling rules were immune by construction: the backward merge and the end-of-block merge guard through `isAtTheStartOfBlock`/ `isAtTheEndOfBlock`, which require a collapsed selection, while `isEmptyTextBlock` is a node predicate with no selection sensitivity. The fix adds the same collapsed requirement via `isSelectionCollapsed`, so expanded selections fall through to the default range-delete. Backspace was never affected, and the caret hop still fires for collapsed selections (existing behavior suites pin it). Three pins in `event.delete.forward.test.tsx`, red without the guard: the real-gesture scenario (`shift+ArrowDown` then Delete), the synthetic `delete` + `direction: 'forward'` contract the editable's expanded-selection branch sends, and a non-empty-lines control that is green throughout.
christianhg
force-pushed
the
repro-empty-block-range-delete
branch
from
July 22, 2026 19:53
96f8975 to
d881f38
Compare
christianhg
enabled auto-merge (rebase)
July 22, 2026 19:58
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Field report: pressing Enter a few times creates empty lines, the user highlights them and presses Delete, and the lines remain. Reproduced with real gestures: keyboard-select from a text block down across three empty blocks, press Delete, and only the focus-edge block is affected while the fully covered empty blocks survive. Backspace works.
The diagnosis moved twice before landing. The editor's logical selection is verifiably expanded before the keypress, and the same gesture over non-empty lines deletes correctly, which pointed at DOM filler-node mapping. A synthetic probe then exonerated the DOM entirely:
deletewithout a direction handles the exact range correctly, whiledeletewithdirection: 'forward', precisely what the editable's expanded-selection branch sends for the Delete key, reproduces the gesture failure byte-for-byte. The bug is in the abstractdeletebehaviors.The forward-delete rule that hops a caret past an empty block guards only on the event direction, the focus block being an empty text block, and a next sibling existing. All three hold for an expanded selection ending at an empty block's start, so the rule intercepted the event and replaced the range-delete with "delete the focus block, select the next block's start". Its sibling rules (backward merge, end-of-block merge) were immune by construction because
isAtTheStartOfBlock/isAtTheEndOfBlockrequire a collapsed selection;isEmptyTextBlockis a node predicate with no selection sensitivity. The fix gives the rule the same collapsed requirement viaisSelectionCollapsed, so expanded selections fall through to the default range-delete.Net behavior change is confined to forward delete over an expanded selection whose focus block is an empty text block. Caret behavior is untouched (the empty-block hop still fires for collapsed selections, pinned by the existing behavior suites, all green) and Backspace was never affected. Three pins land in
event.delete.forward.test.tsx, red without the guard: the real-gesture scenario, the syntheticdirection: 'forward'contract, and the non-empty-lines control that is green throughout.