Skip to content

Grid: Rotate manual grid items from the canvas - #84512

Draft
draganescu wants to merge 186 commits into
add/grid-mobile-stackingfrom
add/grid-rotate-handle
Draft

draganescu wants to merge 186 commits into
add/grid-mobile-stackingfrom
add/grid-rotate-handle

Conversation

@draganescu

@draganescu draganescu commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

What?

Behind the gutenberg-grid-interactivity and gutenberg-block-rotation experiments, a selected block in a manual placement grid has a rotate handle outside its bottom right corner on the canvas. Part of the stack split out of #84340, on top of #84509. It also merges #84505 (the rotate block support) and #84504 (blocks fill fixed-size cells in manual grids), so from this branch up, the stack has the complete feature set of #84340. #84894 builds on it.

The stack:

  1. Grid: Give manual placement grids same-size cells #84499 Same-size cells for manual placement grids
  2. Grid: Snap drags to cells in manual placement grids #84500 Drag blocks in manual grids with cell snapping
  3. Grid: Show alignment guides while dragging in manual grids #84501 Alignment guides while dragging
  4. Grid: Add and remove rows of manual placement grids #84502 Grid row handles, and resizing past the last row
  5. Grid: Draw blocks across cells in manual placement grids #84503 Draw to insert
  6. Grid: Stack manual grids on mobile, and edit them from the stack #84509 Stack manual grids on mobile
  7. Grid: Rotate manual grid items from the canvas #84512 Rotate manual grid items from the canvas (this PR), merging Add a rotation block support #84505 and Blocks: Fill fixed-size cells in manual placement grids #84504
  8. Grid: Show block toolbars over manual grids and add a drag handle #84894 Block toolbars over manual grids, and a drag handle on the canvas
  9. Grid: Show the canvas handles in a small toolbar, and add a Canvas block #84947 The canvas handles in a small toolbar, with a delete button

Why?

#84505 adds rotation as a general block support with a Rotation control in the block settings. In a manual grid, where blocks are placed and sized directly on the canvas, rotating them should work the same way, without going to the sidebar.

