Skip to content

Grid: Draw blocks across cells in manual placement grids - #84503

Draft
draganescu wants to merge 70 commits into
add/grid-row-handlesfrom
add/grid-draw-to-insert
Draft

draganescu wants to merge 70 commits into
add/grid-row-handlesfrom
add/grid-draw-to-insert

Conversation

@draganescu

@draganescu draganescu commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

What?

Behind the gutenberg-grid-interactivity experiment, pressing on an empty cell of a manual placement grid with a mouse or pen and dragging selects a rectangle of cells. On release, the inserter opens, and the inserted block covers the selected cells. Part of the stack split out of #84340, on top of #84502.

Why?

Until now a block inserted into a manual grid always took one cell and had to be resized afterwards. Drawing the area first lets you place and size a block in one step, which is how layout tools usually work.

How?

  • A new GridVisualizerMarquee in the grid visualizer listens for pointer events on the visualizer's cells. A press on an empty cell's appender starts a drag, and pointer capture keeps the drag going across the canvas iframe. Each move works out the cell under the pointer with getGridDropTarget, and the new getBoundingGridRect helper gives the rectangle from the start cell to that cell.
  • On release, the marquee renders a GridVisualizerAppender covering the rectangle and opens its inserter. The click that follows the release is swallowed, so the start cell's own inserter doesn't open too.
  • A press without a drag works as before and opens the inserter for that one cell. Escape during the drag, a cancelled pointer, or lost pointer capture ends the drag without inserting anything. The flag that swallows the click is cleared after the current task, so it can't swallow a later keyboard activation.
  • Drawing is for a mouse or a pen only. Touches are left alone, so a touch on the grid still scrolls the page and a tap opens the inserter for one cell. We didn't use touch-action: none on the cells, because the cells cover the whole grid and that would stop the page from scrolling under a finger.
  • In WebKit, the first press after a drag that started outside the canvas, such as from the block toolbar's drag handle, comes as a pointermove that presses the main button (button 0, buttons 1), not as a pointerdown: WebKit acts as if the button were still held from the drag. The Pointer Events spec reports a button pressed while another is down that way (a chorded button press), so such a move starts drawing too. Without it, drawing only started on the second press.
  • GridVisualizerAppender gains columnSpan and rowSpan. The inserted block's placement is worked out by a new pure helper, getInsertedChildLayoutStyle:
  • Size of an inserted block. Cells can be too small for a block's placeholder, such as Image's, which needs about 300 by 300 pixels (getMinimumInsertionRect()). A block inserted into a single cell always grows to that size, to the right and down, then left and up at the grid's edges, but never past half the grid's columns or rows (getMaximumInsertionSpan()), so a block in a 2 by 2 grid stays one cell. A drawn rectangle is the size that was asked for: a Paragraph drawn across three cells of a row stays three cells wide and one row tall. Once the block renders, it only grows if it shows a placeholder (a .components-placeholder element) and the rectangle is narrower or shorter than the minimum.
    • The block always gets a placement in the default style state: the drawn cells, fitted to the grid's own columnCount and rowCount. Without it, the grid's layout sync would put the block in a cell of its own choosing in the default grid, and could add rows.
    • When a viewport style state (Tablet or Mobile) is selected, the drawn cells are also stored as the block's placement for that viewport.
    • Spans of one cell are written as no span, so an inserted pattern's root block doesn't keep spans it came with. Placement values the block had in other viewports are removed, so the block is where it was drawn everywhere.
  • Fix of trunk behavior: the cell appender used to replace the inserted block's whole style attribute with { layout: { columnStart, rowStart } }. A pattern inserted into a cell lost its root block's styles, such as colors and spacing. The rest of the block's style is now kept.

Blocks inserted without a cell

