diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 7b78148328aec..616864b7b5a84 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -26,7 +26,7 @@ // When selecting multiple blocks, we provide an additional selection indicator. &.is-navigate-mode .block-editor-block-list__block.is-selected, &.is-navigate-mode .block-editor-block-list__block.is-hovered, - .block-editor-block-list__block.is-multi-selected:not([contenteditable]), + .block-editor-block-list__block.is-multi-selected:not(.is-partially-selected), .block-editor-block-list__block.is-highlighted, .block-editor-block-list__block.is-highlighted ~ .is-multi-selected { &::after { diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js index 31369c7a3591c..be112f6dc0744 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js @@ -33,6 +33,7 @@ export function useBlockClassNames( clientId ) { getSettings, hasSelectedInnerBlock, isTyping, + __unstableIsFullySelected, } = select( blockEditorStore ); const { outlineMode } = getSettings(); const isDragging = isBlockBeingDragged( clientId ); @@ -44,10 +45,13 @@ export function useBlockClassNames( clientId ) { clientId, checkDeep ); + const isMultiSelected = isBlockMultiSelected( clientId ); return classnames( { 'is-selected': isSelected, 'is-highlighted': isBlockHighlighted( clientId ), - 'is-multi-selected': isBlockMultiSelected( clientId ), + 'is-multi-selected': isMultiSelected, + 'is-partially-selected': + isMultiSelected && ! __unstableIsFullySelected(), 'is-reusable': isReusableBlock( getBlockType( name ) ), 'is-dragging': isDragging, 'has-child-selected': isAncestorOfSelectedBlock, diff --git a/packages/block-editor/src/components/writing-flow/use-arrow-nav.js b/packages/block-editor/src/components/writing-flow/use-arrow-nav.js index 290c753358728..33755073af888 100644 --- a/packages/block-editor/src/components/writing-flow/use-arrow-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-arrow-nav.js @@ -11,7 +11,7 @@ import { isRTL, } from '@wordpress/dom'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; /** @@ -131,12 +131,15 @@ export function getClosestTabbable( export default function useArrowNav() { const { getSelectedBlockClientId, + getMultiSelectedBlocksStartClientId, getMultiSelectedBlocksEndClientId, getPreviousBlockClientId, getNextBlockClientId, getSettings, hasMultiSelection, + __unstableIsFullySelected, } = useSelect( blockEditorStore ); + const { selectBlock } = useDispatch( blockEditorStore ); return useRefEffect( ( node ) => { // Here a DOMRect is stored while moving the caret vertically so // vertical position of the start position can be restored. This is to @@ -186,7 +189,35 @@ export default function useArrowNav() { const { ownerDocument } = node; const { defaultView } = ownerDocument; + // If there is a multi-selection, the arrow keys should collapse the + // selection to the start or end of the selection. if ( hasMultiSelection() ) { + // Only handle if we have a full selection (not a native partial + // selection). + if ( ! __unstableIsFullySelected() ) { + return; + } + + if ( event.defaultPrevented ) { + return; + } + + if ( ! isNav ) { + return; + } + + if ( isShift ) { + return; + } + + event.preventDefault(); + + if ( isReverse ) { + selectBlock( getMultiSelectedBlocksStartClientId() ); + } else { + selectBlock( getMultiSelectedBlocksEndClientId(), -1 ); + } + return; } diff --git a/packages/block-editor/src/components/writing-flow/use-multi-selection.js b/packages/block-editor/src/components/writing-flow/use-multi-selection.js index aea4ce242b23c..e51abfaba9472 100644 --- a/packages/block-editor/src/components/writing-flow/use-multi-selection.js +++ b/packages/block-editor/src/components/writing-flow/use-multi-selection.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { first, last } from 'lodash'; - /** * WordPress dependencies */ @@ -13,7 +8,6 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { store as blockEditorStore } from '../../store'; -import { __unstableUseBlockRef as useBlockRef } from '../block-list/use-block-props/use-block-refs'; function selector( select ) { const { @@ -44,10 +38,6 @@ export default function useMultiSelection() { selectedBlockClientId, isFullSelection, } = useSelect( selector, [] ); - const selectedRef = useBlockRef( selectedBlockClientId ); - // These must be in the right DOM order. - const startRef = useBlockRef( first( multiSelectedBlockClientIds ) ); - const endRef = useBlockRef( last( multiSelectedBlockClientIds ) ); /** * When the component updates, and there is multi selection, we need to @@ -66,26 +56,6 @@ export default function useMultiSelection() { } if ( ! hasMultiSelection || isMultiSelecting ) { - if ( ! selectedBlockClientId || isMultiSelecting ) { - return; - } - - const selection = defaultView.getSelection(); - - if ( selection.rangeCount && ! selection.isCollapsed ) { - const blockNode = selectedRef.current; - const { startContainer, endContainer } = - selection.getRangeAt( 0 ); - - if ( - !! blockNode && - ( ! blockNode.contains( startContainer ) || - ! blockNode.contains( endContainer ) ) - ) { - selection.removeAllRanges(); - } - } - return; } @@ -105,25 +75,8 @@ export default function useMultiSelection() { // able to select across instances immediately. node.contentEditable = true; - // For some browsers, like Safari, it is important that focus happens - // BEFORE selection. + defaultView.getSelection().removeAllRanges(); node.focus(); - - // The block refs might not be immediately available - // when dragging blocks into another block. - if ( ! startRef.current || ! endRef.current ) { - return; - } - - const selection = defaultView.getSelection(); - const range = ownerDocument.createRange(); - - // These must be in the right DOM order. - range.setStartBefore( startRef.current ); - range.setEndAfter( endRef.current ); - - selection.removeAllRanges(); - selection.addRange( range ); }, [ hasMultiSelection, diff --git a/packages/block-editor/src/components/writing-flow/use-selection-observer.js b/packages/block-editor/src/components/writing-flow/use-selection-observer.js index 324a9a6eabfc7..bb96f19a996b9 100644 --- a/packages/block-editor/src/components/writing-flow/use-selection-observer.js +++ b/packages/block-editor/src/components/writing-flow/use-selection-observer.js @@ -84,12 +84,11 @@ export default function useSelectionObserver() { function onSelectionChange( event ) { const selection = defaultView.getSelection(); - // If no selection is found, end multi selection and disable the - // contentEditable wrapper. + if ( ! selection.rangeCount ) { - setContentEditableWrapper( node, false ); return; } + // If selection is collapsed and we haven't used `shift+click`, // end multi selection and disable the contentEditable wrapper. // We have to check about `shift+click` case because elements diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js index 2a98d2aec100e..5e14d57dcd759 100644 --- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -108,7 +108,6 @@ describe( 'Multi-block selection', () => { await pressKeyWithModifier( 'primary', 'a' ); await pressKeyWithModifier( 'primary', 'a' ); - await testNativeSelection(); expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2, 3 ] ); // TODO: It would be great to do this test by spying on `wp.a11y.speak`, @@ -420,7 +419,6 @@ describe( 'Multi-block selection', () => { await page.mouse.move( coord2.x, coord2.y, { steps: 10 } ); await page.mouse.up(); - await testNativeSelection(); expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] ); } );