How?

  • GridItemRotator (components/grid/grid-item-rotator.tsx) is a handle outside the block's bottom right corner, clear of the resize handles in the middle of each side, in a popover over the block.
    • The handle is a GridItemHandle (components/grid/grid-item-handle.tsx), which looks like a block toolbar with a single button: the toolbar's border and corners, a 32px square and a 24px icon. Its colors and corners come from the design system tokens (--wpds-*). No stroke token is as dark as the toolbar's border, so the border stays $gray-900. The handle turns against the block's rotation, so it stays upright like the toolbar. Grid: Show block toolbars over manual grids and add a drag handle #84894's drag handle shares it.
    • Dragging the handle previews the angle with an inline rotate style on the block element, and shows the angle in the handle in place of its icon. The rotate support's rules have no !important, so the inline preview wins over them. Nothing is written to the block while dragging.
    • The block turns by as much as the pointer turns around the block's center, starting from the block's current angle (getPointerAngle() and the offset of getRotationFromPointer()). The handle is at a corner, so an angle taken straight from the pointer's direction would make the block jump as soon as the handle is pressed.
    • A press starts a rotation in startRotation(), from pointerdown or from a pointermove that presses the main button (button 0, buttons 1). In WebKit, the first press after a drag that started outside the canvas comes that way, as if the button were still held from the drag, and the block only turned on a second press.
    • Releasing the pointer writes the angle once, so a rotation is one undo step. Escape cancels it, and the inline style is cleared on release, cancel, or if the handle goes away mid-drag. Escape is handled in the capture phase in both the canvas and the editor document and claimed with preventDefault(), so writing flow doesn't also move focus away from the block.
    • The angle snaps to multiples of 15° within 5°, holding Shift rotates freely in whole degrees, and angles within 2° of 0 count as no rotation. The maths is in utils/rotation.ts, next to the support's own angle helpers (components/grid/rotation.ts was merged into it).
  • The handle shows only when the block is in a manual grid with the grid interactivity experiment on and isRotateEnabled( blockName ) is true, which means the block rotation experiment is on and the block supports rotation. Like the support's control, it is only offered for blocks in the default editing mode, and not in a pseudo state such as :hover.
  • The handle reads the rotation with the support's getRotateForState() and writes it with getUpdatedRotateStyle(), both in utils/rotation.ts (Add a rotation block support #84505 moved them out of the rotate hook, so grid data code such as getGridChildUpdates doesn't import the hook and its controls), so it stores style.rotate, or style['@tablet'].rotate / style['@mobile'].rotate in a viewport state, exactly as the Rotation control does. The write goes through useUpdateGridChildLayout as a style function, so rotating a block of a grid stacked on mobile first gives the grid its own mobile layout, in the same undo step.
  • Overlays follow the rotation. getRotatedOverlayStyle() and useRotatedOverlayStyle() move a block popover from the corner of the rotated block's bounding box to its center and rotate it by the same angle, so the resize handles stay on the block's edges, in any grid, and the rotate handle stays at the block's corner. While a block is rotated, its unrotated size is tracked with a ResizeObserver, so the overlays follow it when an image loads or the grid changes width.
  • Grid maths ignores rotation. getGridItemPixelRect() measures blocks by their offsets, which leave out the rotate property, so dragged blocks land by the cells they take up, and alignment guides compare unrotated grid areas.
  • Mobile stacking:
    • The stacking rule adds rotate: none to stacked children, in the editor (getMobileStackingStyle) and on the front end (layout.php). Its repeated container class (.c.c > *) is more specific than the support's single .wp-rotate-… class, inside and outside the media query.
    • When a stacked grid gets its own mobile layout (getUnstackedMobileUpdates), children that are rotated on mobile get style['@mobile'].rotate = 0 through getUpdatedRotateStyle(), so nothing turns when the stack goes away.
    • The Rotation control in the block settings works the same way in the Mobile state of a stacked grid: it shows 0, as the stack shows the block unrotated, and an edit goes through useUpdateGridChildLayout, so it turns stacking off for the grid first, with the same snackbar. Before, edits there had no visible effect.
  • While the handle is dragged, GridTools only keeps whether the previewed angle rotates the block, which hides the resizer. The angle itself stays in the handle, so GridTools doesn't re-render for every degree. GridVisualizer is also memoized: from Grid: Show block toolbars over manual grids and add a drag handle #84894 up, the drag handle and the handles toolbar turn with the preview, so the angle is back in GridTools there, and the memo keeps the visualizer's work per cell and grid item out of each degree.
  • Rotation is no longer tied to the grid, so switching a grid out of manual placement, or moving a block out of the grid, keeps its rotation.

Merge notes: #84505 merged cleanly. After #84504 moved filling into block styles, merging it and #84509 again replaced the image-specific stacking rules from this branch with --wp--style--grid-cells: auto, keeping rotate: none. #84504's useIsInFixedGridCell also returns false on mobile here when the grid stacks its blocks (isGridStackedOnMobile), so the size controls it hides come back in the stack. Merging #84504 conflicted in packages/block-editor/CHANGELOG.md (both entries kept) and phpunit/block-supports/layout-test.php (both sets of tests kept). Merging #84509's final version conflicted in the changelog and in the JSDoc of getUnstackedMobileUpdates, which now describes both skipping blocks hidden on mobile and unrotating rotated ones. The merge that brought in #84500's measuring once per drag (2840864) also moved this PR's rotation into it, because the two conflicted: getGridItemMeasurements() now reads each block's rotation when the drag starts, with its position, instead of the drop layer reading every block's rotation on every dragover. It has its own unit test. The grid item handles also use the visualizer's z-index again (7d5b2df), since the visualizer no longer remounts during drags.

Core backport: the stacking rule's rotate: none only reaches the front end in core if rotate is in core's safe_style_css, or safecss_filter_attr() drops it. #84505's core PR WordPress/wordpress-develop#14707 already adds rotate to that list, with a test that rotate: none is kept, so this PR's backport needs nothing more as long as #14707 lands first.

Differences from #84340:

Still to do:

  • Resizing a rotated block works along the screen's axes rather than the block's own: the handles sit on the rotated edges, but dragging them changes the block's span in screen directions. This was on Grid: Canvas exploration #84340's to-do list too.

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 few blocks, including an Image.
  3. Select a block. A handle that looks like a one-button toolbar appears outside its bottom right corner. Drag it around the block: the block turns, the handle stays upright, and it shows the angle. It snaps to 15° steps; hold Shift to rotate freely.
  4. Release. Undo once and check the block goes back to its previous angle in one step.
  5. Start another rotation and press Escape before releasing. The block goes back to its angle and nothing is stored.
  6. With a rotated block selected, check the resize handles sit on its edges, and resizing still snaps to cells.
  7. Drag a rotated block by its toolbar drag handle to another cell. It lands by the cells it takes up, not by its rotated bounding box.
  8. Open the Code editor and check the block has "style":{"rotate":30} (or the angle you chose), not a layout.rotate value. The block settings' Rotation control in the Dimensions panel shows the same angle.
  9. Save and view the front end: the block is rotated. At 480px wide or less, the grid stacks, the block is unrotated, and images have their natural height.
  10. In the editor, switch the preview to Mobile and rotate a stacked block. The grid gets its own mobile layout, the other blocks stay unrotated, and one undo brings the stack back.
  11. Switch the grid back to Auto placement. Rotated blocks keep their rotation, and there is no rotate handle.
  12. Disable "Block rotation". The handle is gone, but rotated blocks still show rotated. Disable "Grid interactivity" too, and check the grid's CSS matches trunk.

Testing Instructions for Keyboard

The rotate handle only works with a pointer and is hidden from assistive technology. Keyboard users rotate blocks with the Rotation control from #84505: open the Dimensions panel's options menu, turn on Rotation, and type an angle. In the Mobile preview of a grid stacked on mobile, the control shows 0, and typing an angle turns stacking off for the grid, as the handle does.

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 13 commits October 10, 2026 11:35
Every block that doesn't opt out with `supports.rotate: false` can now be
rotated, behind the new "Block rotation" experiment
(`gutenberg-block-rotation`, which sets
`window.__experimentalEnableBlockRotation`). The Dimensions panel gets an
optional Rotation item with an angle picker.

The rotation is a number of degrees in (-180, 180] stored in
`style.rotate`. It follows the selected viewport style state: overrides
live in `style['@tablet'].rotate` and `style['@mobile'].rotate`, where 0
is stored and undoes the default rotation on that viewport. In the
default state, 0 is not stored. "Reset all" only clears the selected
state's rotation.

Blocks are rotated with the CSS `rotate` property rather than
`transform`, which the editor writes to animate block moves. In the
editor, the rules are added under a generated class, with viewport
overrides in the responsive media queries. On the front end,
`lib/block-supports/rotate.php` adds the same rules to the block supports
stylesheet under a class generated from them, treating the stored value as
untrusted. Saved markup doesn't change, and stored rotation is shown
whether or not the experiment is on.

`getRotateForState()` and `getUpdatedRotateStyle()` read and write the
rotation of a style state, for tools such as a canvas rotate handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Classic, Custom HTML, Missing, More, Page Break, Pattern, Shortcode and
synced patterns opt out with `supports.rotate: false`, like they opt out
of custom CSS and visibility: they have no wrapper of their own to
rotate, or lose it on save or on the front end.

A block whose own `style` attribute is not an object doesn't support
rotation, and style values that are not objects are read as no
rotation.

JS reads stored values like PHP: only decimal numeric strings, and
halves rounded away from zero after removing floating point noise, so
-12.345 is -12.35 on both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/CHANGELOG.md
#	phpunit/block-supports/layout-test.php
Behind the grid interactivity and block rotation experiments, a selected
block in a manual placement grid gets a rotate handle below it. The
handle reads and writes the rotate support's style.rotate for the
selected style state.

- Dragging the handle previews the angle with an inline rotate style on
  the block, which wins over the rotate support's class rules, and shows
  the angle. Releasing writes it once, as one undo step; Escape cancels.
  Rotation snaps to multiples of 15 degrees unless Shift is held.
- The rotation goes through useUpdateGridChildLayout, so rotating a
  block of a grid stacked on mobile first gives the grid its own mobile
  layout. Blocks rotated on mobile get a mobile rotation of 0 then,
  since stacked blocks are shown unrotated.
- The resize handles follow the block's rotation, in any grid. Dragged
  blocks land by the cells they take up, measured without rotation.
