Skip to content

Grid: Show the canvas handles in a small toolbar, and add a Canvas block - #84947

Draft
draganescu wants to merge 49 commits into
add/grid-captured-toolbar-drag-handlefrom
add/grid-item-handles-toolbar
Draft

draganescu wants to merge 49 commits into
add/grid-captured-toolbar-drag-handlefrom
add/grid-item-handles-toolbar

Conversation

@draganescu

@draganescu draganescu commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What?

Behind the gutenberg-grid-interactivity experiment, the handles of a selected block in a manual placement grid become one small toolbar under the block, with a new button that deletes the block. Part of the grid stack, on top of #84894. This is a try to see how manipulating blocks on the canvas feels with the handles together.

Why?

The handles are what you reach for when arranging blocks on the canvas, and with one in each corner the pointer has to travel around the block between them. Together, they are in one place and read as one small block toolbar, in the editor's own visual language. Deleting is the other common action when arranging a layout, and otherwise needs the block toolbar's options menu.

Starting a free-form layout took several steps: insert a Grid, switch it to manual placement, set its columns and rows, and make it full width. Canvas does that in one.

How?

  • GridItemHandles (components/grid/grid-item-handles.tsx) owns the popover over the block, which follows its rotation, and draws the toolbar in it. The toolbar is placed upright, 8px under the block's bottom right corner and right-aligned with it, clear of the resize handle in the middle of the bottom side.
    • On a rotated block, it is under the block's lowest corner, or the right one of two level corners, so it stays close to the block without covering it. The corner is picked when a rotation is released (getLowestCorner() in rotation.js, from the saved angle); while the block turns, the toolbar follows the corner it started from.
    • The toolbar is inside the overlay that turns with the block, because the rotate handle turns the block around that overlay's center. The corner has a fixed place in the overlay, so getCornerToolbarStyle() places the toolbar there with percentages, adds the gap turned into the overlay's frame, and turns the toolbar back by the block's angle. Unit tests check the corner picked and the toolbar's place on screen at several angles.
  • GridItemDragHandle, GridItemRotator and the new GridItemDeleteHandle (components/grid/grid-item-delete-handle.tsx) no longer have popovers of their own. They are GridItemHandle buttons in the toolbar. The rotator finds the block's center from the toolbar's box, which turns around it.
  • The toolbar has the block toolbar's border and corners, with 24px buttons. Its colors, corners and cursor come from the design system tokens (--wpds-*). No stroke token is as dark as the toolbar's border, so the border stays $gray-900.
  • In GridTools (hooks/layout-child.jsx), the toolbar shows when any of its buttons does. The drag button shows for blocks that can be moved, the rotate button as before, and the delete button for blocks that can be removed (canRemoveBlock).
  • Canvas. A Group variation, group-canvas, in group/variations.js next to Grid, with the layout icon. Its attributes are align: 'full' and layout: { type: 'grid', columnCount: 12, rowCount: 12, isManualPlacement: true }. It sets no padding, so it gets whatever the theme gives Groups; a variation's attributes are fixed when it's registered, so it can't read the theme's spacing, and spacing preset slugs differ between themes.
    • Its isActive keys are layout.type and layout.isManualPlacement. The editor picks the matching variation with the most keys, so a manual grid shows as Canvas and other grids still show as Grid.
    • It is only registered when window.__experimentalEnableGridInteractivity is set, since manual grids can only be edited on the canvas with that experiment. The flag is set by an inline script before wp-block-editor, which wp-block-library depends on, so it is set when the variations are read.
  • Delete calls removeBlock( clientId, true ), which selects the block before it. Like the other handles, the toolbar only works with a pointer and is aria-hidden: keyboard users delete blocks from the block toolbar's options menu.

