diff --git a/packages/edit-navigation/src/components/header/manage-locations.js b/packages/edit-navigation/src/components/header/manage-locations.js
index 480917f901924c..f9d2e31f5b0804 100644
--- a/packages/edit-navigation/src/components/header/manage-locations.js
+++ b/packages/edit-navigation/src/components/header/manage-locations.js
@@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
-import useMenuLocations from './use-menu-locations';
+import useMenuLocations from '../../hooks/use-menu-locations';
export default function ManageLocations() {
const menus = useSelect( ( select ) => select( 'core' ).getMenus(), [] );
diff --git a/packages/edit-navigation/src/components/inspector-additions/index.js b/packages/edit-navigation/src/components/inspector-additions/index.js
index e9cc696bd7c1fd..50fddb1987785c 100644
--- a/packages/edit-navigation/src/components/inspector-additions/index.js
+++ b/packages/edit-navigation/src/components/inspector-additions/index.js
@@ -9,6 +9,9 @@ import { InspectorControls } from '@wordpress/block-editor';
*/
import AutoAddPagesPanel from './auto-add-pages-panel';
import DeleteMenuPanel from './delete-menu-panel';
+import { NameEditor } from '../name-editor';
+import { PanelBody } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
export default function InspectorAdditions( { menuId, onDeleteMenu } ) {
const selectedBlock = useSelect(
@@ -22,8 +25,11 @@ export default function InspectorAdditions( { menuId, onDeleteMenu } ) {
return (
-
-
+
+
+
+
+
);
}
diff --git a/packages/edit-navigation/src/components/layout/index.js b/packages/edit-navigation/src/components/layout/index.js
index d531ed458656e3..be6c0cd4f1a1c1 100644
--- a/packages/edit-navigation/src/components/layout/index.js
+++ b/packages/edit-navigation/src/components/layout/index.js
@@ -19,14 +19,19 @@ import {
BlockInspector,
__unstableUseBlockSelectionClearer as useBlockSelectionClearer,
} from '@wordpress/block-editor';
+import { useMemo, useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import EmptyState from './empty-state';
-import useNavigationEditor from './use-navigation-editor';
-import useNavigationBlockEditor from './use-navigation-block-editor';
-import useMenuNotifications from './use-menu-notifications';
+import {
+ IsMenuNameControlFocusedContext,
+ MenuIdContext,
+ useNavigationEditor,
+ useNavigationBlockEditor,
+ useMenuNotifications,
+} from '../../hooks';
import ErrorBoundary from '../error-boundary';
import NavigationEditorShortcuts from './shortcuts';
import Header from '../header';
@@ -37,7 +42,9 @@ import { store as editNavigationStore } from '../../store';
export default function Layout( { blockEditorSettings } ) {
const canvasRef = useBlockSelectionClearer();
-
+ const [ isMenuNameControlFocused, setIsMenuNameControlFocused ] = useState(
+ false
+ );
const { saveNavigationPost } = useDispatch( editNavigationStore );
const savePost = () => saveNavigationPost( navigationPost );
@@ -74,51 +81,65 @@ export default function Layout( { blockEditorSettings } ) {
'has-block-inspector': isBlockEditorReady,
} ) }
>
-
+
+ [
+ isMenuNameControlFocused,
+ setIsMenuNameControlFocused,
+ ],
+ [ isMenuNameControlFocused ]
+ ) }
+ >
+
- { ! hasFinishedInitialLoad && }
+ { ! hasFinishedInitialLoad && }
- { hasFinishedInitialLoad && ! hasMenus && (
-
- ) }
+ { hasFinishedInitialLoad && ! hasMenus && (
+
+ ) }
- { isBlockEditorReady && (
-
-
-
-
-
-
-
-
-
- ) }
+ { isBlockEditorReady && (
+
+
+
+
+
+
+
+
+
+ ) }
+
+
diff --git a/packages/edit-navigation/src/components/name-display/index.js b/packages/edit-navigation/src/components/name-display/index.js
new file mode 100644
index 00000000000000..1778f21651e865
--- /dev/null
+++ b/packages/edit-navigation/src/components/name-display/index.js
@@ -0,0 +1,37 @@
+/**
+ * WordPress dependencies
+ */
+import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
+import { BlockControls } from '@wordpress/block-editor';
+/**
+ * Internal dependencies
+ */
+import {
+ useSelectedMenuData,
+ IsMenuNameControlFocusedContext,
+} from '../../hooks';
+import { useContext } from '@wordpress/element';
+
+import { sprintf, __ } from '@wordpress/i18n';
+export default function NameDisplay() {
+ const { menuName } = useSelectedMenuData();
+ const [ , setIsMenuNameEditFocused ] = useContext(
+ IsMenuNameControlFocusedContext
+ );
+ return (
+
+
+ setIsMenuNameEditFocused( true ) }
+ >
+ { menuName }
+
+
+
+ );
+}
diff --git a/packages/edit-navigation/src/components/name-editor/index.js b/packages/edit-navigation/src/components/name-editor/index.js
new file mode 100644
index 00000000000000..b948ee893d23a1
--- /dev/null
+++ b/packages/edit-navigation/src/components/name-editor/index.js
@@ -0,0 +1,44 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useEffect, useRef, useContext } from '@wordpress/element';
+/**
+ * Internal dependencies
+ */
+import { TextControl } from '@wordpress/components';
+import {
+ IsMenuNameControlFocusedContext,
+ useMenuEntity,
+ useSelectedMenuData,
+} from '../../hooks';
+
+export function NameEditor() {
+ const [ isMenuNameEditFocused, setIsMenuNameEditFocused ] = useContext(
+ IsMenuNameControlFocusedContext
+ );
+
+ const { menuId } = useSelectedMenuData();
+ const { editMenuName, editedMenuName } = useMenuEntity( menuId );
+ const inputRef = useRef();
+ useEffect( () => {
+ if ( isMenuNameEditFocused ) inputRef.current.focus();
+ }, [ isMenuNameEditFocused ] );
+ return (
+ <>
+ setIsMenuNameEditFocused( false ) }
+ className="edit-navigation-name-editor__text-control"
+ value={ editedMenuName }
+ onChange={ ( value ) => {
+ editMenuName( value );
+ } }
+ />
+ >
+ );
+}
diff --git a/packages/edit-navigation/src/components/name-editor/style.scss b/packages/edit-navigation/src/components/name-editor/style.scss
new file mode 100644
index 00000000000000..206c60f92fb8bd
--- /dev/null
+++ b/packages/edit-navigation/src/components/name-editor/style.scss
@@ -0,0 +1,5 @@
+.edit-navigation-name-editor__text-control {
+ .components-base-control {
+ border: none;
+ }
+}
diff --git a/packages/edit-navigation/src/filters/add-menu-name-editor.js b/packages/edit-navigation/src/filters/add-menu-name-editor.js
new file mode 100644
index 00000000000000..e7c0e4210aeb62
--- /dev/null
+++ b/packages/edit-navigation/src/filters/add-menu-name-editor.js
@@ -0,0 +1,31 @@
+/**
+ * WordPress dependencies
+ */
+/**
+ * Internal dependencies
+ */
+import { addFilter } from '@wordpress/hooks';
+import { createHigherOrderComponent } from '@wordpress/compose';
+import NameDisplay from '../components/name-display';
+
+const addMenuNameEditor = createHigherOrderComponent(
+ ( BlockEdit ) => ( props ) => {
+ if ( props.name !== 'core/navigation' ) {
+ return ;
+ }
+ return (
+ <>
+
+
+ >
+ );
+ },
+ 'withMenuName'
+);
+
+export default () =>
+ addFilter(
+ 'editor.BlockEdit',
+ 'core/edit-navigation/with-menu-name',
+ addMenuNameEditor
+ );
diff --git a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js
new file mode 100644
index 00000000000000..c9a34fe168b0f1
--- /dev/null
+++ b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js
@@ -0,0 +1,22 @@
+/**
+ * WordPress dependencies
+ */
+import { addFilter } from '@wordpress/hooks';
+/**
+ * External dependencies
+ */
+import { set } from 'lodash';
+
+function disableInsertingNonNavigationBlocks( settings, name ) {
+ if ( ! [ 'core/navigation', 'core/navigation-link' ].includes( name ) ) {
+ set( settings, [ 'supports', 'inserter' ], false );
+ }
+ return settings;
+}
+
+export default () =>
+ addFilter(
+ 'blocks.registerBlockType',
+ 'core/edit-navigation/disable-inserting-non-navigation-blocks',
+ disableInsertingNonNavigationBlocks
+ );
diff --git a/packages/edit-navigation/src/filters/index.js b/packages/edit-navigation/src/filters/index.js
new file mode 100644
index 00000000000000..96315b46b7eb8d
--- /dev/null
+++ b/packages/edit-navigation/src/filters/index.js
@@ -0,0 +1,18 @@
+/**
+ * Internal dependencies
+ */
+import addMenuNameEditor from './add-menu-name-editor';
+import disableInsertingNonNavigationBlocks from './disable-inserting-non-navigation-blocks';
+import removeEditUnsupportedFeatures from './remove-edit-unsupported-features';
+import removeSettingsUnsupportedFeatures from './remove-settings-unsupported-features';
+
+export const addFilters = (
+ shouldAddDisableInsertingNonNavigationBlocksFilter
+) => {
+ addMenuNameEditor();
+ if ( shouldAddDisableInsertingNonNavigationBlocksFilter ) {
+ disableInsertingNonNavigationBlocks();
+ }
+ removeEditUnsupportedFeatures();
+ removeSettingsUnsupportedFeatures();
+};
diff --git a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js
new file mode 100644
index 00000000000000..a2074e2216501c
--- /dev/null
+++ b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js
@@ -0,0 +1,30 @@
+/**
+ * WordPress dependencies
+ */
+import { addFilter } from '@wordpress/hooks';
+import { createHigherOrderComponent } from '@wordpress/compose';
+
+const removeNavigationBlockEditUnsupportedFeatures = createHigherOrderComponent(
+ ( BlockEdit ) => ( props ) => {
+ if ( props.name !== 'core/navigation' ) {
+ return ;
+ }
+
+ return (
+
+ );
+ },
+ 'removeNavigationBlockEditUnsupportedFeatures'
+);
+
+export default () =>
+ addFilter(
+ 'editor.BlockEdit',
+ 'core/edit-navigation/remove-navigation-block-edit-unsupported-features',
+ removeNavigationBlockEditUnsupportedFeatures
+ );
diff --git a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js
new file mode 100644
index 00000000000000..26d62f9ec6448c
--- /dev/null
+++ b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js
@@ -0,0 +1,28 @@
+/**
+ * WordPress dependencies
+ */
+import { addFilter } from '@wordpress/hooks';
+
+function removeNavigationBlockSettingsUnsupportedFeatures( settings, name ) {
+ if ( name !== 'core/navigation' ) {
+ return settings;
+ }
+
+ return {
+ ...settings,
+ supports: {
+ customClassName: false,
+ html: false,
+ inserter: true,
+ },
+ // Remove any block variations.
+ variations: undefined,
+ };
+}
+
+export default () =>
+ addFilter(
+ 'blocks.registerBlockType',
+ 'core/edit-navigation/remove-navigation-block-settings-unsupported-features',
+ removeNavigationBlockSettingsUnsupportedFeatures
+ );
diff --git a/packages/edit-navigation/src/hooks/index.js b/packages/edit-navigation/src/hooks/index.js
new file mode 100644
index 00000000000000..5d2c4f94745657
--- /dev/null
+++ b/packages/edit-navigation/src/hooks/index.js
@@ -0,0 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { createContext } from '@wordpress/element';
+
+export const untitledMenu = __( '(untitled menu)' );
+export const MenuIdContext = createContext();
+export const IsMenuNameControlFocusedContext = createContext();
+
+export { default as useMenuEntity } from './use-menu-entity';
+export { default as useNavigationEditor } from './use-navigation-editor';
+export { default as useNavigationBlockEditor } from './use-navigation-block-editor';
+export { default as useMenuNotifications } from './use-menu-notifications';
+export { default as useSelectedMenuData } from './use-selected-menu-data';
+export { default as useMenuLocations } from './use-menu-locations';
diff --git a/packages/edit-navigation/src/hooks/use-menu-entity.js b/packages/edit-navigation/src/hooks/use-menu-entity.js
new file mode 100644
index 00000000000000..0b092f62a5d837
--- /dev/null
+++ b/packages/edit-navigation/src/hooks/use-menu-entity.js
@@ -0,0 +1,32 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect, useDispatch } from '@wordpress/data';
+/**
+ * Internal dependencies
+ */
+import { MENU_KIND, MENU_POST_TYPE } from '../utils/constants';
+
+import { untitledMenu } from './index';
+
+export default function useMenuEntity( menuId ) {
+ const { editEntityRecord } = useDispatch( 'core' );
+
+ const menuEntityData = [ MENU_KIND, MENU_POST_TYPE, menuId ];
+ const editedMenu = useSelect(
+ ( select ) =>
+ menuId &&
+ select( 'core' ).getEditedEntityRecord( ...menuEntityData ),
+ [ menuId ]
+ );
+
+ const editedMenuName = menuId && editedMenu.name;
+
+ const editMenuName = ( name = untitledMenu ) =>
+ editEntityRecord( ...menuEntityData, { name } );
+
+ return {
+ editedMenuName,
+ editMenuName,
+ };
+}
diff --git a/packages/edit-navigation/src/components/header/use-menu-locations.js b/packages/edit-navigation/src/hooks/use-menu-locations.js
similarity index 100%
rename from packages/edit-navigation/src/components/header/use-menu-locations.js
rename to packages/edit-navigation/src/hooks/use-menu-locations.js
diff --git a/packages/edit-navigation/src/components/layout/use-menu-notifications.js b/packages/edit-navigation/src/hooks/use-menu-notifications.js
similarity index 86%
rename from packages/edit-navigation/src/components/layout/use-menu-notifications.js
rename to packages/edit-navigation/src/hooks/use-menu-notifications.js
index 16e7076c0011d2..e85f33f164fdd8 100644
--- a/packages/edit-navigation/src/components/layout/use-menu-notifications.js
+++ b/packages/edit-navigation/src/hooks/use-menu-notifications.js
@@ -4,17 +4,21 @@
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
+/**
+ * Internal dependencies
+ */
+import { MENU_POST_TYPE, MENU_KIND } from '../utils/constants';
export default function useMenuNotifications( menuId ) {
const { lastSaveError, lastDeleteError } = useSelect(
( select ) => ( {
lastSaveError: select( 'core' ).getLastEntitySaveError(
- 'root',
- 'menu'
+ MENU_KIND,
+ MENU_POST_TYPE
),
lastDeleteError: select( 'core' ).getLastEntityDeleteError(
- 'root',
- 'menu',
+ MENU_KIND,
+ MENU_POST_TYPE,
menuId
),
} ),
diff --git a/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js b/packages/edit-navigation/src/hooks/use-navigation-block-editor.js
similarity index 58%
rename from packages/edit-navigation/src/components/layout/use-navigation-block-editor.js
rename to packages/edit-navigation/src/hooks/use-navigation-block-editor.js
index 902e77ac22bbd4..855bd92b758705 100644
--- a/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js
+++ b/packages/edit-navigation/src/hooks/use-navigation-block-editor.js
@@ -8,24 +8,29 @@ import { useEntityBlockEditor } from '@wordpress/core-data';
/**
* Internal dependencies
*/
-import { KIND, POST_TYPE } from '../../store/utils';
-import { store as editNavigationStore } from '../../store';
+import {
+ NAVIGATION_POST_KIND,
+ NAVIGATION_POST_POST_TYPE,
+} from '../utils/constants';
+import { store as editNavigationStore } from '../store';
export default function useNavigationBlockEditor( post ) {
const { createMissingMenuItems } = useDispatch( editNavigationStore );
- const [ blocks, onInput, _onChange ] = useEntityBlockEditor(
- KIND,
- POST_TYPE,
- { id: post?.id }
+ const [ blocks, onInput, onEntityChange ] = useEntityBlockEditor(
+ NAVIGATION_POST_KIND,
+ NAVIGATION_POST_POST_TYPE,
+ {
+ id: post?.id,
+ }
);
const onChange = useCallback(
async ( ...args ) => {
- await _onChange( ...args );
+ await onEntityChange( ...args );
createMissingMenuItems( post );
},
- [ _onChange, post ]
+ [ onEntityChange, post ]
);
return [ blocks, onInput, onChange ];
diff --git a/packages/edit-navigation/src/components/layout/use-navigation-editor.js b/packages/edit-navigation/src/hooks/use-navigation-editor.js
similarity index 74%
rename from packages/edit-navigation/src/components/layout/use-navigation-editor.js
rename to packages/edit-navigation/src/hooks/use-navigation-editor.js
index e6439e804062bb..3af9a2b1003860 100644
--- a/packages/edit-navigation/src/components/layout/use-navigation-editor.js
+++ b/packages/edit-navigation/src/hooks/use-navigation-editor.js
@@ -3,35 +3,35 @@
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useState, useEffect } from '@wordpress/element';
-
/**
* Internal dependencies
*/
-import { store as editNavigationStore } from '../../store';
+import { store as editNavigationStore } from '../store';
+const getMenusData = ( select ) => {
+ const selectors = select( 'core' );
+ const params = { per_page: -1 };
+ return {
+ menus: selectors.getMenus( params ),
+ hasLoadedMenus: selectors.hasFinishedResolution( 'getMenus', [
+ params,
+ ] ),
+ };
+};
export default function useNavigationEditor() {
+ const { deleteMenu: _deleteMenu } = useDispatch( 'core' );
+ const [ selectedMenuId, setSelectedMenuId ] = useState( null );
const [ hasFinishedInitialLoad, setHasFinishedInitialLoad ] = useState(
false
);
- const { menus, hasLoadedMenus } = useSelect( ( select ) => {
- const selectors = select( 'core' );
- const params = { per_page: -1 };
- return {
- menus: selectors.getMenus( params ),
- hasLoadedMenus: selectors.hasFinishedResolution( 'getMenus', [
- params,
- ] ),
- };
- }, [] );
+ const { menus, hasLoadedMenus } = useSelect( getMenusData, [] );
useEffect( () => {
if ( hasLoadedMenus ) {
setHasFinishedInitialLoad( true );
}
}, [ hasLoadedMenus ] );
- const [ selectedMenuId, setSelectedMenuId ] = useState( null );
-
useEffect( () => {
if ( ! selectedMenuId && menus?.length ) {
setSelectedMenuId( menus[ 0 ].id );
@@ -50,12 +50,6 @@ export default function useNavigationEditor() {
[ selectedMenuId ]
);
- const selectMenu = ( menuId ) => {
- setSelectedMenuId( menuId );
- };
-
- const { deleteMenu: _deleteMenu } = useDispatch( 'core' );
-
const deleteMenu = async () => {
const didDeleteMenu = await _deleteMenu( selectedMenuId, {
force: true,
@@ -67,11 +61,11 @@ export default function useNavigationEditor() {
return {
menus,
- hasLoadedMenus,
- hasFinishedInitialLoad,
selectedMenuId,
navigationPost,
- selectMenu,
+ selectMenu: setSelectedMenuId,
deleteMenu,
+ hasFinishedInitialLoad,
+ hasLoadedMenus,
};
}
diff --git a/packages/edit-navigation/src/hooks/use-selected-menu-data.js b/packages/edit-navigation/src/hooks/use-selected-menu-data.js
new file mode 100644
index 00000000000000..7947bd49699659
--- /dev/null
+++ b/packages/edit-navigation/src/hooks/use-selected-menu-data.js
@@ -0,0 +1,22 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { useContext } from '@wordpress/element';
+/**
+ * Internal dependencies
+ */
+import { MenuIdContext, untitledMenu } from './index';
+
+export default function useSelectedMenuData() {
+ const menuId = useContext( MenuIdContext );
+ const menu = useSelect( ( select ) => select( 'core' ).getMenu( menuId ), [
+ menuId,
+ ] );
+ const menuName = menu?.name ?? untitledMenu;
+ return {
+ menuId,
+ menu,
+ menuName,
+ };
+}
diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js
index ef67e940a67599..4744f8b29d6f5e 100644
--- a/packages/edit-navigation/src/index.js
+++ b/packages/edit-navigation/src/index.js
@@ -1,8 +1,3 @@
-/**
- * External dependencies
- */
-import { map, set, flatten, partialRight } from 'lodash';
-
/**
* WordPress dependencies
*/
@@ -11,167 +6,28 @@ import {
__experimentalRegisterExperimentalCoreBlocks,
} from '@wordpress/block-library';
import { render } from '@wordpress/element';
-import { createHigherOrderComponent } from '@wordpress/compose';
-import { __ } from '@wordpress/i18n';
-import apiFetch from '@wordpress/api-fetch';
-import { addQueryArgs } from '@wordpress/url';
-import { decodeEntities } from '@wordpress/html-entities';
-import { addFilter } from '@wordpress/hooks';
-
/**
* Internal dependencies
*/
-import Layout from './components/layout';
-import './store';
-
-function disableInsertingNonNavigationBlocks( settings, name ) {
- if ( ! [ 'core/navigation', 'core/navigation-link' ].includes( name ) ) {
- set( settings, [ 'supports', 'inserter' ], false );
- }
- return settings;
-}
-
-function removeNavigationBlockSettingsUnsupportedFeatures( settings, name ) {
- if ( name !== 'core/navigation' ) {
- return settings;
- }
-
- return {
- ...settings,
- supports: {
- customClassName: false,
- html: false,
- inserter: true,
- },
- // Remove any block variations.
- variations: undefined,
- };
-}
-
-const removeNavigationBlockEditUnsupportedFeatures = createHigherOrderComponent(
- ( BlockEdit ) => ( props ) => {
- if ( props.name !== 'core/navigation' ) {
- return ;
- }
-
- return (
-
- );
- },
- 'removeNavigationBlockEditUnsupportedFeatures'
-);
+import { addFilters } from './filters';
+import fetchLinkSuggestions from './utils/fetch-link-suggestions';
/**
- * Fetches link suggestions from the API. This function is an exact copy of a function found at:
- *
- * packages/editor/src/components/provider/index.js
- *
- * It seems like there is no suitable package to import this from. Ideally it would be either part of core-data.
- * Until we refactor it, just copying the code is the simplest solution.
- *
- * @param {string} search
- * @param {Object} [searchArguments]
- * @param {number} [searchArguments.isInitialSuggestions]
- * @param {number} [searchArguments.type]
- * @param {number} [searchArguments.subtype]
- * @param {Object} [editorSettings]
- * @param {boolean} [editorSettings.disablePostFormats=false]
- * @return {Promise