- The mobile stacking rule unrotates stacked blocks, with more
  specificity than the rotate support's rules, and lets images keep
  their own size instead of covering cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PHPCompatibility flags the '0x10' test value, which checks that
hexadecimal strings are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rotated overlays track the block's size with a ResizeObserver, only
  while it is rotated, instead of reading it during render. The rotate
  and resize handles no longer drift after an image loads or the grid
  changes width.
- Escape is handled in the capture phase in both the canvas and the
  editor document, and claimed, so writing flow doesn't also move focus
  away from the block.
- Like the Rotation control, the handle is only offered for blocks in
  the default editing mode.
- Component tests for the handle (one write on release, Escape cancels,
  unmounting mid-drag clears the preview) and for when it is offered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/CHANGELOG.md
# Conflicts:
#	packages/block-editor/CHANGELOG.md
#	packages/block-editor/src/components/grid/mobile-stacking.js
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added [Package] Block library /packages/block-library [Package] Block editor /packages/block-editor labels Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

📦 Bundle size

Size Change: +4.05 kB (+0.05%)

Total Size: 8.65 MB

📦 View Changed
Filename Size Change
build/modules/block-library/image/view.min.js 3.49 kB +55 B (+1.6%)
build/modules/lazy-editor/index.min.js 14.6 kB +11 B (+0.08%)
build/scripts/block-editor/index.min.js 557 kB +2.88 kB (+0.52%)
build/scripts/block-library/index.min.js 426 kB +35 B (+0.01%)
build/scripts/blocks/index.min.js 44.9 kB +18 B (+0.04%)
build/scripts/core-data/index.min.js 76.7 kB +15 B (+0.02%)
build/scripts/edit-site/index.min.js 379 kB +33 B (+0.01%)
build/scripts/editor/index.min.js 568 kB +96 B (+0.02%)
build/scripts/style-engine/index.min.js 2.35 kB +97 B (+4.3%)
build/styles/block-editor/style-rtl.css 21.1 kB +169 B (+0.81%)
build/styles/block-editor/style-rtl.min.css 18.1 kB +175 B (+0.97%)
build/styles/block-editor/style.css 21.1 kB +168 B (+0.8%)
build/styles/block-editor/style.min.css 18.1 kB +177 B (+0.99%)
build/styles/block-library/image/style-rtl.css 3.12 kB +14 B (+0.45%)
build/styles/block-library/image/style-rtl.min.css 2.05 kB +14 B (+0.69%)
build/styles/block-library/image/style.css 3.13 kB +13 B (+0.42%)
build/styles/block-library/image/style.min.css 2.04 kB +14 B (+0.69%)
build/styles/block-library/style-rtl.css 23.2 kB +17 B (+0.07%)
build/styles/block-library/style-rtl.min.css 19.6 kB +14 B (+0.07%)
build/styles/block-library/style.css 23.3 kB +16 B (+0.07%)
build/styles/block-library/style.min.css 19.6 kB +14 B (+0.07%)

5626a82 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 2dc1460 trunk % Change
timeToFirstByte 49.4 ms +3.74% -3.34% 49.15 ms +4.58% -1.83% 0.51%
largestContentfulPaint 80 ms +10% -5% 76 ms +5.26% -0% 5.26%
lcpMinusTtfb 30.45 ms +14.94% -7.88% 27.2 ms +8.64% -5.33% 11.95%
wpBeforeTemplate 24.46 ms +4.91% -2.62% 24.51 ms +9.22% -2% -0.2%
wpTemplate 20.63 ms +4.56% -4.6% 20.51 ms +2.24% -3.22% 0.59%
wpTotal 45.52 ms +4.59% -3.84% 45.13 ms +4.54% -1.2% 0.86%
wpMemoryUsage 7.72 MB +0% -0% 7.68 MB +0% -0% 0.49%
wpDbQueries 16 +0% -0% 16 +0% -0% 0%

front-end-classic-theme

Metric 2dc1460 trunk % Change
timeToFirstByte 41.05 ms +4.26% -2.68% 40.85 ms +1.84% -2.94% 0.49%
largestContentfulPaint 76 ms +0% -2.63% 76 ms +2.63% -5.26% 0%
lcpMinusTtfb 33.65 ms +5.65% -1.93% 34.05 ms +3.96% -3.52% -1.17%
wpBeforeTemplate 21.43 ms +4.29% -0.65% 21.45 ms +1.4% -2.28% -0.09%
wpTemplate 15.83 ms +6.19% -3.73% 16.01 ms +3.56% -6.18% -1.12%
wpTotal 37.66 ms +4.75% -2.07% 37.75 ms +1.19% -3.23% -0.24%
wpMemoryUsage 6.41 MB +0% -0% 6.37 MB +0% -0% 0.61%
wpDbQueries 13 +0% -0% 13 +0% -0% 0%

media-processing

Metric 2dc1460 trunk % Change
mediaProcessingJpeg 399.91 ms +0.48% -0.24% 404.07 ms +0.38% -2.35% -1.03%
mediaProcessingAvif 5808.8 ms +0.34% -0.3% 5808.25 ms +0.02% -0.31% 0.01%
mediaProcessingJpegToAvif 3960.18 ms +0.09% -0.14% 3982.76 ms +0.12% -0.07% -0.57%

media-upload

Metric 2dc1460 trunk % Change
jpegUploadProcessing 1497.67 ms +34.19% -1.71% 1501.91 ms +0.36% -2.18% -0.28%
pngUploadProcessing 256.02 ms +2.94% -1.57% 250.05 ms +2.32% -1.92% 2.39%
largeJpegUploadProcessing 1488.96 ms +0.53% -0.94% 1481.53 ms +1.32% -0.24% 0.5%
multipleImageUploadProcessing 1642.01 ms +7.56% -0.54% 1639.48 ms +1.73% -1.77% 0.15%

post-editor

Metric 2dc1460 trunk % Change
serverResponse 445.18 ms +1.65% -9.78% 437.55 ms +1.07% -6.73% 1.74%
firstPaint 412.91 ms +5.02% -3.97% 401.9 ms +12.22% -4.89% 2.74%
domContentLoaded 803.06 ms +1.62% -0.39% 786.03 ms +3.66% -0.66% 2.17%
loaded 804.1 ms +1.6% -0.37% 786.98 ms +3.65% -0.67% 2.18%
firstContentfulPaint 458.97 ms +1.7% -0.84% 448.69 ms +4.86% -2.11% 2.29%
firstBlock 2133.57 ms +1.46% -1.16% 2073.8 ms +2.1% -2.17% 2.88%
type 17 ms +7.71% -9.82% 17.38 ms +1.32% -4.26% -2.19%
typeWithoutInspector 18.15 ms +2.31% -7.38% 16.33 ms +4.16% -6.49% 11.15%
typeWithTopToolbar 22.44 ms +4.1% -3.79% 21.65 ms +2.36% -3.51% 3.65%
typeContainer 8.49 ms +3.18% -9.19% 8.57 ms +22.64% -14.7% -0.93%
focus 120.37 ms +4.77% -11.11% 110.81 ms +10.41% -3.58% 8.63%
firstFocus 247.19 ms +0% -0% 237.57 ms +0% -0% 4.05%
selectAll 400.57 ms +0.8% -5.6% 389.84 ms +2.98% -2.5% 2.75%
undo 29.62 ms +5.87% -8.24% 29.07 ms +3.85% -8.02% 1.89%
listViewOpen 90.29 ms +17.45% -16.78% 75.97 ms +3.87% -7.12% 18.85%
inserterOpen 44.67 ms +5.44% -8.1% 44.79 ms +5.89% -8.13% -0.27%
inserterHover 11.63 ms +8.86% -6.02% 11.21 ms +13.56% -5.35% 3.75%
inserterSearch 7.7 ms +4.16% -3.38% 8.07 ms +5.95% -5.95% -4.58%
loadPatterns 619.42 ms +9.31% -3.47% 647.02 ms +2.25% -4.62% -4.27%
wpTotal 434.18 ms +1.72% -9.98% 426.76 ms +1.05% -6.91% 1.74%
wpMemoryUsage 14.78 MB +0% -0% 14.74 MB +0% -0% 0.27%
wpDbQueries 55 +0% -0% 55 +0% -0% 0%

site-editor

