Repository navigation
Grid interactivity stack: review findings #85295
Description
Review findings for the manual grid interactivity stack (#85397): #84499 → #84504 → #84500 → #84501 → #84502 → #84503 → #84509 → #84512 → #84894 → #84947 → #85210, plus the side PR #84505 that #84512 merges.
Each PR was reviewed against the PR it sits on, not against trunk. #84512 was reviewed against a merge of its base with #84504 and #84505, so the side PRs' code isn't counted twice. Categories: performance and multiple grids, public API, couplings between framework areas, could-be-its-own-package, alternative routes, conventions, contracts with 3rd parties, and core backports. Categories with nothing to report are listed under "No findings" for each PR. Severity is shown as [high], [medium] or [low]. Nits were left out.
Top priorities
- [high] Grid: Snap drags to cells in manual placement grids #84500: a multi-selection drag inside a manual grid moves only the pressed block when dropped in the grid, but the whole selection when dropped outside it (no
isMultiSelectionDragcheck inuse-selected-block-event-handlers.ts). Still present in Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210. Done: multi-selection drags skip the manual grid path and move the whole selection through the block flow, in 2504a6c. - [high] Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210: the Columns
NumberControlruns the placement remap on every keystroke. Typing "12" remaps 12 → 1 → 12 and the grid ends up about 3× taller, with one undo step per keystroke. Done: in manual grids a typed count applies on Enter or blur (isPressEnterToChange); arrow keys and spin buttons still step it, in 2c87758. - [high] Grid: Rotate manual grid items from the canvas #84512: resize handles on rotated blocks are drawn on the rotated edges, but
ResizableBoxsizes from screen-axis pointer movement. At 180°, dragging a handle outward shrinks the block. Done for now: the resize handles are hidden while the block is rotated or a rotation is previewed, in 1715b68. Converting pointer movement into the block's axes is still to do. - [high] Block Supports: Add an experimental rotate block support #84505: the rotate
render_blockfilter and the editoruseBlockPropsrun with the experiment off, so existing third-party blocks that storestyle.rotateget rotated by core. Done: gated in 24161b5. - [high] Block Supports: Add an experimental rotate block support #84505 and Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210: backport labels are wrong or missing (see each PR).
- Settle the permanent data shapes before anything leaves the experiment:
style.rotate,stackOnMobile,style.layout.box, and the front-end CSS custom property names.
Across the stack
- [medium] Three CSS custom property namings for one feature:
--wp--style--grid-cells(Grid: Give manual placement grids same-size cells #84499),--wp--grid-item--row-span(Grid: Stack manual grids on mobile, and edit them from the stack #84509) and--wp--grid-item--box-*(Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210). The--wp--grid-item--*family doesn't exist on trunk. Pick one naming scheme. - [medium]
gutenberg_is_experiment_enabled()is called insidelib/block-supports/layout.php. No file inlib/block-supports/does this on trunk. These files are copied to core, which doesn't have the function. Pass the flag in fromlib/experimental/instead. Done:layout.phpreads agutenberg_layout_has_grid_interactivityfilter, whichlib/experimental/grid-interactivity.phpturns on with the experiment, in ad07869 (Grid: Give manual placement grids same-size cells #84499), 34b418b (Grid: Stack manual grids on mobile, and edit them from the stack #84509) and 58d798d (Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210). - [medium] On every
dragover(about every 50ms), the drop layer reads every child's position and callsgetComputedStyleon each, before checking whether anything changed. With 100 children that's hundreds of layout reads per event. Siblings don't move during a drag, so measure them once when it starts (Grid: Snap drags to cells in manual placement grids #84500, worse from Grid: Rotate manual grid items from the canvas #84512 and Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210). Done: the drop layer measures the grid once when a drag starts and reuses that until it ends, in 5d81c33 (Grid: Snap drags to cells in manual placement grids #84500), with rotation added in the Grid: Rotate manual grid items from the canvas #84512 merge 2840864 and free placement in 58d798d (Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210). On Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210,getComputedStylecalls perdragoverwent from 41 to 0. - [medium] The generic block drag handler (
use-selected-block-event-handlers.ts) checkslayout.type === 'grid' && isManualPlacement(Grid: Snap drags to cells in manual placement grids #84500, grown in Grid: Show block toolbars over manual grids and add a drag handle #84894), while Grid: Show block toolbars over manual grids and add a drag handle #84894 adds a layout-type hook for the toolbar case. Route the drag behaviour through the layout type too. Done: layout types have an optionalstartChildDrag(), and the grid's starts the manual grid drag, in bedba56 (Grid: Snap drags to cells in manual placement grids #84500). The generic handler only asks the layout type, also for Grid: Show block toolbars over manual grids and add a drag handle #84894's drag handle. - [low] Import cycle:
components/gridimportsgetUpdatedChildLayoutStylefromhooks/layout-child, which importscomponents/grid(Grid: Snap drags to cells in manual placement grids #84500, direct from Grid: Add and remove rows of manual placement grids #84502). Move the pure helper intocomponents/grid/utilsorhooks/block-style-state. Done:getUpdatedChildLayoutStylemoved tohooks/block-style-statein 8947909 (Grid: Snap drags to cells in manual placement grids #84500), andgetGridItemBoxwithGRID_PLACEMENT_KEYStocomponents/grid/free-placementin 58d798d (Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210). - [medium] No Playwright (e2e) coverage for the drag flows, the Chrome and WebKit workarounds, or the public
useDropZonechange. Done:test/e2e/specs/editor/various/grid-drag-and-drop.spec.jsruns in Chromium and WebKit, in a768b1c (Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210). It covers moving a block by its body, the block toolbar's drag handle and the grid item drag handle; six drags in a row that leave the block tools out of their dragging state (theuseDropZonerepro from Compose: Start useDropZone drags on the document's first dragover #85412); dragging a multi-selection; inserter drags, including a block the grid doesn't allow and closing the inserter mid-drag; and files dragged over the grid. Firefox isn't covered, as in theuseDropZoneitem below.
#84499 — Grid: Give manual placement grids same-size cells
Public API
- [medium]
--wp--style--grid-cells: fixed|autois a new front-end property that blocks are told to read with container style queries, so third-party blocks will copy it. It also sits in--wp--style--*, which so far only holds theme.json values. Decide whether it's public; if it is, document it as experimental.
Alternative routes
- [medium]
aspect-ratiotogether withmin-height: 0and1frrows means content never makes a cell taller. Text taller than its cell spills over the next row and past the grid, which is worst on phones (stacking is opt-in). Keepmin-height: auto, or clip or scroll overflowing cells.
Conventions
- [medium] This is the first experiment check in
lib/block-supports/(see "Across the stack"). Done: see "Across the stack" (ad07869).
Contracts with 3rd parties
- [medium] With the experiment on, every saved manual grid changes layout, including core's navigation overlay pattern. Graduating needs a migration or an opt-in, not a default flip.
Core backports
- [low] Add a "No Core Sync Required" note for now. At graduation, backport the
layout.phpchanges without the experiment check. Done: labelled, with a note in the PR description.
No findings: performance and multiple grids, couplings, own package.
#84504 — Blocks: Fill fixed-size cells in manual placement grids
Public API
- [medium] The fill rules ship ungated in block-library's front-end
style.cssand reach core through the package sync, while the PHP that sets the variable stays plugin-only. In core,--wp--style--grid-cells: fixedbecomes a switch any theme can flip.
Couplings
- [medium] Five blocks each hardcode
.is-layout-grid >and the private variable, and they hide controls inconsistently. Media & Text and Video keep min height; Cover keeps aspect ratio, which its CSS then cancels. Hide the block-support dimension controls in one place, block-editor's dimensions hook. Done for the controls: the dimensions hook turns off the minimum height and aspect ratio block-support controls in fixed cells, and Cover still hides its own minimum height control, resize handle and full height toggle, which aren't block supports, in f7af3db. The five blocks' CSS still names the variable.
Alternative routes
- [medium] Hiding
DimensionsToolon Image and Post Featured Image also hides Scale, and the focal point control needs Scale. Cropped images can therefore never get a focal point. Keep Scale and focal point; hide only width, height and aspect ratio. Done: in fixed cells Image keeps Scale and Focal point (picking a focal point sets the scale to cover) and Post Featured Image keeps Scale;DimensionsToolshows Scale whenever it's the only tool, in f7af3db.
Conventions
- [low] This is the first
@container style()in the repo, and the rules rely on!importantto beat inline styles. Explain both in the PR.
Contracts with 3rd parties
- [medium] Check Firefox support for custom-property style queries. Without it, the grid becomes a fixed box but media doesn't fill it and overflows, while the editor still hides the size controls.
No findings: performance and multiple grids, own package, core backports.
#84505 — Block Supports: Add an experimental rotate block support
Public API
- [high]
supports.rotateis on by default and storesstyle.rotateand per-viewport copies in post content, which is permanent. - [medium]
style.rotateis a top-level key, unlikestyle.dimensions.*. Considerstyle.transform.rotatewhile it's experimental. - [medium] There's no theme.json setting to turn the control off, and no
styles.*.rotatefor Global Styles.
Couplings
- [low]
hooks/style.tsximportshasRotateSupport, so the shared style hook knows about one specific support.
Alternative routes
- [medium] Make it opt-in, like the other visual supports. Rotation needs a single real wrapper on the front end, which is why 8 core blocks had to opt out. The grid's need can be met from the child-layout support instead.
Conventions
- [low] The angle picker rounds to whole degrees but storage keeps two decimals, so a canvas angle of 12.5° is rewritten as 13°.
Contracts with 3rd parties
- [medium]
rotate.php:187and the editoruseBlockPropsrun with the experiment off (see "Top priorities"). Gate both on the experiment, or go opt-in. Done in 24161b5:lib/load.phpremoves the filter and the support'shasSupportisisRotateEnabled(). - [medium] Third-party dynamic blocks without a single wrapper get the rotate class on the first tag in their markup, so the editor and front end differ.
Core backports
- [high] It's labelled "No Core Sync Required" but adds
rotate.phpand asafe_style_cssentry that core must ship. Drop the label and add abackport-changelog/7.2entry. Done: label removed, Editor: Add the rotate block support wordpress-develop#14707 andbackport-changelog/7.2/14707.md. - [medium] Add a guarded
remove_filterfor a future corewp_render_rotate_support, ascustom-css.phpandblock-visibility.phpdo, to avoid duplicate rules.
No findings: performance and multiple grids, own package.
#84500 — Grid: Snap drags to cells in manual placement grids
Performance and multiple grids
- [medium] The per-
dragovermeasuring starts here (see "Across the stack"). Done: see "Across the stack" (5d81c33). - [medium] Switching the popover slot when a drag starts unmounts and remounts every manual grid's visualizer at drag start and end. That remount is the root cause of the later z-index 31 workarounds. Done: the visualizer stays in its slot, in f76e3b3.
- [low] Nested manual grids put both drop layers in the same slot at the same z-index, so which one gets the drop depends on mount order.
Couplings
- [medium] The public
@wordpress/composeuseDropZoneis changed inside a feature stack. If it fixes a real Chrome bug, ship it as its own PR with a repro. Done: split out as Compose: Start useDropZone drags on the document's first dragover #85412. It doesn't reproduce on trunk, so its repro uses Grid: Snap drags to cells in manual placement grids #84500 with the fix taken out, where from the second drag of a grid item on, the block tools stay in their dragging state. Grid: Snap drags to cells in manual placement grids #84500 keeps its copy until Compose: Start useDropZone drags on the document's first dragover #85412 lands. - [medium] The generic drag handler gains about 70 lines of grid logic (see "Across the stack"). Done: see "Across the stack" (bedba56).
Alternative routes
- [medium] Exempt the visualizer from the single
.block-editor-block-tools--is-dragging > .popover-slot { display: none }rule instead of switching slots. That removes the remount and the z-index entries. Done: the rule spares children markedis-shown-while-dragging, which manual grid visualizers are, in f76e3b3. The z-index entries of the item resizer (f76e3b3), the row resizer (7927f8e, Grid: Add and remove rows of manual placement grids #84502) and the grid item handles (7d5b2df, Grid: Rotate manual grid items from the canvas #84512) are gone. Two stay for other reasons: the drop layer gets 32 while a drop is possible, so the selected block's resize handles don't take the firstdragovers (3e007b9), and on Grid: Show block toolbars over manual grids and add a drag handle #84894 the handles keep 31 because the captured block toolbar would cover the drag handle (8e04bc5). - [medium] The
useDropZonefix only helps zones the pointer is already over.<DropZone>staysvisibility: hiddenuntil it starts, so it never gets thatdragover. Listen fordragoveron the document instead. Done: it starts on the document's firstdragover, in de13468.
Conventions
- [high] Multi-selection drag bug (see "Top priorities"). Add
! isMultiSelectionDrag. Done in 2504a6c. - [low]
-webkit-user-drag: noneapplies to every child, and to images at any depth inside the grid. Done: it only matches the item's own image (> img,> figure img), in c11149e.
Contracts with 3rd parties
- [medium]
useDropZone'sonDragStartcan now fire from adragover. Combined with the end-on-mousemovefallback, a drag could start and end on every tick in browsers that sendmousemoveduring a drag. Verify in Safari and Firefox, and update the hook's docs. Partly: amousemovewith a button held no longer ends the drag (de13468).drag-repeatpasses in Playwright WebKit too, which also skips thedragenter, and the hook's docs are updated in Compose: Start useDropZone drags on the document's first dragover #85412 (6969911, d183b00). Playwright's Firefox didn't launch, so Firefox and Safari still need a check by hand.
No findings: public API (other than the useDropZone change), own package, core backports.
#84501 — Grid: Show alignment guides while dragging in manual grids
Alternative routes
- [low] Hiding cell lines when idle is a UX change bundled into a guides PR. Split it out or confirm it with design.
No findings: performance and multiple grids (beyond the shared dragover cost), public API, couplings, own package (keep get-alignment-guides.ts private until something outside grid uses it), conventions, contracts with 3rd parties, core backports.
#84502 — Grid: Add and remove rows of manual placement grids
Couplings
- [low] Makes the import cycle direct (see "Across the stack"). Done: see "Across the stack" (8947909).
Alternative routes
- [medium] Consider discrete "Add row above / below" and "Remove empty row" buttons instead of a full-grid resize box. They're keyboard-accessible by default and reuse the same update helper.
Conventions
- [medium] Adding or removing rows at the top (which shifts every block) has no keyboard path, and the "N rows" label isn't announced to screen readers. Partly: the new row count is announced with
speak()when a row resize ends, in 89d4e2e. Still no keyboard path for rows at the top.
Contracts with 3rd parties
- [low]
updateLayoutnow callsupdateBlockAttributesdirectly instead of the block'ssetAttributes. It has no effect today; note it in the PR. Done: noted in Grid: Add and remove rows of manual placement grids #84502's How? section.
No findings: performance and multiple grids (updates are correctly batched into one action), public API, own package, core backports.
#84503 — Grid: Draw blocks across cells in manual placement grids
Note: #85277 would remove the marquee, the cell appenders and the touch two-tap, which makes some of these moot.
Performance and multiple grids
- [medium]
useGridLayoutSyncis mounted for every grid, experiment on or off, and reads the wholestyleof the selected block. Any style slider on any block re-renders every grid, and each one re-reads all its children. Only select when the selected block is this grid's child, and only its layout entries. Done: it selects only when the selected block is this grid's item, and then only that item's default and viewport layouts, in 2e017b8. The hook is still mounted for every grid. - [low] Every manual-grid child injects the same placeholder CSS rule, so 100 children means 100 copies. Done: one rule in the grid's layout style (
<grid> > * > .components-placeholder), in 87a1573.
Public API
- [low] Every inserter insertion adds
meta.isFromInserter, ungated. Sincemetais free-form, that becomes a de facto contract. Use a private key fromprivate-keys. Done in 073fd4d (isFromInserterKey).
Couplings
- [medium] Layout code detects placeholders through the
@wordpress/componentsclass.components-placeholder, and the 300px minimum is tuned to Image and Cover placeholders.
Alternative routes
- [medium]
draggedInserterBlocksstore state isn't needed:dataTransfer.types, the drop payload and the privateisDragging()already cover it. Removing it also removes the stale-state risk when the drag source unmounts. Done: removed. Grids read the inserter'swp-block:<name>drag types while dragging and the drop's data on drop, in 48d9bb4 (Grid: Draw blocks across cells in manual placement grids #84503) and ccd355f (Grid: Show block toolbars over manual grids and add a drag handle #84894). - [medium] Every new block gets about 300×300px, so a Paragraph added with Enter becomes 3×3 cells. Use the render-then-measure path everywhere.
Conventions
- [medium] On touch, the first tap is swallowed, so a VoiceOver user's first activation does nothing.
- [medium] Closing the marquee's inserter without inserting sends focus to
body.
Contracts with 3rd parties
- [medium] The
blocks.switchToBlockType.transformedBlockfilter is global and ungated, and changes saved content for transforms in every layout type. It's already split out as Block Editor: Keep a block's grid position and span when it is transformed #85045; drop it here or gate it. Done: dropped in 073fd4d. - [low] The new placement rules also run with the experiment off. Done in 073fd4d: without the experiment the sync places blocks as on trunk.
No findings: own package, core backports.
#84509 — Grid: Stack manual grids on mobile, and edit them from the stack
Performance and multiple grids
- [low] Three components each select all children's full block objects and recompute the stacked layout, so typing in any grid item re-renders all three. Share one memoized private selector. Done in 6997076: private selector
getStackedGridLayouts, memoized on each child's style and block visibility.
Public API
- [high]
layout.stackOnMobileandstyle['@mobile'].layout.stackOnMobileare two homes for one permanent flag, with precedence and reset rules between them. Pick one location and a viewport-neutral name. - [medium] The first mobile edit writes a
@mobileplacement into every child, which stays as dead data if stacking is turned back on. Done in 6997076: turning stacking back on removes the blocks' mobile placements in the same undo step (Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210 also removes a mobile free-placementbox). - [medium]
--wp--grid-item--row-spanopens a new namespace (see "Across the stack").
Couplings
- [medium] The generic layout hook hardcodes
viewport === '@mobile', andLayoutTypegains a method named after one viewport. Pass the viewport intogetLayoutStyleinstead. Done in 6997076:getLayoutStylereceivesviewport; the grid adds its stacking rules for@mobile;getMobileStackingStyleis gone. - [medium] With Mobile preview on and Responsive editing off, the canvas is stacked but the tools edit desktop placements.
Alternative routes
- [medium] The row-span property looks like it has no visible effect, because stacked rows are
auto. Verify in a browser; if confirmed, drop it, along with the per-child class and the PHP early-return change. Verified in Chromium and WebKit: it doesn't keep blocks tall (only pads short blocks by the row gaps), but the editor's tools read the rows the browser draws, and the stacked layout numbers rows by row span, so dropping it would misalign them. Kept; comments and the PR description now say what it does (6997076).
Conventions
- [low] The first mobile edit silently turns stacking off for the whole grid. Announce it with a notice or
speak(). Done in 6997076: snackbar "Stack on mobile turned off for this grid."
Contracts with 3rd parties
- [low] Child classes change for existing manual grids (experiment-only).
Core backports
- [low] At graduation: the stacking rules, the row-span property if kept, and the early-return change. The current label is correct.
No findings: own package.
#84512 — Grid: Rotate manual grid items from the canvas
Performance and multiple grids
- [medium] The preview angle is state in
GridTools, so every degree re-renders the unmemoizedGridVisualizer, which costs cells × children work per frame. Memoize the visualizer or keep the angle local. Done: both. On Grid: Rotate manual grid items from the canvas #84512GridToolsonly keeps whether the preview rotates the block and the angle stays in the handle (c68bb45), andGridVisualizeris memoized (4341b70). 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 inGridToolsthere and the memo does the work.
Public API
- [low] It emits a new front-end
rotate: noneon children of stacked grids, and unstacking writes@mobile.rotate: 0into content.
Couplings
- [medium]
mobile-stacking.ts(pure data) now imports the React rotate hook module. Move the pure helpers intoutils/rotation.ts. Done in a48973c, differently: unstacking unrotates blocks ingetGridChildUpdates, somobile-stacking.tsno longer imports the rotate hook module (the store's new selector reads it). Since 5617881 (Block Supports: Add an experimental rotate block support #84505),getRotateForStateandgetUpdatedRotateStylelive inutils/rotation.ts, sogetGridChildUpdatesdoesn't import the hook module either.cleanEmptyObjectmoved toutils/object.ts(d872067) so that module loads without the hook utilities. - [low] The layout support emits a property owned by the rotate support.
Own package
- [low] Not a package, but merge
grid/rotation.tsinto Block Supports: Add an experimental rotate block support #84505'sutils/rotation.ts. Done in c68bb45, with its tests; Grid: Show the canvas handles in a small toolbar, and add a Canvas block #84947's corner helpers moved there in its merge (7793198).
Alternative routes
- [high] Resize on rotated blocks (see "Top priorities"). Convert pointer movement into the block's own axes, or hide the handles while the block is rotated. Done for now: handles hidden while rotated, in 1715b68.
Conventions
- [medium] The keyboard path (the inspector Rotation control) doesn't unstack a stacked grid in the Mobile state, so edits there have no visible effect. Done: in the Mobile state of a stacked grid the control shows 0 and edits go through
useUpdateGridChildLayout, which turns stacking off first, like the handle, in c68bb45.
Contracts with 3rd parties
- [low]
rotate: noneon stacked children is gated only by the grid experiment, and overrides any theme or pluginrotateCSS.
Core backports
- [low] The stacking backport also needs
rotatein core'ssafe_style_css(from Block Supports: Add an experimental rotate block support #84505), or core silently dropsrotate: none. Done: Editor: Add the rotate block support wordpress-develop#14707 already adds it, with a test thatrotate: noneis kept; noted in Grid: Rotate manual grid items from the canvas #84512's description.
#84894 — Grid: Show block toolbars over manual grids and add a drag handle
Performance and multiple grids
- [low] On every pointer move of a touch drag, each manual grid runs its own
elementsFromPoint(), and the subscription re-subscribes on every render. Done: the touch drag looks up the drop layer under the touch once and passes it to every grid, and the visualizer subscribes once with a stableuseEventcallback, in 14b9aa5. - [low] The touch pub/sub is a module-level
Setshared by every editor on the page.
Public API
- [low] The CHANGELOG presents
capturesToolbars()as an extension point, but third parties can't register layout types. Done: the entry no longer mentionscapturesToolbars(); it says a block can opt out with__experimentalCaptureToolbars: false, in 14b9aa5.
Couplings
- [medium] It grows the grid branch in the generic drag handler (see "Across the stack"). Done: see "Across the stack" (bedba56).
Alternative routes
- [medium] Derive capture in
useInnerBlocksProps(|| ( isManualPlacement && experiment )) instead of adding aLayoutTypemethod.
Conventions
- [low] A handle drag is detected by
target !== blockElement, so a drag from an image inside the block counts too. Pass an explicitisHandle. Done:startGridItemDragtakes anisHandleoption, which only the grid item drag handle sets, in 14b9aa5.
Contracts with 3rd parties
- [medium] The layout overrides a block's explicit
__experimentalCaptureToolbars: false, and the result shows up ingetBlockListSettings. Done: a block's own setting now wins, and the layout only applies when the block doesn't set one.useInnerBlocksPropsused to sendfalsefor every block that hadn't opted in, so it now sendstrueor nothing, in 14b9aa5. - [medium] Because the topmost capturing ancestor wins, it reaches blocks at any depth. A Paragraph inside a Group inside a full-page grid gets its toolbar at the top of the grid.
- [low] Multi-selection drags in a manual grid now carry only the pressed block's ID. Done: multi-selection drags no longer go through
startGridItemDrag, so they carry every selected ID (2504a6c, merged into this PR).
No findings: own package, core backports.
#84947 — Grid: Show the canvas handles in a small toolbar, and add a Canvas block
Public API
- [medium]
group-canvasis a public variation: it appears in the inserter, transforms andgetActiveBlockVariation. Its name and title will be relied on, so get design sign-off before it graduates.
Couplings
- [low]
variations.jsreadswindow.__experimentalEnableGridInteractivityat module load, which depends on script order. Register the variation from the experiment's init. Done: the Group block'sinitcallsregisterCanvasVariation(), which reads the flag when the block is registered, in 846237a.
Alternative routes
- [medium] Grid: Show block toolbars over manual grids and add a drag handle #84894 moved the toolbar off the block to avoid covering neighbours; the corner toolbar covers neighbouring cells again. Consider a
BlockControlsgroup in the captured toolbar instead. - [low] The Delete button duplicates the options menu and sits next to the drag handle, where a touch drag can easily hit it.
Conventions
- [medium] The Group placeholder shows Canvas as an empty tile (no icon), and the variation has no
example. Done: the placeholder has a Canvas tile and the variation has an example, in 846237a (scaled to 32 by 32 on Grid: Select the grid on empty cells, draw drag tiles behind blocks, 32×32 Canvas #85277 in 4c27c8f). - [medium] "Canvas" collides with the "Editor canvas" region name for screen readers. Check the copy guide.
- [medium] The toolbar is plain
divs withtitleattributes, notToolbar/ToolbarButton, and Delete isn't keyboard-reachable. Justify keeping itaria-hidden, or use the components. Done: it's aToolbarlabelled "Block handles"; Delete is aToolbarButton, reachable with Tab, and Drag and Rotate areButtons left out of the tab order like the block toolbar's drag handle, in 9bc9945. On Grid: Show the canvas handles in a small toolbar, and add a Canvas block #84947 and Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210, Tab reaches Delete only after the grid's empty-cell appenders, which Grid: Select the grid on empty cells, draw drag tiles behind blocks, 32×32 Canvas #85277 removes. - [low] There's no CHANGELOG entry in block-editor or block-library. Done: added in both, in 846237a and 9bc9945.
Contracts with 3rd parties
- [low] With the experiment on, existing manual grids show as "Canvas" instead of "Grid".
No findings: performance and multiple grids, own package, core backports.
#85210 — Grid: Drag and resize blocks freely in manual grids, Shift to snap
Performance and multiple grids
- [medium] The drop target changes on almost every pixel, so the layer re-renders on almost every
dragover, and the next read forces a synchronous layout. Cache the measurements and move only the preview element, once per animation frame. Partly: the measurements are read once per drag (58d798d). The layer still re-renders when the target changes.
Public API
- [high]
style.layout.boxis a permanent content shape.topis a fraction of the cell area's width, only because CSS resolvesmargin-toppercentages against width.leftis physical, not logical start (RTL). Fix the shape before it ships. - [medium] It adds four
--wp--grid-item--box-*properties (see "Across the stack"). - [low] Validation in JS and PHP is loose: it accepts
leftup to 100 and doesn't checkleft + width <= 1.
Couplings
- [low] The column-count remap measures the canvas DOM, so the saved fractions depend on the canvas width at the time of the edit (desktop vs tablet preview).
Alternative routes
- [high] Consider a separate canvas or freeform layout type, or absolutely positioned grid items whose percentages resolve against the grid area. The current model only works with fixed cells, and needs the remap, the
!importantmargins and thetop-of-width quirk. - [medium] The remap turns every snapped block into a free box, so a tidy layout stays unsnapped after a column change.
Conventions
- [high] Free position and size are pointer-only: no keyboard nudge, no indication in the inspector, no snap action, and typing a span silently deletes the box (WCAG 2.5.7).
- [medium] Touch drags are always free, so touch users can't snap.
Contracts with 3rd parties
- [high] Per-keystroke column remap (see "Top priorities"). Apply it on commit (blur or Enter). Done in 2c87758.
- [medium] RTL: physical
margin-leftis combined with logicaljustify-self: start, so the offset is ignored in RTL. - [medium] The
!importantmargins override user margins, while blocks with their own width or height keep them, so the result matches neither the editor nor the stored box. - [medium] Reset, switching the grid type, global styles and third-party updates all change the column count without the remap.
Core backports
- [medium]
box-sizinginsafe_style_cssis a core change with no backport-changelog entry. It's also an independent fix, since trunk already emitsbox-sizingthat core drops; split it into its own PR. Done: split out as Layout: Keep box-sizing for fixed-width children of flex layouts #85373, with KSES: Allow box-sizing in safecss_filter_attr() wordpress-develop#14708. Grid: Drag and resize blocks freely in manual grids, Shift to snap #85210 keeps its copy until Layout: Keep box-sizing for fixed-width children of flex layouts #85373 lands. - [low] Add "No Core Sync Required" for the experiment-gated
layout.phpchanges.
No findings: own package.
Activity