diff --git a/packages/block-editor/src/components/block-draggable/content.scss b/packages/block-editor/src/components/block-draggable/content.scss
index 102230168e213..25a0f5c256595 100644
--- a/packages/block-editor/src/components/block-draggable/content.scss
+++ b/packages/block-editor/src/components/block-draggable/content.scss
@@ -1,13 +1,12 @@
// This creates a "slot" where the block you're dragging appeared.
// We use !important as one of the rules are meant to be overridden.
.block-editor-block-list__layout .is-dragging {
- background-color: currentColor !important;
- opacity: 0.05 !important;
+ opacity: 0.1 !important;
border-radius: $radius-small !important;
- // Disabling pointer events during the drag event is necessary,
- // lest the block might affect your drag operation.
- pointer-events: none !important;
+ iframe {
+ pointer-events: none;
+ }
// Hide the multi selection indicator when dragging.
&::selection {
@@ -18,3 +17,10 @@
content: none !important;
}
}
+
+// Images are draggable by default, so disable drag for them if not explicitly
+// set. This is done so that the block can capture the drag event instead.
+.wp-block img:not([draggable]),
+.wp-block svg:not([draggable]) {
+ pointer-events: none;
+}
diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js
index 6d4655189d972..0e3a5be5150de 100644
--- a/packages/block-editor/src/components/block-list/block.js
+++ b/packages/block-editor/src/components/block-list/block.js
@@ -797,6 +797,7 @@ function BlockListBlockProvider( props ) {
mayDisplayParentControls,
originalBlockClientId,
themeSupportsLayout,
+ canMove,
};
// Here we separate between the props passed to BlockListBlock and any other
diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss
index 3d3b8517ca09c..cd517fced833e 100644
--- a/packages/block-editor/src/components/block-list/content.scss
+++ b/packages/block-editor/src/components/block-list/content.scss
@@ -427,3 +427,9 @@ _::-webkit-full-page-media, _:future, :root [data-has-multi-selection="true"] .b
// Additional -1px is required to avoid sub pixel rounding errors allowing background to show.
margin: 0 calc(-1 * var(--wp--style--root--padding-right) - 1px) 0 calc(-1 * var(--wp--style--root--padding-left) - 1px) !important;
}
+
+// This only works in Firefox, Chrome and Safari don't accept a custom cursor
+// during drag.
+.is-dragging {
+ cursor: grabbing;
+}
diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js
index 25b9a21f0d286..4696149dc3875 100644
--- a/packages/block-editor/src/components/block-list/use-block-props/index.js
+++ b/packages/block-editor/src/components/block-list/use-block-props/index.js
@@ -30,6 +30,7 @@ import { useIntersectionObserver } from './use-intersection-observer';
import { useScrollIntoView } from './use-scroll-into-view';
import { useFlashEditableBlocks } from '../../use-flash-editable-blocks';
import { canBindBlock } from '../../../hooks/use-bindings-attributes';
+import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility';
/**
* This hook is used to lightly mark an element as a block element. The element
@@ -100,11 +101,15 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
isTemporarilyEditingAsBlocks,
defaultClassName,
isSectionBlock,
+ canMove,
} = useContext( PrivateBlockContext );
+ const canDrag = canMove && ! hasChildSelected;
+
// translators: %s: Type of block (i.e. Text, Image etc)
const blockLabel = sprintf( __( 'Block: %s' ), blockTitle );
const htmlSuffix = mode === 'html' && ! __unstableIsHtml ? '-visual' : '';
+ const ffDragRef = useFirefoxDraggableCompatibility();
const mergedRefs = useMergeRefs( [
props.ref,
useFocusFirstElement( { clientId, initialPosition } ),
@@ -120,6 +125,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
isEnabled: isSectionBlock,
} ),
useScrollIntoView( { isSelected } ),
+ canDrag ? ffDragRef : undefined,
] );
const blockEditContext = useBlockEditContext();
@@ -152,6 +158,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
return {
tabIndex: blockEditingMode === 'disabled' ? -1 : 0,
+ draggable: canDrag ? true : undefined,
...wrapperProps,
...props,
ref: mergedRefs,
diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js
new file mode 100644
index 0000000000000..5fa07fb9be604
--- /dev/null
+++ b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js
@@ -0,0 +1,25 @@
+/**
+ * WordPress dependencies
+ */
+import { useRefEffect } from '@wordpress/compose';
+
+/**
+ * In Firefox, the `draggable` and `contenteditable` attributes don't play well
+ * together. When `contenteditable` is within a `draggable` element, selection
+ * doesn't get set in the right place. The only solution is to temporarily
+ * remove the `draggable` attribute clicking inside `contenteditable` elements.
+ *
+ * @return {Function} Cleanup function.
+ */
+export function useFirefoxDraggableCompatibility() {
+ return useRefEffect( ( node ) => {
+ function onDown( event ) {
+ node.draggable = ! event.target.isContentEditable;
+ }
+ const { ownerDocument } = node;
+ ownerDocument.addEventListener( 'pointerdown', onDown );
+ return () => {
+ ownerDocument.removeEventListener( 'pointerdown', onDown );
+ };
+ }, [] );
+}
diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js
index 68f8a671adbe9..0a13ce6700b8e 100644
--- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js
+++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js
@@ -5,12 +5,15 @@ import { isTextField } from '@wordpress/dom';
import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes';
import { useSelect, useDispatch } from '@wordpress/data';
import { useRefEffect } from '@wordpress/compose';
+import { createRoot } from '@wordpress/element';
+import { store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../../store';
import { unlock } from '../../../lock-unlock';
+import BlockDraggableChip from '../../../components/block-draggable/draggable-chip';
/**
* Adds block behaviour:
@@ -21,12 +24,16 @@ import { unlock } from '../../../lock-unlock';
* @param {string} clientId Block client ID.
*/
export function useEventHandlers( { clientId, isSelected } ) {
- const { getBlockRootClientId, getBlockIndex, isZoomOut } = unlock(
- useSelect( blockEditorStore )
- );
- const { insertAfterBlock, removeBlock, resetZoomLevel } = unlock(
- useDispatch( blockEditorStore )
- );
+ const { getBlockType } = useSelect( blocksStore );
+ const { getBlockRootClientId, isZoomOut, hasMultiSelection, getBlockName } =
+ unlock( useSelect( blockEditorStore ) );
+ const {
+ insertAfterBlock,
+ removeBlock,
+ resetZoomLevel,
+ startDraggingBlocks,
+ stopDraggingBlocks,
+ } = unlock( useDispatch( blockEditorStore ) );
return useRefEffect(
( node ) => {
@@ -76,7 +83,102 @@ export function useEventHandlers( { clientId, isSelected } ) {
* @param {DragEvent} event Drag event.
*/
function onDragStart( event ) {
- event.preventDefault();
+ if (
+ node !== event.target ||
+ node.isContentEditable ||
+ node.ownerDocument.activeElement !== node ||
+ hasMultiSelection()
+ ) {
+ event.preventDefault();
+ return;
+ }
+ const data = JSON.stringify( {
+ type: 'block',
+ srcClientIds: [ clientId ],
+ srcRootClientId: getBlockRootClientId( clientId ),
+ } );
+ event.dataTransfer.effectAllowed = 'move'; // remove "+" cursor
+ event.dataTransfer.clearData();
+ event.dataTransfer.setData( 'wp-blocks', data );
+ const { ownerDocument } = node;
+ const { defaultView } = ownerDocument;
+ const selection = defaultView.getSelection();
+ selection.removeAllRanges();
+
+ const domNode = document.createElement( 'div' );
+ const root = createRoot( domNode );
+ root.render(
+
+ );
+ document.body.appendChild( domNode );
+ domNode.style.position = 'absolute';
+ domNode.style.top = '0';
+ domNode.style.left = '0';
+ domNode.style.zIndex = '1000';
+ domNode.style.pointerEvents = 'none';
+
+ // Setting the drag chip as the drag image actually works, but
+ // the behaviour is slightly different in every browser. In
+ // Safari, it animates, in Firefox it's slightly transparent...
+ // So we set a fake drag image and have to reposition it
+ // ourselves.
+ const dragElement = ownerDocument.createElement( 'div' );
+ // Chrome will show a globe icon if the drag element does not
+ // have dimensions.
+ dragElement.style.width = '1px';
+ dragElement.style.height = '1px';
+ dragElement.style.position = 'fixed';
+ dragElement.style.visibility = 'hidden';
+ ownerDocument.body.appendChild( dragElement );
+ event.dataTransfer.setDragImage( dragElement, 0, 0 );
+
+ let offset = { x: 0, y: 0 };
+
+ if ( document !== ownerDocument ) {
+ const frame = defaultView.frameElement;
+ if ( frame ) {
+ const rect = frame.getBoundingClientRect();
+ offset = { x: rect.left, y: rect.top };
+ }
+ }
+
+ // chip handle offset
+ offset.x -= 58;
+
+ function over( e ) {
+ domNode.style.transform = `translate( ${
+ e.clientX + offset.x
+ }px, ${ e.clientY + offset.y }px )`;
+ }
+
+ over( event );
+
+ function end() {
+ ownerDocument.removeEventListener( 'dragover', over );
+ ownerDocument.removeEventListener( 'dragend', end );
+ domNode.remove();
+ dragElement.remove();
+ stopDraggingBlocks();
+ document.body.classList.remove(
+ 'is-dragging-components-draggable'
+ );
+ ownerDocument.documentElement.classList.remove(
+ 'is-dragging'
+ );
+ }
+
+ ownerDocument.addEventListener( 'dragover', over );
+ ownerDocument.addEventListener( 'dragend', end );
+ ownerDocument.addEventListener( 'drop', end );
+
+ startDraggingBlocks( [ clientId ] );
+ // Important because it hides the block toolbar.
+ document.body.classList.add(
+ 'is-dragging-components-draggable'
+ );
+ ownerDocument.documentElement.classList.add( 'is-dragging' );
}
node.addEventListener( 'keydown', onKeyDown );
@@ -91,11 +193,13 @@ export function useEventHandlers( { clientId, isSelected } ) {
clientId,
isSelected,
getBlockRootClientId,
- getBlockIndex,
insertAfterBlock,
removeBlock,
isZoomOut,
resetZoomLevel,
+ hasMultiSelection,
+ startDraggingBlocks,
+ stopDraggingBlocks,
]
);
}
diff --git a/packages/block-editor/src/components/iframe/content.scss b/packages/block-editor/src/components/iframe/content.scss
index 9b02716671de7..74efb63c0e077 100644
--- a/packages/block-editor/src/components/iframe/content.scss
+++ b/packages/block-editor/src/components/iframe/content.scss
@@ -60,5 +60,9 @@
}
}
}
+
+ .wp-block[draggable] {
+ cursor: grab;
+ }
}
}
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index 8f179d08570ad..bc8eca6ea94d0 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -431,6 +431,11 @@ export function RichTextWrapper(
aria-multiline={ ! disableLineBreaks }
aria-readonly={ shouldDisableEditing }
{ ...props }
+ // Unset draggable (coming from block props) for contentEditable
+ // elements because it will interfere with multi block selection
+ // when the contentEditable and draggable elements are the same
+ // element.
+ draggable={ undefined }
aria-label={
bindingsLabel || props[ 'aria-label' ] || placeholder
}
diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js
index 2a3e4948d40b3..221e5ab74ebb2 100644
--- a/packages/block-editor/src/components/use-block-drop-zone/index.js
+++ b/packages/block-editor/src/components/use-block-drop-zone/index.js
@@ -332,6 +332,7 @@ export default function useBlockDropZone( {
isGroupable,
isZoomOut,
getSectionRootClientId,
+ getBlockParents,
} = unlock( useSelect( blockEditorStore ) );
const {
showInsertionPoint,
@@ -358,13 +359,29 @@ export default function useBlockDropZone( {
// So, ensure that the drag state is set when the user drags over a drop zone.
startDragging();
}
+
+ const draggedBlockClientIds = getDraggedBlockClientIds();
+ const targetParents = [
+ targetRootClientId,
+ ...getBlockParents( targetRootClientId, true ),
+ ];
+
+ // Check if the target is within any of the dragged blocks.
+ const isTargetWithinDraggedBlocks = draggedBlockClientIds.some(
+ ( clientId ) => targetParents.includes( clientId )
+ );
+
+ if ( isTargetWithinDraggedBlocks ) {
+ return;
+ }
+
const allowedBlocks = getAllowedBlocks( targetRootClientId );
const targetBlockName = getBlockNamesByClientId( [
targetRootClientId,
] )[ 0 ];
const draggedBlockNames = getBlockNamesByClientId(
- getDraggedBlockClientIds()
+ draggedBlockClientIds
);
const isBlockDroppingAllowed = isDropTargetValid(
getBlockType,
diff --git a/packages/block-editor/src/components/writing-flow/use-drag-selection.js b/packages/block-editor/src/components/writing-flow/use-drag-selection.js
index 1569c45a7c676..ea4c09b3dc957 100644
--- a/packages/block-editor/src/components/writing-flow/use-drag-selection.js
+++ b/packages/block-editor/src/components/writing-flow/use-drag-selection.js
@@ -80,7 +80,17 @@ export default function useDragSelection() {
} );
}
+ let lastMouseDownTarget;
+
+ function onMouseDown( { target } ) {
+ lastMouseDownTarget = target;
+ }
+
function onMouseLeave( { buttons, target, relatedTarget } ) {
+ if ( ! target.contains( lastMouseDownTarget ) ) {
+ return;
+ }
+
// If we're moving into a child element, ignore. We're tracking
// the mouse leaving the element to a parent, no a child.
if ( target.contains( relatedTarget ) ) {
@@ -141,6 +151,7 @@ export default function useDragSelection() {
}
node.addEventListener( 'mouseout', onMouseLeave );
+ node.addEventListener( 'mousedown', onMouseDown );
return () => {
node.removeEventListener( 'mouseout', onMouseLeave );
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 7865993d4e995..2785447227416 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -21,6 +21,10 @@
- Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)).
- Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)).
+### Bug Fixes
+
+- `ResizableBox`: Make drag handles focusable ([#67305](https://github.com/WordPress/gutenberg/pull/67305)).
+
## 28.13.0 (2024-11-27)
### Deprecations
diff --git a/packages/components/src/resizable-box/index.tsx b/packages/components/src/resizable-box/index.tsx
index 1b05270ea0bf2..3bf3d36aa0d5c 100644
--- a/packages/components/src/resizable-box/index.tsx
+++ b/packages/components/src/resizable-box/index.tsx
@@ -112,6 +112,16 @@ function UnforwardedResizableBox(
showHandle && 'has-show-handle',
className
) }
+ // Add a focusable element within the drag handle. Unfortunately,
+ // `re-resizable` does not make them properly focusable by default,
+ // causing focus to move the the block wrapper which triggers block
+ // drag.
+ handleComponent={ Object.fromEntries(
+ Object.keys( HANDLE_CLASSES ).map( ( key ) => [
+ key,
+
,
+ ] )
+ ) }
handleClasses={ HANDLE_CLASSES }
handleStyles={ HANDLE_STYLES }
ref={ ref }
diff --git a/packages/components/src/resizable-box/style.scss b/packages/components/src/resizable-box/style.scss
index 3c9efd2713646..4db3d27b5fab6 100644
--- a/packages/components/src/resizable-box/style.scss
+++ b/packages/components/src/resizable-box/style.scss
@@ -15,6 +15,14 @@ $resize-handler-container-size: $resize-handler-size + ($grid-unit-05 * 2); // M
.components-resizable-box__container.has-show-handle & {
display: block;
}
+
+ > div {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ z-index: z-index(".components-resizable-box__handle");
+ outline: none;
+ }
}
// Make the image inside the resize to get the full width
diff --git a/test/e2e/specs/editor/blocks/spacer.spec.js b/test/e2e/specs/editor/blocks/spacer.spec.js
index f089402514623..da262c9b4e26d 100644
--- a/test/e2e/specs/editor/blocks/spacer.spec.js
+++ b/test/e2e/specs/editor/blocks/spacer.spec.js
@@ -43,7 +43,9 @@ test.describe( 'Spacer', () => {
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
await expect(
- editor.canvas.locator( 'role=document[name="Block: Spacer"i]' )
+ editor.canvas.locator(
+ 'role=document[name="Block: Spacer"i] >> css=.components-resizable-box__handle >> [tabindex]'
+ )
).toBeFocused();
} );
} );
diff --git a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js
index d6563ce9cb5f5..033a69e2d6170 100644
--- a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js
+++ b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js
@@ -168,7 +168,10 @@ test.describe( 'Registered sources', () => {
name: 'Block: Image',
} )
.locator( 'img' );
- await imageBlockImg.click();
+ // Playwright will complain that the pointer events are captured by
+ // the parent, but that's fine.
+ // eslint-disable-next-line playwright/no-force-option
+ await imageBlockImg.click( { force: true } );
// Image src is the custom field value.
await expect( imageBlockImg ).toHaveAttribute(
@@ -735,7 +738,10 @@ test.describe( 'Registered sources', () => {
name: 'Block: Image',
} )
.locator( 'img' );
- await imageBlockImg.click();
+ // Playwright will complain that the pointer events are captured by
+ // the parent, but that's fine.
+ // eslint-disable-next-line playwright/no-force-option
+ await imageBlockImg.click( { force: true } );
// Edit the custom field value in the alt textarea.
const altInputArea = page
diff --git a/test/e2e/specs/editor/various/draggable-blocks.spec.js b/test/e2e/specs/editor/various/draggable-blocks.spec.js
index e08030191dd60..704817f4a2c38 100644
--- a/test/e2e/specs/editor/various/draggable-blocks.spec.js
+++ b/test/e2e/specs/editor/various/draggable-blocks.spec.js
@@ -18,6 +18,14 @@ test.use( {
},
} );
+async function dragTo( page, x, y ) {
+ // Call the move function twice to make sure the `dragOver` event is sent.
+ // @see https://github.com/microsoft/playwright/issues/17153
+ for ( let i = 0; i < 2; i += 1 ) {
+ await page.mouse.move( x, y );
+ }
+}
+
test.describe( 'Draggable block', () => {
test.beforeEach( async ( { admin } ) => {
await admin.createNewPost();
@@ -60,14 +68,7 @@ test.describe( 'Draggable block', () => {
'role=document[name="Block: Paragraph"i] >> text=1'
);
const firstParagraphBound = await firstParagraph.boundingBox();
- // Call the move function twice to make sure the `dragOver` event is sent.
- // @see https://github.com/microsoft/playwright/issues/17153
- for ( let i = 0; i < 2; i += 1 ) {
- await page.mouse.move(
- firstParagraphBound.x,
- firstParagraphBound.y
- );
- }
+ await dragTo( page, firstParagraphBound.x, firstParagraphBound.y );
await expect(
page.locator( 'data-testid=block-draggable-chip >> visible=true' )
@@ -132,15 +133,11 @@ test.describe( 'Draggable block', () => {
'role=document[name="Block: Paragraph"i] >> text=2'
);
const secondParagraphBound = await secondParagraph.boundingBox();
- // Call the move function twice to make sure the `dragOver` event is sent.
- // @see https://github.com/microsoft/playwright/issues/17153
- // Make sure mouse is > 30px within the block for bottom drop indicator to appear.
- for ( let i = 0; i < 2; i += 1 ) {
- await page.mouse.move(
- secondParagraphBound.x + 32,
- secondParagraphBound.y + secondParagraphBound.height * 0.75
- );
- }
+ await dragTo(
+ page,
+ secondParagraphBound.x + 32,
+ secondParagraphBound.y + secondParagraphBound.height * 0.75
+ );
await expect(
page.locator( 'data-testid=block-draggable-chip >> visible=true' )
@@ -216,14 +213,11 @@ test.describe( 'Draggable block', () => {
'role=document[name="Block: Paragraph"i] >> text=1'
);
const firstParagraphBound = await firstParagraph.boundingBox();
- // Call the move function twice to make sure the `dragOver` event is sent.
- // @see https://github.com/microsoft/playwright/issues/17153
- for ( let i = 0; i < 2; i += 1 ) {
- await page.mouse.move(
- firstParagraphBound.x + firstParagraphBound.width * 0.25,
- firstParagraphBound.y
- );
- }
+ await dragTo(
+ page,
+ firstParagraphBound.x + firstParagraphBound.width * 0.25,
+ firstParagraphBound.y
+ );
await expect(
page.locator( 'data-testid=block-draggable-chip >> visible=true' )
@@ -297,14 +291,11 @@ test.describe( 'Draggable block', () => {
'role=document[name="Block: Paragraph"i] >> text=2'
);
const secondParagraphBound = await secondParagraph.boundingBox();
- // Call the move function twice to make sure the `dragOver` event is sent.
- // @see https://github.com/microsoft/playwright/issues/17153
- for ( let i = 0; i < 2; i += 1 ) {
- await page.mouse.move(
- secondParagraphBound.x + secondParagraphBound.width * 0.75,
- secondParagraphBound.y
- );
- }
+ await dragTo(
+ page,
+ secondParagraphBound.x + secondParagraphBound.width * 0.75,
+ secondParagraphBound.y
+ );
await expect(
page.locator( 'data-testid=block-draggable-chip >> visible=true' )
@@ -465,4 +456,47 @@ test.describe( 'Draggable block', () => {
] );
}
} );
+
+ test( 'can directly drag an image', async ( { page, editor } ) => {
+ await editor.insertBlock( { name: 'core/image' } );
+ await editor.insertBlock( {
+ name: 'core/group',
+ attributes: { layout: { type: 'constrained' } },
+ innerBlocks: [ { name: 'core/paragraph' } ],
+ } );
+
+ const imageBlock = editor.canvas.getByRole( 'document', {
+ name: 'Block: Image',
+ } );
+
+ const groupBlock = editor.canvas.getByRole( 'document', {
+ name: 'Block: Group',
+ } );
+
+ await imageBlock.hover();
+ await page.mouse.down();
+ const groupBlockBox = await groupBlock.boundingBox();
+ await dragTo(
+ page,
+ groupBlockBox.x + groupBlockBox.width * 0.5,
+ groupBlockBox.y + groupBlockBox.height * 0.5
+ );
+ await page.mouse.up();
+
+ await expect.poll( editor.getBlocks ).toMatchObject( [
+ {
+ name: 'core/group',
+ attributes: {
+ tagName: 'div',
+ layout: { type: 'constrained' },
+ },
+ innerBlocks: [
+ {
+ name: 'core/image',
+ attributes: { alt: '', caption: '' },
+ },
+ ],
+ },
+ ] );
+ } );
} );