Metric 2dc1460 trunk % Change
serverResponse 421.1 ms +2.83% -6.81% 404.35 ms +5.63% -4.63% 4.14%
firstPaint 459.33 ms +1.7% -11.67% 421.71 ms +6.38% -6.58% 8.92%
domContentLoaded 815.46 ms +1.04% -0.3% 807.6 ms +2.86% -0.92% 0.97%
loaded 816.58 ms +1.04% -0.28% 808.53 ms +2.88% -0.89% 1%
firstContentfulPaint 459.9 ms +1.57% -1.23% 455.17 ms +1.4% -1.44% 1.04%
firstBlock 2470.61 ms +3.12% -2.14% 2446.23 ms +0.54% -1% 1%
type 16.8 ms +15.18% -5.71% 17.67 ms +1.81% -9.96% -4.92%
navigate 110.79 ms +1.71% -3.19% 111.06 ms +5.12% -8.02% -0.24%
loadPatterns 1293.4 ms +13.08% -2.71% 1245.29 ms +8.89% -8.58% 3.86%
loadPages 912.06 ms +4.23% -1.82% 917.79 ms +3.08% -1.81% -0.62%
wpTotal 409.25 ms +3.1% -6.71% 393.72 ms +5.81% -4.73% 3.94%
wpMemoryUsage 12.79 MB +0% -0% 12.75 MB +0% -0% 0.31%
wpDbQueries 45 +0% -0% 45 +0% -0% 0%

5626a82 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.

Navigates the items list via UP/DOWN arrow keys in /test/e2e/specs/site-editor/dataviews-list-layout-keyboard.spec.js, passed after 1 failed attempt.
Error: expect(locator).toBeFocused() failed

Locator:  getByLabel('Page Two')
Expected: focused
Received: inactive
Timeout:  5000ms

Call log:
  - Expect "toBeFocused" getByLabel('Page Two') with timeout 5000ms
  - waiting for getByLabel('Page Two')
    14 × locator resolved to <button type="button" tabindex="-1" aria-pressed="false" id="view-list-0-175-item-wrapper" class="dataviews-view-list__item" aria-labelledby="view-list-0-175-label" aria-describedby="view-list-0-175-description"></button>
       - unexpected value "inactive"

    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/site-editor/dataviews-list-layout-keyboard.spec.js:146:49
Page List converts to Navigation Links with entity bindings in /test/e2e/specs/editor/blocks/navigation.spec.js, passed after 1 failed attempt.
Error: expect(locator).toContainText(expected) failed

Locator: getByRole('region', { name: 'Editor settings' }).getByRole('tabpanel', { name: 'Content' }).getByRole('button', { name: /Link to:/ })
Expected substring: "test-page-1"
Received string:    "Link to:Cat/catPagePublished"
Timeout: 5000ms

Call log:
  - Expect "toContainText" getByRole('region', { name: 'Editor settings' }).getByRole('tabpanel', { name: 'Content' }).getByRole('button', { name: /Link to:/ }) with timeout 5000ms
  - waiting for getByRole('region', { name: 'Editor settings' }).getByRole('tabpanel', { name: 'Content' }).getByRole('button', { name: /Link to:/ })
    14 × locator resolved to <button type="button" aria-expanded="false" aria-haspopup="dialog" class="components-button link-preview-button is-next-40px-default-size is-secondary">…</button>
       - unexpected value "Link to:Cat/catPagePublished"

    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/navigation.spec.js:1949:31
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": 4,
+   "offset": 4,
  }

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

5626a82 Run

@draganescu draganescu mentioned this pull request Oct 10, 2026
1 of 3 tasks
@draganescu
draganescu added this pull request to stack #84511 October 10, 2026 17:39
draganescu and others added 2 commits October 10, 2026 12:40
PHP 7 rejects numeric strings with trailing whitespace, while PHP 8
accepts them, so the PHP 7.4 test run failed on ' 15 '.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@draganescu draganescu mentioned this pull request Oct 10, 2026
3 tasks
draganescu and others added 9 commits October 10, 2026 13:51
…o add/grid-rotate-handle

# Conflicts:
#	packages/block-editor/CHANGELOG.md
#	phpunit/block-supports/layout-test.php
# Conflicts:
#	lib/block-supports/layout.php
#	packages/block-editor/CHANGELOG.md
#	packages/block-editor/src/hooks/test/layout.jsdom.test.js
#	packages/block-editor/src/layouts/grid.jsx
#	packages/block-editor/src/layouts/test/grid.jsdom.test.jsx
#	phpunit/block-supports/layout-test.php
useIsInFixedGridCell also returns false on mobile when the grid stacks
its blocks, since stacked blocks are sized by their content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/block-editor/CHANGELOG.md
#	packages/block-library/CHANGELOG.md
# Conflicts:
#	packages/block-editor/CHANGELOG.md
#	packages/block-library/CHANGELOG.md
# Conflicts:
#	packages/block-library/CHANGELOG.md
# Conflicts:
#	packages/block-library/CHANGELOG.md
# Conflicts:
#	packages/block-editor/src/components/grid/get-grid-drop-target.js
#	packages/block-editor/src/components/grid/grid-visualizer.jsx
draganescu and others added 6 commits November 3, 2026 13:02
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Elements already forbid transform, but their :hover and other states
referenced the complete style properties, which now include it. The PHP
side strips it, so the schema now rejects it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o add/grid-rotate-handle

# Conflicts:
#	docs/reference-guides/core-blocks/README.md
#	lib/load.php
The rotate handle now reads and writes the transform block support's
`style.transform.rotate`, a CSS angle, instead of the rotation block support
from #84505, which was closed in favour of #86240. The old support, its
Rotation control in the Position panel and its PHP are removed; the Transform
panel in the Styles tab is the control now.

The stacking rule of grids stacked on mobile unrotates blocks with
`rotate: 0deg !important`: the transform support rotates blocks inline and its
viewport rules are important, and the KSES filter only allows a single angle.
The handle puts the block's own inline `rotate` back after a preview, since
the support sets it inline too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added [Package] Blocks /packages/blocks [Package] Style Engine /packages/style-engine labels Nov 3, 2026
aaronrobertshaw and others added 22 commits November 9, 2026 19:19
Only the blocks from the original rotate PR opt in: Group, Heading, Image and Video. The :hover rotation tests now expect Button's root selector, and the e2e spec drops the Button hover test.
theme.json and Global Styles rotations rendered on the front end but not
in the post or site editor canvas, because STYLE_KEYS left out transform.
Also lists transform in the Global Styles change summary.
A fractional angle, such as 1rad shown as 57.3, failed the angle picker's
step and was rewritten as soon as the field lost focus.
PHP's $ matched before a final newline, so "45deg\n" passed in PHP but
not in JS.
Elements already forbid transform, but their :hover and other states
referenced the complete style properties, which now include it. The PHP
side strips it, so the schema now rejects it too.
The select and text input states still referenced the complete style properties, which include transform. Guard them the same way as the link and button states.
Trunk now requires new test files to be TypeScript.
The rotation now applies to the block wrapper instead of the image or video, so the caption turns with it. The lightbox resets the rotation on the enlarged figure, which copies the block's classes and inline styles.
A rotation turns the figure around its centre, which its bounding box keeps. The zoom now places the unrotated image from that centre and the image's layout offset, instead of from the larger rotated bounding box.
# Conflicts:
#	lib/class-wp-theme-json-gutenberg.php
#	lib/compat/wordpress-7.3/kses.php
#	packages/global-styles-ui/CHANGELOG.md
#	packages/style-engine/CHANGELOG.md
…o add/grid-rotate-handle

# Conflicts:
#	lib/class-wp-theme-json-gutenberg.php
#	lib/compat/wordpress-7.3/kses.php
#	packages/block-editor/CHANGELOG.md
#	packages/blocks/CHANGELOG.md
#	packages/global-styles-ui/CHANGELOG.md
#	packages/style-engine/CHANGELOG.md
#	phpunit/block-supports/states-test.php
#	phpunit/class-wp-theme-json-test.php
#	test/integration/theme-schema.test.js
The transform block support branch was rewritten, so merging it kept a few lines of the copy this branch already had: the Image and Video transform selectors, the old lightbox reset, a duplicate changelog entry, and the JavaScript e2e test that is now TypeScript.

Co-Authored-By: Claude Fable 5.2 <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

No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Package] Block editor /packages/block-editor [Package] Block library /packages/block-library [Package] Blocks /packages/blocks [Package] Style Engine /packages/style-engine [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants