diff --git a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js index 2ee224d96a4d7..bb240e67859d4 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js +++ b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js @@ -32,6 +32,7 @@ function useInsertionPoint( { clientId, isAppender, selectBlockOnInsert, + insertionIndex, } ) { const { destinationRootClientId, @@ -76,12 +77,16 @@ function useInsertionPoint( { } = useDispatch( 'core/block-editor' ); function getInsertionIndex() { + if ( insertionIndex !== undefined ) { + return insertionIndex; + } + // If the clientId is defined, we insert at the position of the block. if ( clientId ) { return getBlockIndex( clientId, destinationRootClientId ); } - // If there a selected block, we insert after the selected block. + // If there's a selected block, and the selected block is not the destination root block, we insert after the selected block. const end = getBlockSelectionEnd(); if ( ! isAppender && end ) { return getBlockIndex( end, destinationRootClientId ) + 1; diff --git a/packages/block-editor/src/components/inserter/library.js b/packages/block-editor/src/components/inserter/library.js index d22966dff2f67..74e72702042d1 100644 --- a/packages/block-editor/src/components/inserter/library.js +++ b/packages/block-editor/src/components/inserter/library.js @@ -19,7 +19,8 @@ function InserterLibrary( { isAppender, showInserterHelpPanel, showMostUsedBlocks = false, - __experimentalSelectBlockOnInsert: selectBlockOnInsert, + __experimentalSelectBlockOnInsert, + __experimentalInsertionIndex, onSelect = noop, } ) { const destinationRootClientId = useSelect( @@ -41,7 +42,10 @@ function InserterLibrary( { isAppender={ isAppender } showInserterHelpPanel={ showInserterHelpPanel } showMostUsedBlocks={ showMostUsedBlocks } - __experimentalSelectBlockOnInsert={ selectBlockOnInsert } + __experimentalSelectBlockOnInsert={ + __experimentalSelectBlockOnInsert + } + __experimentalInsertionIndex={ __experimentalInsertionIndex } /> ); } diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 3d59359799397..8669d52b51a6a 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -26,6 +26,7 @@ function InserterMenu( { clientId, isAppender, __experimentalSelectBlockOnInsert, + __experimentalInsertionIndex, onSelect, showInserterHelpPanel, showMostUsedBlocks, @@ -46,6 +47,7 @@ function InserterMenu( { clientId, isAppender, selectBlockOnInsert: __experimentalSelectBlockOnInsert, + insertionIndex: __experimentalInsertionIndex, } ); const { hasPatterns, hasReusableBlocks } = useSelect( ( select ) => { const { diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js index 949c381719976..d489e81c31065 100644 --- a/packages/edit-widgets/src/components/header/index.js +++ b/packages/edit-widgets/src/components/header/index.js @@ -20,15 +20,18 @@ import { useRef } from '@wordpress/element'; import SaveButton from '../save-button'; import UndoButton from './undo-redo/undo'; import RedoButton from './undo-redo/redo'; -import useLastSelectedRootId from '../../hooks/use-last-selected-root-id'; +import useLastSelectedWidgetArea from '../../hooks/use-last-selected-widget-area'; function Header() { const inserterButton = useRef(); const isLargeViewport = useViewportMatch( 'medium' ); + const widgetAreaClientId = useLastSelectedWidgetArea(); const isLastSelectedWidgetAreaOpen = useSelect( ( select ) => - select( 'core/edit-widgets' ).getIsWidgetAreaOpen( rootClientId ), - [ rootClientId ] + select( 'core/edit-widgets' ).getIsWidgetAreaOpen( + widgetAreaClientId + ), + [ widgetAreaClientId ] ); const isInserterOpened = useSelect( ( select ) => select( 'core/edit-widgets' ).isInserterOpened() @@ -37,7 +40,6 @@ function Header() { 'core/edit-widgets' ); const { selectBlock } = useDispatch( 'core/block-editor' ); - const rootClientId = useLastSelectedRootId(); const handleClick = () => { if ( isInserterOpened ) { // Focusing the inserter button closes the inserter popover @@ -45,9 +47,9 @@ function Header() { } else { if ( ! isLastSelectedWidgetAreaOpen ) { // Select the last selected block if hasn't already. - selectBlock( rootClientId ); + selectBlock( widgetAreaClientId ); // Open the last selected widget area when opening the inserter. - setIsWidgetAreaOpen( rootClientId, true ); + setIsWidgetAreaOpen( widgetAreaClientId, true ); } // The DOM updates resulting from selectBlock() and setIsInserterOpened() calls are applied the // same tick and pretty much in a random order. The inserter is closed if any other part of the diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index ddfc49a3abdf7..bc2e77870abe4 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -1,100 +1,22 @@ /** * WordPress dependencies */ -import { Popover, Button } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; -import { close } from '@wordpress/icons'; -import { __experimentalLibrary as Library } from '@wordpress/block-editor'; -import { useEffect } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; +import { Popover } from '@wordpress/components'; import { PluginArea } from '@wordpress/plugins'; /** * Internal dependencies */ import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; -import Header from '../header'; import Sidebar from '../sidebar'; -import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; -import PopoverWrapper from './popover-wrapper'; -import useLastSelectedRootId from '../../hooks/use-last-selected-root-id'; +import Interface from './interface'; function Layout( { blockEditorSettings } ) { - const isMobileViewport = useViewportMatch( 'medium', '<' ); - const isHugeViewport = useViewportMatch( 'huge', '>=' ); - const { setIsInserterOpened, closeGeneralSidebar } = useDispatch( - 'core/edit-widgets' - ); - const rootClientId = useLastSelectedRootId(); - - const { hasSidebarEnabled, isInserterOpened } = useSelect( ( select ) => ( { - hasSidebarEnabled: !! select( - 'core/interface' - ).getActiveComplementaryArea( 'core/edit-widgets' ), - isInserterOpened: !! select( 'core/edit-widgets' ).isInserterOpened(), - } ) ); - - // Inserter and Sidebars are mutually exclusive - useEffect( () => { - if ( hasSidebarEnabled && ! isHugeViewport ) { - setIsInserterOpened( false ); - } - }, [ hasSidebarEnabled, isHugeViewport ] ); - - useEffect( () => { - if ( isInserterOpened && ! isHugeViewport ) { - closeGeneralSidebar(); - } - }, [ isInserterOpened, isHugeViewport ] ); - return ( - } - leftSidebar={ - isInserterOpened && ( - setIsInserterOpened( false ) } - > -
-
-
-
- { - if ( isMobileViewport ) { - setIsInserterOpened( false ); - } - } } - rootClientId={ rootClientId } - /> -
-
-
- ) - } - sidebar={ - hasSidebarEnabled && ( - - ) - } - content={ - - } - /> + diff --git a/packages/edit-widgets/src/components/layout/interface.js b/packages/edit-widgets/src/components/layout/interface.js new file mode 100644 index 0000000000000..ddccb34206fd6 --- /dev/null +++ b/packages/edit-widgets/src/components/layout/interface.js @@ -0,0 +1,98 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; +import { close } from '@wordpress/icons'; +import { __experimentalLibrary as Library } from '@wordpress/block-editor'; +import { useEffect } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; + +/** + * Internal dependencies + */ +import Header from '../header'; +import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; +import PopoverWrapper from './popover-wrapper'; +import useWidgetLibraryInsertionPoint from '../../hooks/use-widget-library-insertion-point'; + +function Interface( { blockEditorSettings } ) { + const isMobileViewport = useViewportMatch( 'medium', '<' ); + const isHugeViewport = useViewportMatch( 'huge', '>=' ); + const { setIsInserterOpened, closeGeneralSidebar } = useDispatch( + 'core/edit-widgets' + ); + const { rootClientId, insertionIndex } = useWidgetLibraryInsertionPoint(); + + const { hasSidebarEnabled, isInserterOpened } = useSelect( ( select ) => ( { + hasSidebarEnabled: !! select( + 'core/interface' + ).getActiveComplementaryArea( 'core/edit-widgets' ), + isInserterOpened: !! select( 'core/edit-widgets' ).isInserterOpened(), + } ) ); + + // Inserter and Sidebars are mutually exclusive + useEffect( () => { + if ( hasSidebarEnabled && ! isHugeViewport ) { + setIsInserterOpened( false ); + } + }, [ hasSidebarEnabled, isHugeViewport ] ); + + useEffect( () => { + if ( isInserterOpened && ! isHugeViewport ) { + closeGeneralSidebar(); + } + }, [ isInserterOpened, isHugeViewport ] ); + + return ( + } + leftSidebar={ + isInserterOpened && ( + setIsInserterOpened( false ) } + > +
+
+
+
+ { + if ( isMobileViewport ) { + setIsInserterOpened( false ); + } + } } + rootClientId={ rootClientId } + __experimentalInsertionIndex={ + insertionIndex + } + /> +
+
+
+ ) + } + sidebar={ + hasSidebarEnabled && ( + + ) + } + content={ + + } + /> + ); +} + +export default Interface; diff --git a/packages/edit-widgets/src/hooks/use-last-selected-root-id.js b/packages/edit-widgets/src/hooks/use-last-selected-root-id.js deleted file mode 100644 index 692e9cb1b2141..0000000000000 --- a/packages/edit-widgets/src/hooks/use-last-selected-root-id.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { buildWidgetAreasPostId, KIND, POST_TYPE } from '../store/utils'; - -const useLastSelectedRootId = () => { - const firstRootId = useSelect( ( select ) => { - // Default to the first widget area - const { getEntityRecord } = select( 'core' ); - const widgetAreasPost = getEntityRecord( - KIND, - POST_TYPE, - buildWidgetAreasPostId() - ); - return widgetAreasPost?.blocks[ 0 ]?.clientId; - }, [] ); - - const selectedRootId = useSelect( ( select ) => { - const { getBlockRootClientId, getBlockSelectionEnd } = select( - 'core/block-editor' - ); - const blockSelectionEnd = getBlockSelectionEnd(); - const blockRootClientId = getBlockRootClientId( blockSelectionEnd ); - // getBlockRootClientId returns an empty string for top-level blocks, in which case just return the block id. - return blockRootClientId === '' ? blockSelectionEnd : blockRootClientId; - }, [] ); - - // Fallbacks to the first widget area. - return selectedRootId || firstRootId; -}; - -export default useLastSelectedRootId; diff --git a/packages/edit-widgets/src/hooks/use-last-selected-widget-area.js b/packages/edit-widgets/src/hooks/use-last-selected-widget-area.js new file mode 100644 index 0000000000000..4a33e2b3d6097 --- /dev/null +++ b/packages/edit-widgets/src/hooks/use-last-selected-widget-area.js @@ -0,0 +1,53 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { buildWidgetAreasPostId, KIND, POST_TYPE } from '../store/utils'; + +/** + * A react hook that returns the client id of the last widget area to have + * been selected, or to have a selected block within it. + * + * @return {string} clientId of the widget area last selected. + */ +const useLastSelectedWidgetArea = () => + useSelect( ( select ) => { + const { getBlockSelectionEnd, getBlockParents, getBlockName } = select( + 'core/block-editor' + ); + const blockSelectionEndClientId = getBlockSelectionEnd(); + + // If the selected block is a widget area, return its clientId. + if ( + getBlockName( blockSelectionEndClientId ) === 'core/widget-area' + ) { + return blockSelectionEndClientId; + } + + // Otherwise, find the clientId of the top-level widget area by looking + // through the selected block's parents. + const blockParents = getBlockParents( blockSelectionEndClientId ); + const rootWidgetAreaClientId = blockParents.find( + ( clientId ) => getBlockName( clientId ) === 'core/widget-area' + ); + + if ( rootWidgetAreaClientId ) { + return rootWidgetAreaClientId; + } + + // If no widget area has been selected, return the clientId of the first + // area. + const { getEntityRecord } = select( 'core' ); + const widgetAreasPost = getEntityRecord( + KIND, + POST_TYPE, + buildWidgetAreasPostId() + ); + return widgetAreasPost?.blocks[ 0 ]?.clientId; + }, [] ); + +export default useLastSelectedWidgetArea; diff --git a/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js b/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js new file mode 100644 index 0000000000000..445272f05d0d5 --- /dev/null +++ b/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js @@ -0,0 +1,54 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { buildWidgetAreasPostId, KIND, POST_TYPE } from '../store/utils'; + +const useWidgetLibraryInsertionPoint = () => { + const firstRootId = useSelect( ( select ) => { + // Default to the first widget area + const { getEntityRecord } = select( 'core' ); + const widgetAreasPost = getEntityRecord( + KIND, + POST_TYPE, + buildWidgetAreasPostId() + ); + return widgetAreasPost?.blocks[ 0 ]?.clientId; + }, [] ); + + return useSelect( + ( select ) => { + const { + getBlockRootClientId, + getBlockSelectionEnd, + getBlockOrder, + getBlockIndex, + } = select( 'core/block-editor' ); + + const clientId = getBlockSelectionEnd() || firstRootId; + const rootClientId = getBlockRootClientId( clientId ); + + // If the selected block is at the root level, it's a widget area and + // blocks can't be inserted here. Return this block as the root and the + // last child clientId indicating insertion at the end. + if ( clientId && rootClientId === '' ) { + return { + rootClientId: clientId, + insertionIndex: getBlockOrder( clientId ).length, + }; + } + + return { + rootClientId, + insertionIndex: getBlockIndex( clientId, rootClientId ) + 1, + }; + }, + [ firstRootId ] + ); +}; + +export default useWidgetLibraryInsertionPoint;