Skip to content

List: Keep nested lists when pasting a copied list item into a list - #84115

Open
im3dabasia wants to merge 9 commits into
trunkfrom
fix/list-copy-nested-71317
Open

im3dabasia wants to merge 9 commits into
trunkfrom
fix/list-copy-nested-71317

Conversation

@im3dabasia

@im3dabasia im3dabasia commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

What?

Closes #71317

Pasting a copied list item into another list item now keeps its nested list.

Why?

Copying an item with sub-items works since #80462. Pasting it into a list still fails in two ways.

1. The nested list is dropped when pasting into an item with text

  • Pasting at the end of "item 1.1" gives "item 1.1item 3" without 3.1–3.3.
  • This is what the issue describes.

2. Nothing is pasted into an empty list item

How?

  • Fixes 1: the paste merge now keeps the merged item's inner blocks, not just its text.
  • Fixes 2: an empty or fully selected block is now replaced only after the pasted blocks are converted to fit, as @Mamaduka suggested.

Pasting into the middle of an item's text still joins the text, e.g. "item 1.1item 3". This is the existing behaviour for every block type that can merge, like paragraphs, and is unchanged here. This PR only stops the nested list from being lost.

Testing Instructions

  1. Create a post with the list from List block: unable to cut or copy main item if sub-items exist #71317.
  2. Drag from "item 3" down to "item 3.3", then copy.
  3. Paste into an empty list item: "item 3" arrives with its sub-items.
  4. Paste at the end of "item 1.1": its sub-items come along too.
  5. Paste into a new paragraph below the list: still works as before.

Testing Instructions for Keyboard

  1. Place the caret at the end of "item 3".
  2. Press Shift+Right twice to extend into "item 3.1", then Cmd+C.
  3. Move to an empty list item and press Cmd+V.

Screenshots or screencast

Before After
Screen.Recording.2026-10-06.at.5.51.19.PM.mov
temp-bug-1.mov
Screen.Recording.2026-10-06.at.5.51.50.PM.mov
temp-bug-2.mov

Use of AI Tools

Authored with Claude Code under my direction; I reviewed the changes and tested them manually.

@im3dabasia im3dabasia self-assigned this Oct 6, 2026
@im3dabasia
im3dabasia requested a review from ellatrix as a code owner October 6, 2026 12:24
@im3dabasia im3dabasia added [Type] Bug An existing feature does not function as intended [Feature] Paste [Block] List Affects the List Block labels Oct 6, 2026
@github-actions github-actions Bot added the [Package] Block editor /packages/block-editor label Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

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: im3dabasia <im3dabasia1@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: Rishit30G <rishit30g@git.wordpress.org>
Co-authored-by: sanketio <sanketparmar@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: annezazu <annezazu@git.wordpress.org>

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

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

📦 Bundle size

Size Change: +30 B (0%)

Total Size: 9.06 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 606 kB +30 B (0%)

576de08 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric d7f8000 trunk % Change
timeToFirstByte 41.75 ms +10.18% -5.75% 41.45 ms +10.01% -3.38% 0.72%
largestContentfulPaint 76 ms +0% -7.89% 70 ms +8.57% -2.86% 8.57%
lcpMinusTtfb 31.45 ms +6.36% -13.99% 28.3 ms +22.26% -6.54% 11.13%
wpBeforeTemplate 21.75 ms +7.26% -2.57% 21.69 ms +9.17% -1.61% 0.28%
wpTemplate 15.94 ms +7.03% -5.46% 16.16 ms +5.01% -4.89% -1.36%
wpTotal 38.53 ms +9.11% -5.87% 38.16 ms +9.85% -2.86% 0.97%
wpMemoryUsage 7.68 MB +0% -0% 7.64 MB +0% -0% 0.51%
wpDbQueries 16 +0% -0% 16 +0% -0% 0%

front-end-classic-theme

Metric d7f8000 trunk % Change
timeToFirstByte 31.3 ms +10.06% -2.72% 34.55 ms +5.64% -1.74% -9.41%
largestContentfulPaint 70 ms +2.86% -2.86% 76 ms +10.53% -0% -7.89%
lcpMinusTtfb 37.6 ms +5.45% -1.6% 42.3 ms +6.26% -0.83% -11.11%
wpBeforeTemplate 19.13 ms +10.04% -1.05% 18.99 ms +4.69% -0.63% 0.74%
wpTemplate 9.31 ms +4.94% -0.75% 12.68 ms +2.6% -2.21% -26.58%
wpTotal 28.89 ms +10.21% -2.39% 31.92 ms +5.48% -1.69% -9.49%
wpMemoryUsage 6.26 MB +0% -0% 6.33 MB +0% -0% -1.13%
wpDbQueries 9 +0% -0% 13 +0% -0% -30.77%

media-processing

Metric d7f8000 trunk % Change
mediaProcessingJpeg 322.64 ms +6.75% -0.59% 322.09 ms +1.26% -0.44% 0.17%
mediaProcessingAvif 4790.64 ms +0.9% -0.2% 4808.96 ms +0.06% -0.28% -0.38%
mediaProcessingJpegToAvif 3363.52 ms +0.19% -0.31% 3356.41 ms +0.09% -0.39% 0.21%

media-upload

Metric d7f8000 trunk % Change
jpegUploadProcessing 1410.64 ms +0.69% -1.22% 1400.16 ms +1.32% -0.78% 0.75%
pngUploadProcessing 177.9 ms +1.65% -1.55% 175.26 ms +1.82% -2.46% 1.51%
largeJpegUploadProcessing 1402.21 ms +0.65% -1.06% 1381.58 ms +2.41% -0.4% 1.49%
multipleImageUploadProcessing 1461.98 ms +1.89% -0.81% 1458.04 ms +2.76% -0.4% 0.27%

post-editor

Metric d7f8000 trunk % Change
serverResponse 279.18 ms +8.33% -9.58% 272.66 ms +8.66% -3.2% 2.39%
firstPaint 296.6 ms +25.25% -37.29% 215.91 ms +75.25% -11.26% 37.37%
domContentLoaded 807.3 ms +2.22% -2.31% 849.4 ms +3.91% -2.06% -4.96%
loaded 808.36 ms +2.2% -2.33% 850.44 ms +3.88% -2.05% -4.95%
firstContentfulPaint 369.32 ms +2.92% -6.7% 391.64 ms +4.01% -4.17% -5.7%
firstBlock 2264.54 ms +2.27% -1.68% 2399.35 ms +1.52% -0.71% -5.62%
type 16.85 ms +3.92% -10.8% 16.67 ms +4.98% -5.7% 1.08%
typeWithoutInspector 15.38 ms +12.48% -1.56% 16.36 ms +4.65% -3.3% -5.99%
typeWithTopToolbar 20.49 ms +10.54% -10.05% 23.8 ms +4.62% -5.38% -13.91%
typeContainer 6.35 ms +5.83% -3.15% 8.2 ms +6.59% -4.88% -22.56%
focus 95.17 ms +7.11% -4.01% 97.74 ms +10.87% -2.99% -2.63%
firstFocus 160.75 ms +0% -0% 188 ms +0% -0% -14.49%
selectAll 398.38 ms +3.33% -0.96% 425.24 ms +3.96% -0.89% -6.32%
listViewOpen 58.63 ms +1.52% -6.23% 59.49 ms +6% -6.02% -1.45%
inserterOpen 34.39 ms +6.95% -6.48% 33.42 ms +8.62% -7.93% 2.9%
inserterHover 11.13 ms +6.47% -9.25% 10.54 ms +12.71% -9.49% 5.6%
inserterSearch 8.18 ms +5.01% -4.65% 8.18 ms +6.85% -6.97% 0%
loadPatterns 523.4 ms +7.87% -3.93% 491.72 ms +7.33% -2.41% 6.44%
wpTotal 270.21 ms +8.73% -9.29% 265.81 ms +5.77% -3.36% 1.66%
wpMemoryUsage 14.71 MB +0% -0% 14.67 MB +0% -0% 0.28%
wpDbQueries 55 +0% -0% 55 +0% -0% 0%

site-editor

Metric d7f8000 trunk % Change
serverResponse 288.05 ms +9.43% -3.03% 294.57 ms +6.12% -5.52% -2.21%
firstPaint 168.22 ms +38.45% -6.09% 238.29 ms +21.85% -16.68% -29.41%
domContentLoaded 959.73 ms +2.52% -1.84% 952.9 ms +0.64% -1.53% 0.72%
loaded 960.5 ms +2.52% -1.85% 953.69 ms +0.64% -1.54% 0.71%
firstContentfulPaint 386.48 ms +4.17% -3.15% 396.38 ms +4.61% -2.04% -2.5%
firstBlock 3388.57 ms +1.35% -0.17% 3437.65 ms +0.23% -0.21% -1.43%
type 14.7 ms +6.53% -2.38% 15.1 ms +4.5% -4.64% -2.65%
navigate 88.22 ms +12.21% -1.51% 87.85 ms +12.23% -4.29% 0.42%
loadPatterns 1034.84 ms +12.01% -10.77% 1062 ms +6.34% -3.21% -2.56%
loadPages 983.97 ms +2.44% -0.87% 1049.88 ms +2.11% -2.85% -6.28%
wpTotal 280.07 ms +9.67% -3.22% 286.53 ms +6.25% -5.69% -2.25%
wpMemoryUsage 12.73 MB +0% -0% 12.69 MB +0% -0% 0.32%
wpDbQueries 44 +2.27% -0% 44.5 +1.12% -1.12% -1.12%

576de08 Run

🏁 Flaky tests

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.

Regression: updating a specific option will only change its value and will not corrupt others in /test/e2e/specs/editor/various/core-settings.spec.js, passed after 1 failed attempt.
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 1
+ Received  + 1

  Array [
    "rewrite_rules",
-   "SERIALIZED DATA",
+   "",
  ]
    at forEach (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/core-settings.spec.js:46:26)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/core-settings.spec.js:43:35
should cut and paste individual blocks with collapsed selection in /test/e2e/specs/editor/various/copy-cut-paste.spec.js, passed after 1 failed attempt.
Error: expect(string).toMatchSnapshot(expected) failed

  @@ -1,3 +1,3 @@
   <!-- wp:paragraph -->
  -<p>2Cut - collapsed selection</p>
  +<p>2</p>
   <!-- /wp:paragraph -->


    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/copy-cut-paste.spec.js:45:49

576de08 Run

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Pasting into an item that already has children can silently delete its existing nested list.

1 open finding
What changed in this PR

Preserves nested list items during copy/paste operations.

Changes:

  • Retains inner blocks when merging pasted list items.
  • Handles pasting into empty list items.
  • Adds end-to-end coverage and a changelog entry.
File Description
packages/​block-editor/​src/​store/​actions.js Preserves inner blocks during merges.
packages/​block-editor/​src/​components/​writing-flow/​use-clipboard-handler.js Converts invalid replacement blocks before pasting.
test/​e2e/​specs/​editor/​blocks/​list.spec.js Tests nested-list copy, cut, and paste.
packages/​block-editor/​CHANGELOG.md Documents the fix.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

@@ -1228,6 +1228,9 @@ export const __unstableSplitSelection =
...head.attributes,
...headType.merge( head.attributes, first.attributes ),
},
// Keep the merged block's inner blocks, like the nested list
// of a pasted list item.
innerBlocks: [ ...head.innerBlocks, ...first.innerBlocks ],
@youknowriad

Copy link
Copy Markdown
Contributor

cc @ellatrix this feels like a good one to review.

@Mamaduka

Copy link
Copy Markdown
Member

Thanks for the PR, @im3dabasia!

The empty list item bug has the same root cause as #82980 (Preformatted and Poetry, fix proposed in #83552). The "entirely selected" branch from #80994 runs before the splitting check and the canInsertBlockType conversion, so it skips both.

I think we can fix both here by moving that branch to the end of the handler, instead of adding the blocks.every() condition:

// Pasting over an entirely selected block replaces it, the
// equivalent of pasting into an empty block.
if (
	! hasMultiSelection() &&
	isBlockEntirelySelected(
		event.target.ownerDocument,
		firstSelectedClientId
	)
) {
	replaceBlocks(
		selectedBlockClientIds,
		newBlocks,
		newBlocks.length - 1,
		-1
	);
} else {
	__unstableSplitSelection( newBlocks );
}
event.preventDefault();

I haven't tested this. If it works, could you also add an e2e test for pasting multi-line text into an empty Preformatted block? It can go in test/e2e/specs/editor/blocks/preformatted.spec.js.

@Mamaduka

Copy link
Copy Markdown
Member

@im3dabasia, I merged #83552, but I think my suggestion is still valid. Give it a try when you have time.

@im3dabasia

Copy link
Copy Markdown
Contributor Author

I merged #83552, but I think my suggestion is still valid. Give it a try when you have time.

Thanks, @Mamaduka! This is on my priority list. I’ll look into it and test it today. I appreciate the (re)ping! I’ll get this PR into shape as soon as possible and get back to you. Thanks again!

@Mamaduka

Copy link
Copy Markdown
Member

@im3dabasia, no rush. Just wanted to keep you updated, hence the pings :)

@im3dabasia

im3dabasia commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@im3dabasia, no rush. Just wanted to keep you updated, hence the pings :)

Thanks @Mamaduka, done. Moving the check to the end works for the empty list item.

Attaching some screencast.

Screen.Recording.2026-10-15.at.3.42.42.PM.mov

@im3dabasia

Copy link
Copy Markdown
Contributor Author

Unrelated to this PR: the list item selection outline cuts through the first letter and nested bullets. It happens on trunk too, so I'd leave it out of here. Worth a separate issue?

image image

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] List Affects the List Block [Feature] Paste [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.

List block: unable to cut or copy main item if sub-items exist

4 participants