From 9ffbd9c215b2b43e527355d83b8d3f313b68582f Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:19:13 +0000 Subject: [PATCH] Block editor: try direct drag (outside text editable) (#67305) Co-authored-by: ellatrix Co-authored-by: youknowriad Co-authored-by: jasmussen Co-authored-by: draganescu --- .../components/block-draggable/content.scss | 16 ++- .../src/components/block-list/block.js | 1 + .../src/components/block-list/content.scss | 6 + .../block-list/use-block-props/index.js | 7 + .../use-firefox-draggable-compatibility.js | 25 ++++ .../use-selected-block-event-handlers.js | 120 ++++++++++++++++-- .../src/components/iframe/content.scss | 4 + .../src/components/rich-text/index.js | 5 + .../components/use-block-drop-zone/index.js | 19 ++- .../writing-flow/use-drag-selection.js | 11 ++ packages/components/CHANGELOG.md | 4 + .../components/src/resizable-box/index.tsx | 10 ++ .../components/src/resizable-box/style.scss | 8 ++ test/e2e/specs/editor/blocks/spacer.spec.js | 4 +- .../block-bindings/custom-sources.spec.js | 10 +- .../editor/various/draggable-blocks.spec.js | 100 ++++++++++----- 16 files changed, 300 insertions(+), 50 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js 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: '' }, + }, + ], + }, + ] ); + } ); } );