Writing flow: Delete at end of nested list item should merge into next block#78742
Conversation
…7245) - list-item useMerge: when there's no next list item in the tree, walk to the outermost list and call mergeBlocks against the block that follows it. The block-editor's fallback `onMerge` operates on the selected block and only walks one parent level up, so it can't reach past the outer list on its own. - mergeBlocks __experimentalOnMerge path: only move the caret to the newly inserted block when the caret was inside the block being removed; otherwise keep the caller's selection (so Delete from a deeper block in clientIdA stays where the user pressed it). - Add an e2e test for the nested-item-followed-by-paragraph case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…b sibling list on nested Delete (#77245) In mergeBlocks's `__experimentalOnMerge` path, branch on whether blockA and blockB share a name. Same-name container merges (list+list, quote+quote, group+group) now `moveBlocksToPosition` blockB's children into blockA and remove the empty blockB. Going through `switchToBlockType` + `insertBlocks` previously failed for these because there is no list->list transform, and using `[blockB]` as a shortcut shared references that `removeBlock( clientIdB )` then cascade-removed. Cross-type merges still clone via `switchToBlockType` + `insertBlocks`, but only redirect the caret when blockB itself was selected. Selection inside blockA (e.g. Delete from a deeper item) is preserved. In list-item's `useMerge`, when there is no next list item in the tree, walk up to the outermost containing list and call `mergeBlocks` against the block that follows it. The fallback `onMerge` walks only one parent level up so it cannot reach the next block past the outer list. Adds e2e tests for the nested-item-followed-by-paragraph and nested-item-followed-by-sibling-list cases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…in one pass Replaces the `getNextId` / `_getNextId` helpers with an inline dive plus an explicit walk-up loop, so the search for the next list item and the search for the topmost containing list item happen together. `listItemId` ends on the topmost list item when no next sibling is found, which the outer-list fallback then uses without a second walk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rop vestigial onMerge fallback The forward branch now falls through naturally instead of using `return` to skip the sibling case. The `onMerge( forward )` call in the exhausted-tree branch is also removed: by the time we reach it, both `getNextBlockClientId( clientId )` and `getNextBlockClientId( outermostListId )` have already returned undefined, so the generic `block.js` fallback has nothing left to try and was a no-op. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Size Change: +67 B (0%) Total Size: 8.18 MB 📦 View Changed
ℹ️ View Unchanged
|
…77245) Reverts the `__experimentalOnMerge` path additions in `mergeBlocks` (same-name container short-circuit, selection-on-B logic) and moves that logic into `useMerge` instead: * Same-name lists (the only realistic case here, since `outerListId` is always `core/list`): `moveBlocksToPosition` the second list's items into the outer list, `removeBlock` the empty container. * Other block types: convert through the list's `from:[paragraph, heading, ...]` transform via `switchToBlockType`, then `insertBlocks` the resulting list items with `updateSelection: false` so the caret stays at the end of the inner item. This keeps the fix scoped to one file and avoids touching the generic merge primitive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous setup nested the second list to mirror the first, which required navigating across two block boundaries to position the caret at the end of the first list's inner item. That cross-block jump preserved column rather than landing at end, and the snap-back via ArrowRight worked on Mac locally but not on CI (Linux). Flatten the second list to a single item so navigation needs only one Home + ArrowLeft (same pattern as the paragraph variant). The behavior under test is the same; only the setup is simpler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Flaky tests detected in 86185bf. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/26529930573
|
Reverts the structural flatten in 4a3a310 and instead positions the caret by clicking directly on the inner list item. Keyboard navigation across two nested block boundaries was inconsistent between local and CI environments. Clicking the target element is deterministic and matches what a real user does to place the caret. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
editor.insertBlock leaves the inserted block fully selected (not a caret inside it). From a selected list 2, ArrowUp navigates to the previous block as a unit and lands at the start of the deepest leaf in list 1, which is "b". ArrowRight then moves the caret one char to the end of "b". This is deterministic across Mac and Linux unlike the earlier Home + ArrowLeft cross-block jumps, which were column-preserving on Mac and column-resetting on CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What?
Closes #77245.
Pressing Delete at the end of a deeply-nested list item now absorbs the following block (a paragraph or another list) into the outermost containing list, instead of silently doing nothing.
Why?
The block-editor's fallback
onMergeonly walks one parent level up, so it can't see past the outer<ul>/<ol>that wraps the nested item. The cursor was effectively stuck at the end of a nested list.How?
All logic is scoped to
list-item'suseMerge; the genericmergeBlocksaction is untouched.core/list, move its items into the outermost list withmoveBlocksToPositionand remove the now-empty container.from:[paragraph, heading, ...]transform withswitchToBlockType, theninsertBlocksthe resulting list items withupdateSelection: falseso the caret stays at the end of the inner item.Testing Instructions
ul[outer[ul[inner]]].inner, press Delete.inner.Testing Instructions for Keyboard
Same as above. The change is keyboard-only behavior.
Use of AI Tools
Authored with Claude Code (Opus 4.7) under direction and review.