Testing Instructions

  1. In Gutenberg > Experiments, enable "Grid interactivity" and "Block rotation".
  2. Add a Grid block, set "Grid item position" to Manual, and add a Paragraph and an Image in different cells.
  3. Select the Paragraph. A small toolbar with drag, rotate and delete buttons appears under its bottom right corner.
  4. Drag the Paragraph by the drag button to another cell. It stays in place, dimmed, while the grid shows where it will land, then moves there.
  5. Drag the rotate button around the block. The block turns by as much as the pointer does, and the button shows the angle. The toolbar stays upright and follows the corner it started under. Release the pointer: the toolbar moves under the block's lowest corner.
  6. Select the Image and click the delete button. The Image is removed. Undo brings it back.
  7. Lock a block against removal. Its toolbar has no delete button.
  8. Open the inserter and search for "Canvas". Insert it: it is a full width grid of 12 by 12 cells in manual mode, the block settings call it Canvas, and hovering a cell shows its "Add block" button. Switch it to Auto in the settings: it shows as Grid again. Without the "Grid interactivity" experiment, Canvas isn't in the inserter.

Checked with Playwright in Chromium: dragging by the drag button and by an Image's body moves the block, rotating turns the block by the pointer's angle, Delete removes the block, and Canvas inserts from the inserter as a full width 12 by 12 manual grid. New unit tests cover Canvas only being offered with the experiment and which variation a grid shows as. The unit tests for the rotator and the grid tools are updated for the toolbar.

Known issues

  • The "Add block" tooltip of an empty cell under the block can cover the toolbar. The tooltip takes pointer events and stays open while hovered, so moving from that cell onto the toolbar can leave the tooltip in the way.

🤖 Generated with Claude Code

draganescu and others added 2 commits October 16, 2026 19:02
The drag and rotate handles were two separate buttons in different
corners of the block. They are now one small block toolbar outside the
block, under its bottom right corner and clear of the bottom resize
handle, with a third button that deletes the block.

GridItemHandles owns the overlay over the block and the toolbar, which
stays upright as the block turns. The drag, rotate and delete handles
are buttons in it. The delete button only shows when the block can be
removed, and like the others it only works with a pointer: keyboard
users delete blocks from the block toolbar's options menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/components/grid/grid-item-rotator.tsx
@draganescu
draganescu added this pull request to stack #84511 October 16, 2026 16:05
@github-actions github-actions Bot added the [Package] Block editor /packages/block-editor label Oct 16, 2026
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

Preview in WordPress Playground

📦 Bundle size

Size Change: +833 B (+0.01%)

Total Size: 9.09 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 620 kB +337 B (+0.05%)
build/scripts/block-library/index.min.js 450 kB +310 B (+0.07%)
build/styles/block-editor/style-rtl.css 21.6 kB +53 B (+0.25%)
build/styles/block-editor/style-rtl.min.css 18.6 kB +41 B (+0.22%)
build/styles/block-editor/style.css 21.6 kB +53 B (+0.25%)
build/styles/block-editor/style.min.css 18.6 kB +39 B (+0.21%)

c80227e Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 8a587de trunk % Change
timeToFirstByte 35.2 ms +15.91% -3.55% 34.6 ms +19.08% -3.32% 1.73%
largestContentfulPaint 64 ms +3.13% -6.25% 60 ms +13.33% -3.33% 6.67%
lcpMinusTtfb 25.7 ms +8.56% -6.61% 25.8 ms +11.63% -10.47% -0.39%
wpBeforeTemplate 18.27 ms +22.99% -2.9% 18.31 ms +12.4% -1.8% -0.22%
wpTemplate 13.46 ms +4.61% -4.09% 13.46 ms +11.29% -5.5% 0%
wpTotal 32.2 ms +14.44% -3.23% 31.61 ms +19.68% -3.23% 1.87%
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 8a587de trunk % Change
timeToFirstByte 31.5 ms +5.4% -6.67% 29.35 ms +8.35% -2.56% 7.33%
largestContentfulPaint 70 ms +8.57% -2.86% 68 ms +5.88% -0% 2.94%
lcpMinusTtfb 38.75 ms +12.65% -3.48% 38.85 ms +2.7% -3.09% -0.26%
wpBeforeTemplate 17.18 ms +16.24% -3.9% 16.52 ms +11.44% -1.94% 4%
wpTemplate 10.55 ms +9.76% -4.83% 10.22 ms +7.83% -4.6% 3.23%
wpTotal 28.9 ms +5.36% -6.44% 26.85 ms +8.86% -3.13% 7.64%
wpMemoryUsage 6.37 MB +0% -0% 6.34 MB +0% -0% 0.54%
wpDbQueries 13 +0% -0% 13 +0% -0% 0%