Blocks can also arrive in a manual grid without a cell of their own: from the plus next to the parent selector in a grid item's toolbar, the block inserter sidebar, Add before or Add after, Enter, paste, or a block moved in from elsewhere. The grid's layout sync (useGridLayoutSync) used to put each of them in the first free cell after the selected block, one cell big, which looked random. Now:

  • Size. A block new to the grid gets the same minimum size as one inserted into a cell, by the same rule, including the half-grid cap. Blocks that were already in the grid, such as when the grid switches to manual placement, are placed one after the other at their own size, as before.
  • Picked in an inserter: the middle of the grid. The parent selector's plus, the sidebar and the quick inserter put the block in the middle of the grid. If a block already starts there, the new one moves a cell down and to the right, as many times as the grid allows, so blocks inserted one after another don't hide each other. It covers other blocks if it has to, like a block inserted into a cell.
  • Added next to a block: next to it. Add after, Enter and paste put the block to the right of the block that was selected, or below it if there's no room. Add before puts it to the left, or above, or else after it. A block next to which nothing fits goes further down, where rows are added.
  • Replacing a block: its place. A block that replaces the selected block, such as one picked in the inserter to replace an empty paragraph or typed as /heading in one, takes that block's place and size.
  • Dragged from the inserter: where it's dropped. While blocks are dragged from the inserter sidebar, manual grids show their visualizer and drop layer, as for blocks dragged in the canvas. The drop indicator shows where the block lands, at the minimum insertion size, centered on the pointer. The rest of a dragged pattern goes next to its first block.
  • Blocks stay where they were inserted in the block order. Moving a just-inserted block into reading order moved the element being typed in, so the selection went back to the block it was added after.

How it works:

  • The placement maths is a pure helper, getInsertedBlockRect() and getInsertionSize() in get-inserted-block-rect.ts, with unit tests.
  • An inserter can't be told apart from Add after by the block order alone, since both insert after the selected block. useInsertionPoint now marks the insertion's meta with a private key (isFromInserterKey in store/private-keys.ts, a symbol, so it doesn't become a public meta contract), and the block editor store keeps it with the last inserted blocks. A new private selector, wasBlockJustInsertedFromInserter(), reads it. It is a separate key, not meta.source, because the Search block checks wasBlockJustInserted() without a source.
  • The private startDragging() action takes the blocks dragged from an inserter, kept in a new draggedInserterBlocks state and read with the private getDraggedInserterBlocks() selector, so grids know what is being dragged before it's dropped.
  • The code that copies a block's child layout to the block that takes its place is in components/grid/child-layout.ts (copyChildLayout()), used by the layout sync. This PR doesn't add a blocks.switchToBlockType.transformedBlock filter: a global one would change saved content for transforms in every layout type, and it's proposed on its own in Block Editor: Keep a block's grid position and span when it is transformed #85045. Within a manual grid, a transform replaces the selected block, so the sync gives the result its place.
  • Without the experiment, nothing here changes behavior: useGridLayoutSync places blocks as on trunk (the first free cell after the selected block, one cell big) and selects only the selected block's default layout, and blocks dragged from an inserter don't show any grid's tools. With the experiment, the sync only selects the style of this grid's selected item, so styling a block elsewhere doesn't re-render every grid.

Testing Instructions

  1. Enable the "Grid interactivity" experiment in Gutenberg > Experiments.

  2. Add a Grid block, and in its settings switch to manual placement with a few columns and rows.

  3. Select the grid. Press on an empty cell and drag across several cells. A highlighted rectangle follows the pointer.

  4. Release. The inserter opens over the rectangle. Pick a block; it covers the selected cells.

  5. Repeat and press Escape or click elsewhere instead of picking a block. The rectangle goes away and nothing is inserted.

  6. Click an empty cell without dragging. The inserter opens for that cell. The inserted block grows from that cell to about 300 by 300 pixels, so that a placeholder fits.

  7. Draw three cells of one row and insert a Paragraph: it stays three cells wide and one row tall. Draw the same and insert an Image: it grows to fit its placeholder.

  8. Start drawing a rectangle and press Escape before releasing. The rectangle goes away, and releasing doesn't open an inserter.

  9. Switch the style state to Tablet, with the grid set to more columns on Tablet than on Desktop if you like. Draw a rectangle and insert a block. It covers the drawn cells on Tablet. Switch back to Desktop: the block has a placement there too, the same cells fitted to the Desktop grid, and the grid gains no rows.

  10. Insert a pattern whose root block has a background color into an empty cell. The inserted block keeps its background color.

  11. On a touch device, or with touch emulation, swipe across the grid. The page scrolls and no rectangle is drawn. Tapping an empty cell opens its inserter.

  12. Select a block in a manual grid and use the plus next to the parent selector in its toolbar to insert an Image. It goes in the middle of the grid, about 300 by 300 pixels. Do it again: the second one is a cell down and to the right of the first.

  13. Put the cursor at the end of a paragraph in the grid and press Enter, or use Add after from the block's options. The new paragraph is next to it, at the minimum size, and keeps the cursor: typing goes into it.

  14. In that empty paragraph, type /heading and press Enter. The heading takes the paragraph's place and size.

  15. Open the block inserter sidebar and drag an Image onto the grid. A drop indicator follows the pointer, and the image lands where it's dropped.

  16. In a grid with two columns and two rows, insert a block from the plus: it takes one cell.

Testing Instructions for Keyboard

  1. With a manual grid selected, tab to an empty cell's appender and press Enter.
  2. Pick a block from the inserter. It is inserted in that cell and grows from it to the minimum insertion size, as in step 6.
  3. With a block in the grid selected, press Enter at the end of its text, or press the Add after shortcut. The new block is next to it, and typing goes into it.

Screenshots or screencast

To add.

Use of AI Tools

Split out of #84340 with Claude Code (Claude Opus 5.5), which ported the code and tests from that branch. The split and its decisions were made in conversation with the PR author.

🤖 Generated with Claude Code

draganescu and others added 4 commits October 10, 2026 11:35
Pressing on an empty cell of a manual grid and dragging selects a
rectangle of cells. On release, the inserter opens, and the block
inserted covers the selected cells. A press without a drag still opens
the inserter for the one cell, and so does the keyboard.

Inserted blocks get their position and span in the selected style
state, like blocks moved by dragging, and keep any style the inserter
gave them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/src/components/grid/test/utils.test.js
A block inserted into a manual grid always gets a placement in the
default style state: the cells drawn, fitted to the grid's own columns
and rows. When a viewport is selected, the drawn cells are also its
placement there. Before, a block drawn on Tablet or Mobile only had a
placement for that viewport, so the grid's layout sync put it in a cell
of its own choosing in the default grid, and could add rows. Spans of
one cell are written as no span, and placements the block had in other
viewports are removed, so an inserted pattern's root block covers the
cells drawn. The placement is worked out by a new pure helper,
getInsertedChildLayoutStyle, which is unit tested.

Drawing ends when the pointer loses its capture, and Escape cancels it
without opening the inserter. The flag that swallows the click after a
drag is cleared after the current task, so it can't swallow a later
keyboard activation. Touches don't draw, so the page still scrolls
under a finger, and a tap opens the inserter for one cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the [Package] Block editor /packages/block-editor label Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

📦 Bundle size

Size Change: +4.67 kB (+0.05%)

Total Size: 9.08 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 614 kB +3.93 kB (+0.64%)
build/styles/block-editor/style-rtl.css 21.3 kB +169 B (+0.8%)
build/styles/block-editor/style-rtl.min.css 18.4 kB +204 B (+1.12%)
build/styles/block-editor/style.css 21.4 kB +170 B (+0.8%)
build/styles/block-editor/style.min.css 18.3 kB +204 B (+1.12%)

2a0a715 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 63bbf25 trunk % Change
timeToFirstByte 35.2 ms +15.06% -5.11% 35.05 ms +9.84% -2.28% 0.43%
largestContentfulPaint 64 ms +0% -3.13% 62 ms +3.23% -3.23% 3.23%
lcpMinusTtfb 27.4 ms +11.13% -14.96% 25.75 ms +4.85% -4.47% 6.41%
wpBeforeTemplate 18.35 ms +26.27% -3.16% 18.56 ms +10.56% -1.94% -1.13%
wpTemplate 13.01 ms +3.46% -5.15% 13.29 ms +6.7% -5.12% -2.11%
wpTotal 31.72 ms +16.71% -3.88% 31.65 ms +11.91% -1.33% 0.22%
wpMemoryUsage 7.68 MB +0% -0% 7.65 MB +0% -0% 0.43%
wpDbQueries 16 +0% -0% 16 +0% -0% 0%

front-end-classic-theme

Metric 63bbf25 trunk % Change
timeToFirstByte 29.3 ms +7.17% -1.71% 32.15 ms +6.38% -1.87% -8.86%
largestContentfulPaint 68 ms +5.88% -0% 74 ms +2.7% -2.7% -8.11%
lcpMinusTtfb 38.85 ms +6.44% -5.66% 40.45 ms +4.45% -3.96% -3.96%
wpBeforeTemplate 16.4 ms +9.39% -1.28% 17.76 ms +4.5% -1.35% -7.66%
wpTemplate 10.09 ms +8.03% -2.68% 11.55 ms +6.41% -3.98% -12.64%
wpTotal 26.74 ms +7.29% -2.21% 29.25 ms +7.93% -1.3% -8.58%
wpMemoryUsage 6.37 MB +0% -0% 6.34 MB +0% -0% 0.54%
wpDbQueries 13 +0% -0% 13 +0% -0% 0%

media-processing

Metric 63bbf25 trunk % Change
mediaProcessingJpeg 230.52 ms +7.17% -1.93% 231.85 ms +1.47% -1.54% -0.57%
mediaProcessingAvif 3750.75 ms +0.37% -0.66% 3697.36 ms +1.45% -1.97% 1.44%
mediaProcessingJpegToAvif 2558.76 ms +0.74% -0.7% 2573.17 ms +1.15% -1.12% -0.56%

media-upload

Metric 63bbf25 trunk % Change
jpegUploadProcessing 1406.93 ms +1.65% -31.67% 1395.2 ms +0.53% -0.6% 0.84%
pngUploadProcessing 167.95 ms +9.32% -2.24% 173.16 ms +4.41% -1.43% -3.01%
largeJpegUploadProcessing 1387.65 ms +0.73% -0.74% 1369.38 ms +1.64% -32.98% 1.33%
multipleImageUploadProcessing 1491.93 ms +0.58% -2.02% 1467.6 ms +1.02% -0.34% 1.66%

post-editor

Metric 63bbf25 trunk % Change
serverResponse 305.86 ms +6.37% -5.82% 328.16 ms +3.61% -2.97% -6.8%
firstPaint 208.33 ms +64.71% -26.29% 330.01 ms +16.93% -33.9% -36.87%
domContentLoaded 929.42 ms +2.23% -4.81% 902.7 ms +3.53% -3.27% 2.96%
loaded 930.35 ms +2.24% -4.78% 903.62 ms +3.53% -3.27% 2.96%
firstContentfulPaint 408.67 ms +0.98% -16.03% 388.56 ms +3.3% -14.2% 5.18%
firstBlock 2604.58 ms +1.79% -3.55% 2508.81 ms +1.46% -0.51% 3.82%
type 15.17 ms +1.91% -1.91% 14.95 ms +5.35% -2.54% 1.47%
typeWithoutInspector 15.03 ms +1.4% -4.19% 14.64 ms +2.46% -1.5% 2.66%
typeWithTopToolbar 18.64 ms +7.94% -1.34% 19.27 ms +2.44% -3.89% -3.27%
typeContainer 6.71 ms +2.68% -8.49% 6.56 ms +9.76% -4.88% 2.29%
focus 89.91 ms +5.37% -2.84% 92.56 ms +9.81% -3.27% -2.86%
firstFocus 194.38 ms +0% -0% 174.48 ms +0% -0% 11.41%
selectAll 437.11 ms +0.11% -1.58% 460.18 ms +0.1% -5.38% -5.01%
listViewOpen 59.19 ms +11.76% -4.26% 58.52 ms +4.19% -8.17% 1.14%
inserterOpen 30.19 ms +13.65% -5.63% 31.42 ms +3.09% -7.03% -3.91%
inserterHover 9.47 ms +9.71% -4.86% 9.15 ms +7.54% -8.2% 3.5%
inserterSearch 7.51 ms +9.72% -10.39% 6.92 ms +6.36% -4.91% 8.53%
loadPatterns 498.74 ms +1.16% -3.05% 503.97 ms +1.62% -4.63% -1.04%
wpTotal 297.61 ms +6.58% -5.96% 319.33 ms +3.85% -2.97% -6.8%
wpMemoryUsage 14.71 MB +0% -0% 14.68 MB +0% -0% 0.24%
wpDbQueries 55 +0% -1.82% 55 +0% -0% 0%

site-editor

Metric 63bbf25 trunk % Change
serverResponse 508.84 ms +6.32% -3.49% 490.31 ms +1.93% -5.77% 3.78%
firstPaint 249.83 ms +5.86% -10.4% 265.12 ms +7.75% -1.6% -5.77%
domContentLoaded 1296.66 ms +1.51% -1.7% 1280.04 ms +2.19% -0.67% 1.3%
loaded 1297.66 ms +1.54% -1.68% 1281.23 ms +2.23% -0.69% 1.28%
firstContentfulPaint 512.3 ms +2.9% -1.51% 527.48 ms +1.96% -1.11% -2.88%
firstBlock 4629.72 ms +0.38% -0.43% 4598.77 ms +0.24% -0.53% 0.67%
type 18.7 ms +4.12% -6.31% 18.99 ms +3.95% -3.69% -1.53%
navigate 118.7 ms +11.86% -3.87% 115.47 ms +8.61% -6.88% 2.8%
loadPatterns 1276.98 ms +4.51% -3.14% 1254.5 ms +1.47% -2.84% 1.79%
loadPages 999.79 ms +1.27% -1.79% 985.05 ms +3.12% -0.94% 1.5%
wpTotal 499.52 ms +6.45% -3.58% 480.97 ms +2.05% -5.86% 3.86%
wpMemoryUsage 12.73 MB +0% -0% 12.69 MB +0% -0% 0.27%
wpDbQueries 44 +2.27% -0% 44 +2.27% -0% 0%

2a0a715 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.

Custom Completer: should allow newlines after multiple option completions in /test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 3
+ Received  + 3

  <!-- wp:paragraph -->
- <p>🫐 test</p>
+ <p>🍎 test</p>
  <!-- /wp:paragraph -->

  <!-- wp:paragraph -->
- <p>🫐 test</p>
+ <p>🍎 test</p>
  <!-- /wp:paragraph -->

  <!-- wp:paragraph -->
  <p>🫐 test</p>
  <!-- /wp:paragraph -->

  <!-- wp:paragraph -->
- <p>🫐 test</p>
+ <p>🍎 test</p>
  <!-- /wp:paragraph -->

  <!-- wp:paragraph -->
  <p></p>
  <!-- /wp:paragraph -->

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js:466:6
Custom Completer: should not insert disabled options in /test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 1
+ Received  + 1

  <!-- wp:paragraph -->
- <p>Sorry, we are all out of ~g grapes.</p>
+ <p>Sorry, we are all out of 🍎 grapes.</p>
  <!-- /wp:paragraph -->

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js:391:7
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
Undo restores the post selection when another synced entity is loaded (RTC WebSocket) in /test/e2e/specs/editor/collaboration/collaboration-undo-redo.spec.ts, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 2
+ Received  + 2

  Object {
    "attributeKey": "content",
    "blockIndex": 0,
    "content": "abcdef",
-   "endOffset": 3,
-   "offset": 3,
+   "endOffset": 2,
+   "offset": 2,
  }

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/collaboration/collaboration-undo-redo.spec.ts:363:5
can apply custom gradient background color in /test/e2e/specs/editor/blocks/buttons.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 1
+ Received  + 1

  <!-- wp:buttons -->
- <div class="wp-block-buttons"><!-- wp:button {"style":{"background":{"gradient":"linear-gradient(135deg,rgb(255,0,0) 0%,rgb(0,255,0) 100%)"}}} -->
+ <div class="wp-block-buttons"><!-- wp:button {"style":{"background":{"gradient":"linear-gradient(135deg,rgb(6,147,227) 0%,rgb(0,255,0) 100%)"}}} -->
  <div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Content</a></div>
  <!-- /wp:button --></div>
  <!-- /wp:buttons -->
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/buttons.spec.js:411:21
can be converted when nested to paragraphs in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 1
+ Received  + 1

  <!-- wp:paragraph -->
- <p>one</p>
+ <p>ne</p>
  <!-- /wp:paragraph -->

  <!-- wp:paragraph -->
  <p>two</p>
  <!-- /wp:paragraph -->

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:417:52
does not select blocks in the canvas preview when editing from the sidebar (Site Editor v2) in /test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js, passed after 1 failed attempt.
Error: expect(locator).toBeFocused() failed

Locator: getByRole('combobox', { name: 'Search or type URL' })
Expected: focused
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toBeFocused" getByRole('combobox', { name: 'Search or type URL' }) with timeout 5000ms
  - waiting for getByRole('combobox', { name: 'Search or type URL' })

    at LinkControl.useLinkControlSearch (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js:389:47)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js:179:21
renders srcset on the front end after publishing a CSM-uploaded image in /test/e2e/specs/editor/various/client-side-media-processing.spec.js, passed after 1 failed attempt.
Error: apiRequestContext.fetch: socket hang up
Call log:
  - → GET http://localhost:8889/wp-json/wp/v2/media/187
    - user-agent: Playwright/1.63.0 (x64; ubuntu 24.04) node/24.20 CI/1
    - accept: */*
    - accept-encoding: gzip,deflate,br
    - X-WP-Nonce: 27bfde1073
    - cookie: wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_23778236db82f19306f247e20a353a99=admin%7C1792965593%7Cgj4BnlAUT6TSkOmrPTEoXup3BFhth5yV0SLCQ4J1eAN%7C4988af6dc44d11a84f0211a41c32621c674b63019ce2a99843f91dd684128575; wp-settings-time-1=1792792981

    at RequestUtils.rest (/home/runner/work/gutenberg/gutenberg/packages/e2e-test-utils-playwright/src/request-utils/rest.ts:112:39)
    at MediaProcessingUtils.getMediaDetails (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/client-side-media-processing.spec.js:169:29)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/client-side-media-processing.spec.js:772:44
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
should format text across blocks in /test/e2e/specs/editor/various/multi-block-selection.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 1
+ Received  + 1

@@ -1,9 +1,9 @@
  Array [
    Object {
      "attributes": Object {
-       "content": "ab",
+       "content": "b",
      },
      "name": "core/paragraph",
    },
    Object {
      "attributes": Object {

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/multi-block-selection.spec.js:2170:41
should gradually multi-select in /test/e2e/specs/editor/various/multi-block-selection.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 3
+ Received  + 7

  Array [
    Object {
-     "name": "core/column",
+     "attributes": Object {
+       "content": "1",
+       "dropCap": false,
      },
-   Object {
-     "name": "core/column",
+     "clientId": "63386edb-269b-410a-a59a-6292db5e7492",
+     "innerBlocks": Array [],
+     "isValid": true,
+     "name": "core/paragraph",
    },
  ]

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/multi-block-selection.spec.js:1151:5
should insert a line break on shift+enter in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 1
+ Received  + 1

  <!-- wp:list -->
  <ul class="wp-block-list"><!-- wp:list-item -->
- <li>a<br></li>
+ <li><br></li>
  <!-- /wp:list-item --></ul>
  <!-- /wp:list -->

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:1210:52
should merge a following sibling list into the outermost list with Delete from a nested item (#77245) in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 2
+ Received  + 2

@@ -8,11 +8,11 @@
          "innerBlocks": Array [
            Object {
              "innerBlocks": Array [
                Object {
                  "attributes": Object {
-                   "content": "b‸",
+                   "content": "b",
                  },
                  "name": "core/list-item",
                },
              ],
              "name": "core/list",
@@ -25,11 +25,11 @@
    },
    Object {
      "innerBlocks": Array [
        Object {
          "attributes": Object {
-           "content": "1",
+           "content": "‸1",
          },
          "innerBlocks": Array [
            Object {
              "innerBlocks": Array [
                Object {

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:2854:41
should offer the formats every selected block allows in /test/e2e/specs/editor/various/multi-block-selection.spec.js, passed after 1 failed attempt.
Error: expect(received).toEqual(expected) // deep equality

- Expected  - 2
+ Received  + 2

  Object {
-   "anchorOffset": 1,
+   "anchorOffset": 0,
    "blocks": 2,
-   "focusOffset": 1,
+   "focusOffset": 0,
  }

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/multi-block-selection.spec.js:2504:5
should only mutate text data on input in /test/e2e/specs/editor/various/rich-text.spec.js, passed after 1 failed attempt.
Error: locator.evaluate: Error: The selection change event listener was never called.
    at eval (eval at evaluate (:311:30), <anonymous>:6:15)
    at UtilityScript.evaluate (<anonymous>:313:16)
    at UtilityScript.<anonymous> (<anonymous>:1:44)
    at eval (eval at evaluate (:311:30), <anonymous>:6:15)
    at UtilityScript.evaluate (<anonymous>:313:16)
    at UtilityScript.<anonymous> (<anonymous>:1:44)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/rich-text.spec.js:407:42
should outdent list items when deleting an empty parent at the top in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 19
+ Received  +  1

- Array [
-   Object {
-     "innerBlocks": Array [
-       Object {
-         "attributes": Object {
-           "content": "a",
-         },
-         "name": "core/list-item",
-       },
-       Object {
-         "attributes": Object {
-           "content": "b",
-         },
-         "name": "core/list-item",
-       },
-     ],
-     "name": "core/list",
-   },
- ]
+ Array []

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:3074:41
should remove an anchored list when deleting its last item in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 1
+ Received  + 1

@@ -5,11 +5,11 @@
      },
      "innerBlocks": Array [
        Object {
          "attributes": Object {
            "anchor": "item",
-           "content": "x",
+           "content": "",
          },
          "name": "core/list-item",
        },
      ],
      "name": "core/list",

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:2961:41
should replace selected non-breaking spaces from the toolbar in /test/e2e/specs/editor/various/rich-text.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 1
+ Received  + 1

  Array [
    Object {
      "attributes": Object {
-       "content": "one two three",
+       "content": "ne two three",
      },
      "name": "core/paragraph",
    },
  ]

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/rich-text.spec.js:1050:41

Truncated. See the full report.

2a0a715 Run

# Conflicts:
#	packages/block-editor/CHANGELOG.md
@draganescu
draganescu added this pull request to stack #84511 October 10, 2026 17:34
draganescu and others added 18 commits October 10, 2026 13:47
# Conflicts:
#	packages/block-editor/CHANGELOG.md
Drawing a block across the cells of a manual placement grid now draws the
lines of the cells that blocks are in, as dragging a block does, until the
block is inserted or the inserter closes. The marquee's state moves up to
the manual grid visualizer, which draws the cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/src/components/grid/grid-visualizer.jsx
# Conflicts:
#	packages/block-editor/src/components/grid/grid-visualizer.jsx
The cells being drawn moved up to the manual grid visualizer so that it
could draw the lines over blocks while a block was drawn. The lines are
drawn over every cell again, so the state goes back to the marquee.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pressing on an empty cell and dragging across a manual placement grid
selects a rectangle of cells, but the grid only drew its cells while a
block was dragged into it. The cells are now drawn, the same way, while a
selection is shown, including while its inserter is open, so the cells
being selected can be seen against the rest of the grid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty cell now draws its rounded square while it is hovered or focused,
or its inserter is open, so it is clear which cell a click inserts a block
into. The square is also the appender's focus ring, and the appender drops
its own square border, line and tint in these cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a manual grid with many columns, on a tablet or mobile canvas, a cell can
be a few dozen pixels across, too small for a block like Image to show its
placeholder. A block inserted into one cell, or into a drawn range of cells,
now gets more of the free cells around them until it is at least 160px
across and down, the size at which `Placeholder` switches to its medium
layout. It grows right and down first, then left and up, and never past the
grid's tracks or into cells other blocks cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
draganescu and others added 3 commits October 21, 2026 11:39
Moving a block placed by the grid layout sync into reading order moved the
focused block's element, so the selection went back to the block it was
added after, and typing went there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Converts getInsertedChildLayoutStyle and getMinimumInsertionRect, with
their unit tests, to TypeScript so that the files this PR adds satisfy
the gutenberg/use-typescript rule. getInsertedBlockRect now imports the
GridTrack type from get-minimum-insertion-rect instead of redeclaring
it. No runtime changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
draganescu and others added 3 commits October 21, 2026 15:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/src/components/grid/grid-visualizer.tsx
#	packages/block-editor/src/components/grid/use-grid-layout-sync.ts
#	packages/block-editor/src/components/grid/utils.ts
#	packages/block-editor/src/components/inserter-draggable-blocks/index.tsx
#	packages/block-editor/src/hooks/layout-child.tsx
#	packages/block-editor/src/hooks/layout.tsx
draganescu and others added 3 commits October 21, 2026 23:43
…iment

- Drop the global blocks.switchToBlockType.transformedBlock filter, which
  changed saved content for transforms in every layout; it is proposed on
  its own in #85045. Blocks that replace the selected item of a manual grid
  still take its place through the layout sync.
- Mark blocks picked in an inserter with a private key in the insert
  action's meta instead of the public meta.isFromInserter.
- Without the gutenberg-grid-interactivity experiment, useGridLayoutSync
  places blocks as trunk does (first free cell after the selected block)
  and selects only the selected block's layout. With it, it only selects the
  style of this grid's selected item, so styling other blocks doesn't
  re-render every grid.
- Inserter drags only show and feed the grid tools of manual grids with the
  experiment on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@draganescu
draganescu removed this pull request from stack #84511 October 22, 2026 14:01
@draganescu
draganescu added this pull request to stack #85397 October 22, 2026 14:02
draganescu and others added 16 commits October 22, 2026 18:57
# Conflicts:
#	packages/block-editor/src/components/grid/grid-visualizer.tsx
Blocks dragged from an inserter were kept in a `draggedInserterBlocks` store
state, so that manual grids could take them, which stayed behind if the
inserter closed during the drag. Trunk's inserter already adds a
`wp-block:<name>` data type for each dragged block, which can be read while
dragging. Manual grids now read the names from the drag events, with
`useInserterDragBlockNames()`, to know whether they can take the blocks, and
read the blocks from the drop's data. `startDragging()` takes no blocks
again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/src/hooks/layout-child.tsx
Every manual grid child injected the same placeholder rule, so a grid
with 100 children had 100 copies. The grid's layout style now has one
rule for all of its children.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The layout sync of every grid selected the whole style of its selected
item, so any style change of that item, such as dragging a color or
spacing slider, re-rendered the grid. It now selects only the item's
default and viewport layouts, the same objects until a layout changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

[Package] Block editor /packages/block-editor [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant