Skip to content

Writing flow: fully select the ancestor when a text selection crosses a nesting boundary - #80462

Merged
ellatrix merged 8 commits into
trunkfrom
fix/nested-selection-fallback
Jul 20, 2026
Merged

Writing flow: fully select the ancestor when a text selection crosses a nesting boundary#80462
ellatrix merged 8 commits into
trunkfrom
fix/nested-selection-fallback

Conversation

@ellatrix

@ellatrix ellatrix commented Jul 20, 2026

Copy link
Copy Markdown
Member

What?

Fixes #76233.

image

A text selection from a block into its own nested block now fully selects the outer block, like a multi-selection. Backspace or Delete removes it in one press. Previously nothing happened.

Why?

The selection observer mishandled these selections, so the store never saw them and key presses were dropped.

Partial selections are only mergeable within the same list, so fully selecting the outer block is the safe behavior for 7.1: what is highlighted is what gets deleted. A real partial merge is tracked in #80489.

How?

  1. use-selection-observer.js: record the selection instead of promoting it to a broken multiSelect. The native selection is untouched, so the gesture can continue.
  2. Selectors: getSelectionNestingAncestor centralizes the rule: getSelectedBlockClientIds resolves to the outer block and __unstableIsFullySelected reports it as fully selected. The overlay, one-press delete, copy, and arrow-key collapse all follow from the existing fully-selected paths.
  3. block-parent-selector: resolve through getSelectedBlockClientIds; the single-selection selector is empty here.

Testing Instructions

  1. Make a list item "ab" with a nested item "cd", and a sibling "zz".
  2. Select from the middle of "cd" into "ab" (keyboard or mouse).
  3. The "ab" item is highlighted like a multi-selected block.
  4. Press Backspace: the item is removed whole; "zz" remains.

Covered by e2e tests in list.spec.js.

Use of AI Tools

Authored with Claude Code under direction and review.

…esting boundary

A text selection running from an outer block into its own nested
descendant previously did nothing on Backspace or Delete: the
selection observer promoted such selections to a block-level
multi-selection with out-of-range arguments, and the store's merge
and delete paths bail on any selection that does not start and end
in the same block list.

Partial selections remain mergeable only between blocks of the same
list. Instead of merging across the nesting boundary:

- use-selection-observer: when one endpoint is an ancestor of the
  other, record the rich-text selection across the boundary so the
  gesture can continue, instead of promoting to multiSelect.
- use-input: when Backspace or Delete acts on such a selection,
  fall back to selecting the ancestor as a whole (select without
  caret placement, then focus its wrapper). The block is selected
  exactly as if its shell were clicked, and a follow-up key press
  removes the full block, so exactly what is highlighted gets
  deleted.

Adds e2e coverage for extending backward and forward across the
boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the [Package] Block editor /packages/block-editor label Jul 20, 2026
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

Size Change: +72 B (0%)

Total Size: 7.74 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 426 kB +72 B (+0.02%)

compressed-size-action

@github-actions

Copy link
Copy Markdown

Flaky tests detected in c2cf10a.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/29719128908
📝 Reported issues:

ellatrix and others added 6 commits July 20, 2026 09:45
A selection crossing a nesting boundary is now presented like a
block multi-selection while the gesture is in progress, and a
single Backspace or Delete removes the ancestor.

- getSelectedBlockClientIds: when one selection endpoint is nested
  inside the other, return the ancestor, which contains the whole
  selection. Previously this state produced nonsense (the
  descendant alone in one direction, an empty list in the other).
- block.js: don't mark the ancestor of such a selection as
  partially selected, so the multi-selection overlay applies and
  hides the native selection within it.
- use-input: remove the ancestor directly on Backspace/Delete
  instead of converting to a shell selection first; the block is
  already visibly fully selected.

Also adds a test documenting the adjacent promotion behavior:
extending from a nested item to the previous top level item
multi-selects the top level items.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parent selector resolved the selected block through
getSelectedBlockClientId, which returns nothing when the selection
start and end differ. A text selection crossing into a nested block
is such a state while resolving to a single block, so the button
rendered with an empty icon and label. Resolve through
getSelectedBlockClientIds instead, which is also what the toolbar
uses to decide to render the parent selector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract a getSelectionNestingAncestor helper and have
__unstableIsFullySelected return true when one selection endpoint is
nested inside the other: the selection resolves to the ancestor,
which is presented and treated as fully selected.

With the rule in the selectors, the special cases disappear from the
consumers: use-input's Backspace/Delete flows through the existing
fully-selected branch (removeBlocks of the selected client ids), and
block.js no longer needs its own ancestor check to suppress the
partially-selected class. Copy and arrow-key collapse also become
consistent with the fully-selected presentation for free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the mouse gesture alongside the existing keyboard tests, and
rewrites the observer comment in plain terms, pointing at
getSelectionNestingAncestor for the rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The backward test verified the block tree twice; the assertion
before the action already covers the whole tree, so drop the first
one. Also assert that the native selection still exists mid-gesture
after the crossing (it is only hidden by the block overlay), pinning
that the gesture can continue. The drag test doesn't assert this:
after mouseup the gesture is over and the drag pipeline moves the
native selection during its own post-processing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three presses instead of two: over "c", over the nesting boundary,
and over "b", asserting the native selection reads "bc" before the
action. This pins that the crossing lands inside the outer item
rather than stopping at the boundary.

A matching mid-drag assertion turned out not to hold: during an
automated drag across the boundary the native range reads as the
nested item's text on both browsers, even though the recorded
selection ends up correct. Left out of the drag test and noted for
the drag selection follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ellatrix ellatrix changed the title Writing flow: fall back to selecting the ancestor when a text selection crosses a nesting boundary Writing flow: fully select the ancestor when a text selection crosses a nesting boundary Jul 20, 2026
The helper was inserted between the doc comment of
getSelectedBlockClientIds and its export, so the generated data
docs lost the selector's entry. Order restored; docs:build is a
no-op again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ellatrix
ellatrix marked this pull request as ready for review July 20, 2026 18:41
@github-actions

Copy link
Copy Markdown

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: ellatrix <ellatrix@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@ellatrix ellatrix added the [Type] Bug An existing feature does not function as intended label Jul 20, 2026
@ellatrix
ellatrix merged commit 4787818 into trunk Jul 20, 2026
48 of 49 checks passed
@ellatrix
ellatrix deleted the fix/nested-selection-fallback branch July 20, 2026 20:32
@github-actions github-actions Bot added this to the Gutenberg 23.7 milestone Jul 20, 2026
@ellatrix ellatrix added the Backport to WP 7.1 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Jul 20, 2026
@github-actions github-actions Bot removed the Backport to WP 7.1 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Jul 20, 2026
gutenbergplugin pushed a commit that referenced this pull request Jul 20, 2026
@github-actions github-actions Bot added the Backported to WP Core Pull request that has been successfully merged into WP Core label Jul 20, 2026
@github-actions

Copy link
Copy Markdown

I just cherry-picked this PR to the wp/7.1 branch to get it included in the next release: 51b62e0

@t-hamano t-hamano added the Backport to Gutenberg RC Pull request that needs to be backported to a Gutenberg release candidate (RC) label Jul 21, 2026
t-hamano pushed a commit that referenced this pull request Jul 21, 2026
@t-hamano

Copy link
Copy Markdown
Contributor

I just cherry-picked this PR to the release/23.6 branch to get it included in the next release: cfeb120

@t-hamano t-hamano removed this from the Gutenberg 23.7 milestone Jul 21, 2026
@t-hamano t-hamano added this to the Gutenberg 23.6 milestone Jul 21, 2026
@t-hamano t-hamano removed the Backport to Gutenberg RC Pull request that needs to be backported to a Gutenberg release candidate (RC) label Jul 21, 2026
pento pushed a commit to WordPress/wordpress-develop that referenced this pull request Jul 22, 2026
This updates the pinned commit hash of the Gutenberg repository from `e73c3c481db0650183f092af157f6e42efe9ee2d` to `4997026b75c922d8a6f77a03d72ed7cad04c7073`.

A full list of changes included in this commit can be found on GitHub: 
WordPress/gutenberg@e73c3c4...4997026