media-processing

Metric 8a587de trunk % Change
mediaProcessingJpeg 229.86 ms +2.26% -1.57% 230.99 ms +0.71% -2.31% -0.49%
mediaProcessingAvif 3708.64 ms +1.29% -0.33% 3693.43 ms +0.15% -0.67% 0.41%
mediaProcessingJpegToAvif 2593.24 ms +0.48% -0.35% 2572.95 ms +0.33% -0.53% 0.79%

media-upload

Metric 8a587de trunk % Change
jpegUploadProcessing 1417.01 ms +3.43% -1.56% 910.11 ms +1.9% -1.64% 55.7%
pngUploadProcessing 178.95 ms +5.48% -4.08% 163.58 ms +2.22% -1.69% 9.4%
largeJpegUploadProcessing 1408.75 ms +1.03% -32.9% 1397.28 ms +0.57% -30.67% 0.82%
multipleImageUploadProcessing 1493 ms +1.87% -0.73% 1479.35 ms +0.27% -1.11% 0.92%

post-editor

Metric 8a587de trunk % Change
serverResponse 290.12 ms +4.29% -10.05% 290.44 ms +3.81% -6.44% -0.11%
firstPaint 295.2 ms +19.32% -42.07% 200.99 ms +75.01% -30.71% 46.87%
domContentLoaded 750.02 ms +1.43% -4.69% 745.16 ms +2.96% -0.19% 0.65%
loaded 750.87 ms +1.43% -4.65% 745.91 ms +2.98% -0.15% 0.66%
firstContentfulPaint 340.42 ms +6.71% -8.73% 348.83 ms +1.38% -17.48% -2.41%
firstBlock 2084.85 ms +3.24% -2.01% 2092.85 ms +1.15% -0.92% -0.38%
type 13.13 ms +4.72% -2.67% 13.56 ms +5.38% -2.65% -3.17%
typeWithoutInspector 13.36 ms +1.12% -3.52% 14.12 ms +4.67% -2.97% -5.38%
typeWithTopToolbar 17.13 ms +10.45% -1.81% 17.65 ms +3.85% -5.44% -2.95%
typeContainer 6.09 ms +7.22% -7.88% 6.4 ms +4.37% -5.31% -4.84%
focus 69.24 ms +14.4% -4.78% 75.87 ms +8.12% -3.85% -8.74%
firstFocus 155 ms +0% -0% 150.97 ms +0% -0% 2.67%
selectAll 333.25 ms +1.44% -4.42% 334.04 ms +6.62% -3.52% -0.24%
listViewOpen 50.08 ms +4.55% -5.21% 45.06 ms +14.51% -3.68% 11.14%
inserterOpen 28.75 ms +15.72% -8.52% 26.12 ms +4.75% -4.21% 10.07%
inserterHover 9 ms +21% -4.56% 8.18 ms +4.16% -3.55% 10.02%
inserterSearch 6.83 ms +9.81% -6.59% 5.91 ms +6.09% -3.05% 15.57%
loadPatterns 426.24 ms +2.34% -1.71% 406.63 ms +1.97% -1.77% 4.82%
wpTotal 283.29 ms +4.32% -10.22% 283.9 ms +3.44% -6.69% -0.21%
wpMemoryUsage 14.72 MB +0% -0% 14.68 MB +0% -0% 0.24%
wpDbQueries 55 +0% -0% 55 +0% -0% 0%

site-editor

Metric 8a587de trunk % Change
serverResponse 402.55 ms +9.05% -3.52% 423.97 ms +8.31% -2.83% -5.05%
firstPaint 248.52 ms +13.83% -5.95% 243.43 ms +14.29% -3.31% 2.09%
domContentLoaded 1229.34 ms +2.07% -0.9% 1320.83 ms +1.26% -2% -6.93%
loaded 1230.22 ms +2.07% -0.86% 1321.9 ms +1.29% -2% -6.94%
firstContentfulPaint 490.2 ms +3.66% -23.21% 499 ms +7.95% -20.85% -1.76%
firstBlock 4328.39 ms +0.61% -1.19% 4594.54 ms +0.31% -1.12% -5.79%
type 18.24 ms +1.97% -6.74% 21.33 ms +1.55% -6.8% -14.49%
navigate 109.66 ms +11.5% -3.36% 130.81 ms +2.56% -3.78% -16.17%
loadPatterns 1181.95 ms +3.51% -5.61% 1282.55 ms +5.14% -3.56% -7.84%
loadPages 978.95 ms +5.29% -3.94% 975.63 ms +1.75% -5.78% 0.34%
wpTotal 392.13 ms +9.29% -3.61% 413.52 ms +8.41% -2.94% -5.17%
wpMemoryUsage 12.74 MB +0% -0% 12.71 MB +0% -0% 0.24%
wpDbQueries 44 +2.27% -0% 44 +2.27% -0% 0%

