diff --git a/packages/customize-widgets/src/components/inserter/index.js b/packages/customize-widgets/src/components/inserter/index.js
index 09ef6bdcb5611e..01516947f6dab0 100644
--- a/packages/customize-widgets/src/components/inserter/index.js
+++ b/packages/customize-widgets/src/components/inserter/index.js
@@ -5,13 +5,22 @@ import { __ } from '@wordpress/i18n';
import { __experimentalLibrary as Library } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
+import { useSelect } from '@wordpress/data';
import { closeSmall } from '@wordpress/icons';
+/**
+ * Internal dependencies
+ */
+import { store as customizeWidgetsStore } from '../../store';
+
function Inserter( { setIsOpened } ) {
const inserterTitleId = useInstanceId(
Inserter,
'customize-widget-layout__inserter-panel-title'
);
+ const insertionPoint = useSelect( ( select ) =>
+ select( customizeWidgetsStore ).__experimentalGetInsertionPoint()
+ );
return (
setIsOpened( false ) }
/>
diff --git a/packages/customize-widgets/src/components/inserter/use-inserter.js b/packages/customize-widgets/src/components/inserter/use-inserter.js
index e4668dfbd56222..002f76c3b72cc4 100644
--- a/packages/customize-widgets/src/components/inserter/use-inserter.js
+++ b/packages/customize-widgets/src/components/inserter/use-inserter.js
@@ -1,16 +1,27 @@
/**
* WordPress dependencies
*/
-import { useState, useEffect, useCallback } from '@wordpress/element';
+import { useEffect, useCallback } from '@wordpress/element';
+import { useSelect, useDispatch, select as selectStore } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { store as customizeWidgetsStore } from '../../store';
export default function useInserter( inserter ) {
- const [ isInserterOpened, setIsInserterOpened ] = useState(
- () => inserter.isOpen
+ const isInserterOpened = useSelect( ( select ) =>
+ select( customizeWidgetsStore ).isInserterOpened()
);
+ const { setIsInserterOpened } = useDispatch( customizeWidgetsStore );
useEffect( () => {
- return inserter.subscribe( setIsInserterOpened );
- }, [ inserter ] );
+ if ( isInserterOpened ) {
+ inserter.open();
+ } else {
+ inserter.close();
+ }
+ }, [ inserter, isInserterOpened ] );
return [
isInserterOpened,
@@ -18,16 +29,14 @@ export default function useInserter( inserter ) {
( updater ) => {
let isOpen = updater;
if ( typeof updater === 'function' ) {
- isOpen = updater( inserter.isOpen );
+ isOpen = updater(
+ selectStore( customizeWidgetsStore ).isInserterOpened()
+ );
}
- if ( isOpen ) {
- inserter.open();
- } else {
- inserter.close();
- }
+ setIsInserterOpened( isOpen );
},
- [ inserter ]
+ [ setIsInserterOpened ]
),
];
}
diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js
index 911b24c119e767..f2459f195a0e86 100644
--- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js
+++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js
@@ -88,6 +88,7 @@ export default function SidebarBlockEditor( {
blockEditorSettings,
isFixedToolbarActive,
keepCaretInsideBlock,
+ setIsInserterOpened,
] );
if ( isWelcomeGuideActive ) {
diff --git a/packages/customize-widgets/src/controls/inserter-outer-section.js b/packages/customize-widgets/src/controls/inserter-outer-section.js
index d668d0c0cb8573..b90e24c4dad538 100644
--- a/packages/customize-widgets/src/controls/inserter-outer-section.js
+++ b/packages/customize-widgets/src/controls/inserter-outer-section.js
@@ -3,6 +3,12 @@
*/
import { ESCAPE } from '@wordpress/keycodes';
import { focus } from '@wordpress/dom';
+import { dispatch } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { store as customizeWidgetsStore } from '../store';
export default function getInserterOuterSection() {
const {
@@ -52,14 +58,16 @@ export default function getInserterOuterSection() {
'keydown',
( event ) => {
if (
- this.isOpen &&
+ this.expanded() &&
( event.keyCode === ESCAPE ||
event.code === 'Escape' ) &&
! event.defaultPrevented
) {
event.preventDefault();
event.stopPropagation();
- this.close();
+ dispatch( customizeWidgetsStore ).setIsInserterOpened(
+ false
+ );
}
},
// Use capture mode to make this run before other event listeners.
@@ -67,20 +75,28 @@ export default function getInserterOuterSection() {
);
this.contentContainer.addClass( 'widgets-inserter' );
- }
- get isOpen() {
- return this.expanded();
- }
- subscribe( handler ) {
- this.expanded.bind( handler );
- return () => this.expanded.unbind( handler );
+
+ // Set a flag if the state is being changed from open() or close().
+ // Don't propagate the event if it's an internal action to prevent infinite loop.
+ this.isFromInternalAction = false;
+ this.expanded.bind( () => {
+ if ( ! this.isFromInternalAction ) {
+ // Propagate the event to React to sync the state.
+ dispatch( customizeWidgetsStore ).setIsInserterOpened(
+ this.expanded()
+ );
+ }
+ this.isFromInternalAction = false;
+ } );
}
open() {
- if ( ! this.isOpen ) {
+ if ( ! this.expanded() ) {
const contentContainer = this.contentContainer[ 0 ];
this.activeElementBeforeExpanded =
contentContainer.ownerDocument.activeElement;
+ this.isFromInternalAction = true;
+
this.expand( {
completeCallback() {
// We have to do this in a "completeCallback" or else the elements will not yet be visible/tabbable.
@@ -97,11 +113,13 @@ export default function getInserterOuterSection() {
}
}
close() {
- if ( this.isOpen ) {
+ if ( this.expanded() ) {
const contentContainer = this.contentContainer[ 0 ];
const activeElement =
contentContainer.ownerDocument.activeElement;
+ this.isFromInternalAction = true;
+
this.collapse( {
completeCallback() {
// Return back the focus when closing the inserter.
diff --git a/packages/customize-widgets/src/controls/sidebar-control.js b/packages/customize-widgets/src/controls/sidebar-control.js
index d980832f44d059..d45a7abd44cd20 100644
--- a/packages/customize-widgets/src/controls/sidebar-control.js
+++ b/packages/customize-widgets/src/controls/sidebar-control.js
@@ -1,8 +1,14 @@
+/**
+ * WordPress dependencies
+ */
+import { dispatch } from '@wordpress/data';
+
/**
* Internal dependencies
*/
import SidebarAdapter from '../components/sidebar-block-editor/sidebar-adapter';
import getInserterOuterSection from './inserter-outer-section';
+import { store as customizeWidgetsStore } from '../store';
const getInserterId = ( controlId ) => `widgets-inserter-${ controlId }`;
@@ -45,7 +51,9 @@ export default function getSidebarControl() {
if ( ! args.unchanged ) {
// Close the inserter when the section collapses.
if ( ! expanded ) {
- this.inserter.close();
+ dispatch( customizeWidgetsStore ).setIsInserterOpened(
+ false
+ );
}
this.subscribers.forEach( ( subscriber ) =>
diff --git a/packages/customize-widgets/src/store/actions.js b/packages/customize-widgets/src/store/actions.js
index b156e1e3bbeadb..e1ee14b4682d74 100644
--- a/packages/customize-widgets/src/store/actions.js
+++ b/packages/customize-widgets/src/store/actions.js
@@ -15,3 +15,22 @@ export function __unstableToggleFeature( feature ) {
feature,
};
}
+
+/**
+ * Returns an action object used to open/close the inserter.
+ *
+ * @param {boolean|Object} value Whether the inserter should be
+ * opened (true) or closed (false).
+ * To specify an insertion point,
+ * use an object.
+ * @param {string} value.rootClientId The root client ID to insert at.
+ * @param {number} value.insertionIndex The index to insert at.
+ *
+ * @return {Object} Action object.
+ */
+export function setIsInserterOpened( value ) {
+ return {
+ type: 'SET_IS_INSERTER_OPENED',
+ value,
+ };
+}
diff --git a/packages/customize-widgets/src/store/reducer.js b/packages/customize-widgets/src/store/reducer.js
index 8076cc26df9439..bfc2cbfdcc4366 100644
--- a/packages/customize-widgets/src/store/reducer.js
+++ b/packages/customize-widgets/src/store/reducer.js
@@ -25,6 +25,20 @@ const createWithInitialState = ( initialState ) => ( reducer ) => {
return ( state = initialState, action ) => reducer( state, action );
};
+/**
+ * Reducer tracking whether the inserter is open.
+ *
+ * @param {boolean|Object} state
+ * @param {Object} action
+ */
+function blockInserterPanel( state = false, action ) {
+ switch ( action.type ) {
+ case 'SET_IS_INSERTER_OPENED':
+ return action.value;
+ }
+ return state;
+}
+
/**
* Reducer returning the user preferences.
*
@@ -50,5 +64,6 @@ export const preferences = flow( [
} );
export default combineReducers( {
+ blockInserterPanel,
preferences,
} );
diff --git a/packages/customize-widgets/src/store/selectors.js b/packages/customize-widgets/src/store/selectors.js
index cc546ec6e5a7e8..855bb17780b62d 100644
--- a/packages/customize-widgets/src/store/selectors.js
+++ b/packages/customize-widgets/src/store/selectors.js
@@ -18,3 +18,26 @@ import { get } from 'lodash';
export function __unstableIsFeatureActive( state, feature ) {
return get( state.preferences.features, [ feature ], false );
}
+
+/**
+ * Returns true if the inserter is opened.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {boolean} Whether the inserter is opened.
+ */
+export function isInserterOpened( state ) {
+ return !! state.blockInserterPanel;
+}
+
+/**
+ * Get the insertion point for the inserter.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {Object} The root client ID and index to insert at.
+ */
+export function __experimentalGetInsertionPoint( state ) {
+ const { rootClientId, insertionIndex } = state.blockInserterPanel;
+ return { rootClientId, insertionIndex };
+}
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
index 014c139dbe7075..a3af4f5d0abab1 100644
--- a/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js
+++ b/packages/edit-widgets/src/hooks/use-widget-library-insertion-point.js
@@ -8,6 +8,7 @@ import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
+import { store as editWidgetsStore } from '../store';
import { buildWidgetAreasPostId, KIND, POST_TYPE } from '../store/utils';
const useWidgetLibraryInsertionPoint = () => {
@@ -31,6 +32,16 @@ const useWidgetLibraryInsertionPoint = () => {
getBlockIndex,
} = select( blockEditorStore );
+ const insertionPoint = select(
+ editWidgetsStore
+ ).__experimentalGetInsertionPoint();
+
+ // "Browse all" in the quick inserter will set the rootClientId to the current block.
+ // Otherwise, it will just be undefined, and we'll have to handle it differently below.
+ if ( insertionPoint.rootClientId ) {
+ return insertionPoint;
+ }
+
const clientId = getBlockSelectionEnd() || firstRootId;
const rootClientId = getBlockRootClientId( clientId );
diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js
index 3c72988e2157c8..9526ee6811a1fa 100644
--- a/packages/edit-widgets/src/store/selectors.js
+++ b/packages/edit-widgets/src/store/selectors.js
@@ -222,6 +222,18 @@ export function isInserterOpened( state ) {
return !! state.blockInserterPanel;
}
+/**
+ * Get the insertion point for the inserter.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {Object} The root client ID and index to insert at.
+ */
+export function __experimentalGetInsertionPoint( state ) {
+ const { rootClientId, insertionIndex } = state.blockInserterPanel;
+ return { rootClientId, insertionIndex };
+}
+
/**
* Returns true if a block can be inserted into a widget area.
*