- Notes: Replace blur-deselect bookkeeping with useFocusOutside (WordPress/gutenberg#80222)
- Playlist: Update @SInCE tags to 7.1.0 (WordPress/gutenberg#80317)
- fix playlist block Dimensions Design (WordPress/gutenberg#80312)
- UI: Backport compat overlay fixes to WordPress 7.1 (WordPress/gutenberg#80322)
- Editor: allow selecting which block styles to apply globally (WordPress/gutenberg#79839)
- Global Styles: Reject non-string custom CSS in the REST controller (WordPress/gutenberg#80338)
- Open inspector sidebar when toggling responsive editing (WordPress/gutenberg#80307)
- Client Side Media: Honor image_strip_meta and image_max_bit_depth on the client upload path (WordPress/gutenberg#80218)
- Hide block style variations when state is enabled in global styles (WordPress/gutenberg#80341)
- Media REST API: Fix sideload and finalize for EXIF rotated images (WordPress/gutenberg#80295)
- Fix upload snackbar stuck in uploading state on server-side uploads (WordPress/gutenberg#80345)
- Try fixing responsive layout in Nav block (WordPress/gutenberg#80305)
- Responsive styles: Use viewport dropdown to control states for in-editor global styles sidebar (WordPress/gutenberg#80339)
- RichTextControl: Replace DOM focus tracking with a single React-tree focus boundary (WordPress/gutenberg#80324)
- Notes: Finish WPDS treatment for mention chips (WordPress/gutenberg#80300)
- Notes: Add placeholders to the RichText fields (WordPress/gutenberg#80296)
- Fix upload hang when converting long animated GIFs: decode only the first frame for still outputs (WordPress/gutenberg#80260) (WordPress/gutenberg#80342)
- Device preview dropdown: use active color for device icon when responsive styles are active (WordPress/gutenberg#80346)
- Fix default aspect ratio for lazy loaded Featured image (WordPress/gutenberg#80386)
- Vips/upload-media: consolidate optional params into options objects (WordPress/gutenberg#80330)
- Autocompleters: Don't pre-encode mention search terms (WordPress/gutenberg#80377)
- Animated GIF uploads: generate sub-sizes from the first frame, matching core (WordPress/gutenberg#80268)
- Custom CSS: Fix cascade order against block style variations (WordPress/gutenberg#80340)
- Rich Text: Restore the selection when focus returns to the editable (WordPress/gutenberg#80396)
- Notes: Arm the mention kses allowance on REST note creation (WordPress/gutenberg#80221)
- Fix upload snackbar double-counting a single HEIC upload in Safari (WordPress/gutenberg#80436)
- ContentEditableControl: fix invalid label association with contenteditable div (WordPress/gutenberg#80441)
- Editor: Disable canvas resizing while zoomed out (WordPress/gutenberg#80391)
- Fix Color Picker Cursor Shaking Issue (WordPress/gutenberg#80205) (WordPress/gutenberg#80435)
- Misc fixes for WordPress-Develop 7.0 merges (WordPress/gutenberg#80444)
- Style Book: Restore live global styles updates on the styles route (WordPress/gutenberg#80459)
- Worker threads: reject pending RPC calls on worker failure or termination (WordPress/gutenberg#79955) (WordPress/gutenberg#80421)
- Media: Add timeout and size guardrails to client-side GIF to video conversion (WordPress/gutenberg#80420)
- Post Content: Use the default block appender for empty content (WordPress/gutenberg#80026)
- Block Supports: Handle nested array block gap values properly (WordPress/gutenberg#80464)
- Editor: Restore fixed device preview height for mobile and tablet (WordPress/gutenberg#80466)
- Block Editor: Guard against non-string spacing preset values (WordPress/gutenberg#80467)
- Writing flow: fully select the ancestor when a text selection crosses a nesting boundary (WordPress/gutenberg#80462)
- Block Editor: Reflect inherited Global Styles values in block inspector controls (WordPress/gutenberg#80481)
- Autocomplete: Reference the suggestions list with `aria-controls` and `aria-haspopup` (WordPress/gutenberg#80403) (WordPress/gutenberg#80499)
- Media: Remove the redundant __heicUploadSupport flag (WordPress/gutenberg#80486)
- State control - avoid tertiary variant on toggle to match style of other dropdown toggles (WordPress/gutenberg#80505)
- Icons: Store the sanitized SVG content when registering an icon (WordPress/gutenberg#80508)
- Fix `useHomeEnd` on tabs in mac testing (WordPress/gutenberg#80374)
- Playlist: Fix playback of tracks served without CORS headers (WordPress/gutenberg#80533)
- Redirect editing events to extension handlers under editableRoot (WordPress/gutenberg#80287)
- Writing flow: fully select the items when a selection extends down into a nested item (WordPress/gutenberg#80492)
- Global Styles panels: fix wrong preset committed and shown when two color presets share a hex (WordPress/gutenberg#80497)
- Replaces the `title` attributes used by revision inline diff annotations with `aria-describedby` (WordPress/gutenberg#80440)
- Notes: Remove "Add note" from the inline styles dropdown (WordPress/gutenberg#80531)
- Global Styles: Resolve per-level heading element styles in block inspector controls (WordPress/gutenberg#80495)
- Notes: Render @ mentions as span chips and narrow the kses class allowance (WordPress/gutenberg#80528)
- Revisions: Specify block level diff status via aria-label (WordPress/gutenberg#77779)
- Backport from Core: improve icon name unit tests (WordPress/gutenberg#80552)
- Device type preview: fix collapsing to content height (WordPress/gutenberg#80553)
- Wrap notices in ThemeProvider with 0 corner radius (WordPress/gutenberg#80557)
- Global Styles: Limit the inherited value treatment to the Gutenberg plugin (WordPress/gutenberg#80555)
- Fix crashes when manipulating locked blocks (WordPress/gutenberg#80509)
- Notes: align floating threads with their inline marker (WordPress/gutenberg#79877)

Props wildworks.
See #65529.

git-svn-id: https://develop.svn.wordpress.org/trunk@62824 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Jul 22, 2026
This updates the pinned commit hash of the Gutenberg repository from `e73c3c481db0650183f092af157f6e42efe9ee2d` to `4997026b75c922d8a6f77a03d72ed7cad04c7073`.

A full list of changes included in this commit can be found on GitHub: 
WordPress/gutenberg@e73c3c4...4997026

- Notes: Replace blur-deselect bookkeeping with useFocusOutside (WordPress/gutenberg#80222)
- Playlist: Update @SInCE tags to 7.1.0 (WordPress/gutenberg#80317)
- fix playlist block Dimensions Design (WordPress/gutenberg#80312)
- UI: Backport compat overlay fixes to WordPress 7.1 (WordPress/gutenberg#80322)
- Editor: allow selecting which block styles to apply globally (WordPress/gutenberg#79839)
- Global Styles: Reject non-string custom CSS in the REST controller (WordPress/gutenberg#80338)
- Open inspector sidebar when toggling responsive editing (WordPress/gutenberg#80307)
- Client Side Media: Honor image_strip_meta and image_max_bit_depth on the client upload path (WordPress/gutenberg#80218)
- Hide block style variations when state is enabled in global styles (WordPress/gutenberg#80341)
- Media REST API: Fix sideload and finalize for EXIF rotated images (WordPress/gutenberg#80295)
- Fix upload snackbar stuck in uploading state on server-side uploads (WordPress/gutenberg#80345)
- Try fixing responsive layout in Nav block (WordPress/gutenberg#80305)
- Responsive styles: Use viewport dropdown to control states for in-editor global styles sidebar (WordPress/gutenberg#80339)
- RichTextControl: Replace DOM focus tracking with a single React-tree focus boundary (WordPress/gutenberg#80324)
- Notes: Finish WPDS treatment for mention chips (WordPress/gutenberg#80300)
- Notes: Add placeholders to the RichText fields (WordPress/gutenberg#80296)
- Fix upload hang when converting long animated GIFs: decode only the first frame for still outputs (WordPress/gutenberg#80260) (WordPress/gutenberg#80342)
- Device preview dropdown: use active color for device icon when responsive styles are active (WordPress/gutenberg#80346)
- Fix default aspect ratio for lazy loaded Featured image (WordPress/gutenberg#80386)
- Vips/upload-media: consolidate optional params into options objects (WordPress/gutenberg#80330)
- Autocompleters: Don't pre-encode mention search terms (WordPress/gutenberg#80377)
- Animated GIF uploads: generate sub-sizes from the first frame, matching core (WordPress/gutenberg#80268)
- Custom CSS: Fix cascade order against block style variations (WordPress/gutenberg#80340)
- Rich Text: Restore the selection when focus returns to the editable (WordPress/gutenberg#80396)
- Notes: Arm the mention kses allowance on REST note creation (WordPress/gutenberg#80221)
- Fix upload snackbar double-counting a single HEIC upload in Safari (WordPress/gutenberg#80436)
- ContentEditableControl: fix invalid label association with contenteditable div (WordPress/gutenberg#80441)
- Editor: Disable canvas resizing while zoomed out (WordPress/gutenberg#80391)
- Fix Color Picker Cursor Shaking Issue (WordPress/gutenberg#80205) (WordPress/gutenberg#80435)
- Misc fixes for WordPress-Develop 7.0 merges (WordPress/gutenberg#80444)
- Style Book: Restore live global styles updates on the styles route (WordPress/gutenberg#80459)
- Worker threads: reject pending RPC calls on worker failure or termination (WordPress/gutenberg#79955) (WordPress/gutenberg#80421)
- Media: Add timeout and size guardrails to client-side GIF to video conversion (WordPress/gutenberg#80420)
- Post Content: Use the default block appender for empty content (WordPress/gutenberg#80026)
- Block Supports: Handle nested array block gap values properly (WordPress/gutenberg#80464)
- Editor: Restore fixed device preview height for mobile and tablet (WordPress/gutenberg#80466)
- Block Editor: Guard against non-string spacing preset values (WordPress/gutenberg#80467)
- Writing flow: fully select the ancestor when a text selection crosses a nesting boundary (WordPress/gutenberg#80462)
- Block Editor: Reflect inherited Global Styles values in block inspector controls (WordPress/gutenberg#80481)
- Autocomplete: Reference the suggestions list with `aria-controls` and `aria-haspopup` (WordPress/gutenberg#80403) (WordPress/gutenberg#80499)
- Media: Remove the redundant __heicUploadSupport flag (WordPress/gutenberg#80486)
- State control - avoid tertiary variant on toggle to match style of other dropdown toggles (WordPress/gutenberg#80505)
- Icons: Store the sanitized SVG content when registering an icon (WordPress/gutenberg#80508)
- Fix `useHomeEnd` on tabs in mac testing (WordPress/gutenberg#80374)
- Playlist: Fix playback of tracks served without CORS headers (WordPress/gutenberg#80533)
- Redirect editing events to extension handlers under editableRoot (WordPress/gutenberg#80287)
- Writing flow: fully select the items when a selection extends down into a nested item (WordPress/gutenberg#80492)
- Global Styles panels: fix wrong preset committed and shown when two color presets share a hex (WordPress/gutenberg#80497)
- Replaces the `title` attributes used by revision inline diff annotations with `aria-describedby` (WordPress/gutenberg#80440)
- Notes: Remove "Add note" from the inline styles dropdown (WordPress/gutenberg#80531)
- Global Styles: Resolve per-level heading element styles in block inspector controls (WordPress/gutenberg#80495)
- Notes: Render @ mentions as span chips and narrow the kses class allowance (WordPress/gutenberg#80528)
- Revisions: Specify block level diff status via aria-label (WordPress/gutenberg#77779)
- Backport from Core: improve icon name unit tests (WordPress/gutenberg#80552)
- Device type preview: fix collapsing to content height (WordPress/gutenberg#80553)
- Wrap notices in ThemeProvider with 0 corner radius (WordPress/gutenberg#80557)
- Global Styles: Limit the inherited value treatment to the Gutenberg plugin (WordPress/gutenberg#80555)
- Fix crashes when manipulating locked blocks (WordPress/gutenberg#80509)
- Notes: align floating threads with their inline marker (WordPress/gutenberg#79877)

Props wildworks.
See #65529.
Built from https://develop.svn.wordpress.org/trunk@62824


git-svn-id: http://core.svn.wordpress.org/trunk@62104 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backported to WP Core Pull request that has been successfully merged into WP Core [Package] Block editor /packages/block-editor [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Writing flow: Can't delete list items with backspace

2 participants