c80227e 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.

Backspace in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  -  5
+ Received  + 11

@@ -1,25 +1,31 @@
  Array [
    Object {
      "innerBlocks": Array [
        Object {
          "attributes": Object {
-           "content": "1",
+           "content": "1‸a",
          },
-         "innerBlocks": Array [
+         "innerBlocks": Array [],
+         "name": "core/list-item",
+       },
        Object {
+         "attributes": Object {
+           "content": "2",
+         },
          "innerBlocks": Array [
            Object {
              "attributes": Object {
-                   "content": "a‸2",
+               "ordered": false,
+               "values": "",
              },
-                 "name": "core/list-item",
-               },
+             "innerBlocks": Array [
                Object {
                  "attributes": Object {
                    "content": "b",
                  },
+                 "innerBlocks": Array [],
                  "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:2041:42
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  - 4
+ Received  + 4

  <!-- 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>
+ <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
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": 6,
+   "offset": 6,
  }

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
Undo restores the post selection when another synced entity is loaded 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": 1,
+   "offset": 1,
  }

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
does not double up an event React already delivers in /test/e2e/specs/editor/various/editable-root-compat.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: 0

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/editable-root-compat.spec.js:291:5
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
should be immediately saved on indentation 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

@@ -1,8 +1,8 @@
  <!-- wp:list -->
  <ul class="wp-block-list"><!-- wp:list-item -->
- <li>one<!-- wp:list -->
+ <li>ne<!-- wp:list -->
  <ul class="wp-block-list"><!-- wp:list-item -->
  <li></li>
  <!-- /wp:list-item --></ul>
  <!-- /wp:list --></li>
  <!-- /wp:list-item --></ul>

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:694:52
should change the indented list type in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
TimeoutError: locator.click: Timeout 10000ms exceeded.
Call log:
  - waiting for locator('role=toolbar[name="Block tools"i]').locator('role=button[name="Indent"]')

    at Editor.clickBlockToolbarButton (/home/runner/work/gutenberg/gutenberg/packages/e2e-test-utils-playwright/src/editor/click-block-toolbar-button.ts:17:15)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:727:3
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": "6c4d26c2-0f66-4826-a3c1-d82cf66ade7d",
+     "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 indent with Space and outdent with Shift+Space at the start in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 7
+ Received  + 7

  <!-- wp:list -->
  <ul class="wp-block-list"><!-- wp:list-item -->
- <li>a<!-- wp:list -->
- <ul class="wp-block-list"><!-- wp:list-item -->
- <li>b</li>
+ <li></li>
+ <!-- /wp:list-item --></ul>
- <!-- /wp:list-item --></ul>
+ <!-- /wp:list -->
+
- <!-- /wp:list --></li>
- <!-- /wp:list-item --></ul>
+ <!-- wp:paragraph -->
+ <p> b</p>
- <!-- /wp:list -->
+ <!-- /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:875:52
should insert a line break on shift+enter in a non trailing list item in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 10
+ Received  + 10

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

- <!-- wp:list-item -->
+ <!-- wp:paragraph -->
- <li>c</li>
+ <p>c</p>
- <!-- /wp:list-item --></ul>
- <!-- /wp:list -->
+ <!-- /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:1233:52
should keep a selection ending after a non-breaking space 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": 3,
+   "anchorOffset": 1,
    "blocks": 2,
-   "focusOffset": 2,
+   "focusOffset": 1,
  }

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:2326:37
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 outdent with children in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
TimeoutError: locator.click: Timeout 10000ms exceeded.
Call log:
  - waiting for locator('role=toolbar[name="Block tools"i]').locator('role=button[name="Indent"]')

    at Editor.clickBlockToolbarButton (/home/runner/work/gutenberg/gutenberg/packages/e2e-test-utils-playwright/src/editor/click-block-toolbar-button.ts:17:15)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/list.spec.js:1055:3
should update the URL from the last navigation if only varies in the URL fragment in /test/e2e/specs/interactivity/router-navigate.spec.ts, passed after 1 failed attempt.
Error: expect(locator).toHaveText(expected) failed

Locator:  getByTestId('title')
Expected: "Link 1"
Received: "Main"
Timeout:  5000ms

Truncated. See the full report.

From 9bc9945, not the current head. Run

@draganescu draganescu changed the title add/grid item handles toolbar Grid: Show the canvas handles in a small toolbar, with a delete button Oct 16, 2026
draganescu and others added 2 commits October 16, 2026 19:10
With the grid interactivity experiment, the inserter offers Canvas: a
Group with a full width grid of 12 columns and 12 rows in manual
placement mode, so a free-form layout can be added in one step instead
of adding a Grid and changing its settings.

It sets no padding, so it gets whatever the theme gives Groups. Its
isActive keys are more specific than Grid's, so a manual grid shows as
Canvas and other grids still show as Grid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the [Package] Block library /packages/block-library label Oct 16, 2026
@draganescu draganescu changed the title Grid: Show the canvas handles in a small toolbar, with a delete button Grid: Show the canvas handles in a small toolbar, and add a Canvas block Oct 16, 2026
draganescu and others added 11 commits October 16, 2026 19:44
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/components/grid/grid-item-rotator.tsx
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/components/grid/grid-item-rotator.tsx
The toolbar was placed under the block's own bottom right corner and
turned upright around its center, so on a rotated block it moved around
the block and could cover it. It now stays upright under the bottom
right corner of the block's bounding box, in the same place at any
angle.

getBoundingBoxCornerStyle() works out that point inside the overlay
that turns with the block, in container units of the overlay, so it
follows the block's size without measuring it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the bottom right corner of a rotated block's bounding box, the
toolbar could end up far from the block, next to an empty corner of the
box. It now sits upright under the block's own lowest corner, or the
right one of two level corners, picked when a rotation is released.
While the block turns, it follows that corner.

The corner has a fixed place in the overlay that turns with the block,
so getCornerToolbarStyle() places the toolbar with percentages and the
gap turned into the overlay's frame, and the overlay no longer needs to
be a size container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@draganescu draganescu added [Package] Grid /packages/grid [Type] Enhancement A suggestion for improvement. labels Oct 19, 2026
@github-actions github-actions Bot removed the [Package] Grid /packages/grid label Oct 19, 2026
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/components/grid/grid-item-drag-handle.tsx
draganescu and others added 3 commits October 21, 2026 15:13
They came into the rotation helpers after those were converted to
TypeScript.

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 18 commits October 22, 2026 19:29
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/hooks/test/layout-child-tools.jsdom.test.tsx
Trunk now type checks block-library's tests, and the TypeScript test of
the variations can't import a JavaScript module. The Canvas variation
gets its own typed constant, since inside the conditional spread its
scope would widen to `string[]`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/components/grid/grid-item-rotator.tsx
#	packages/block-editor/src/components/grid/rotation.ts
#	packages/block-editor/src/components/grid/test/rotation.test.ts
…em-handles-toolbar

# Conflicts:
#	packages/block-editor/src/components/grid/grid-item-drag-handle.tsx
Read the grid interactivity flag in the block's init instead of at module
load, so the variation doesn't depend on the order of the scripts. Give
Canvas a tile in the Group placeholder and an example for the inserter
preview, and add a CHANGELOG entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The corner toolbar was plain divs with titles, hidden from assistive
technology. It is now a labelled Toolbar. Delete is a ToolbarButton, so it
can be reached from the keyboard; Drag and Rotate are Buttons left out of
the tab order, like the drag handle of the block toolbar, since they only
work with a pointer. Add a CHANGELOG entry.

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 [Package] Block library /packages/block-library [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant