From 289bf68962bb0856c41f1d9d74a69a8959032a01 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 11:00:49 +0000
Subject: [PATCH 01/21] Initial plan
From 75494b4a1aa07afdc141903e34309409a0d13a31 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 11:12:07 +0000
Subject: [PATCH 02/21] Add page creator appender to Page List block
Co-authored-by: scruffian <275961+scruffian@users.noreply.github.com>
---
packages/block-library/src/page-list/edit.js | 12 +-
.../block-library/src/page-list/editor.scss | 12 ++
.../src/page-list/page-creator-appender.js | 184 ++++++++++++++++++
3 files changed, 207 insertions(+), 1 deletion(-)
create mode 100644 packages/block-library/src/page-list/page-creator-appender.js
diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js
index cd665bb4434972..e85acca6f17eba 100644
--- a/packages/block-library/src/page-list/edit.js
+++ b/packages/block-library/src/page-list/edit.js
@@ -39,6 +39,7 @@ import {
ConvertToLinksModal,
} from './convert-to-links-modal';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
+import { PageCreatorAppender } from './page-creator-appender';
// We only show the edit option when page count is <= MAX_PAGE_COUNT
// Performance of Navigation Links is not good past this value.
@@ -259,12 +260,14 @@ export default function PageListEdit( {
parentClientId,
hasDraggedChild,
isChildOfNavigation,
+ isSelected,
} = useSelect(
( select ) => {
const {
getBlockParentsByBlockName,
hasSelectedInnerBlock,
hasDraggedInnerBlock,
+ getSelectedBlockClientId,
} = select( blockEditorStore );
const blockParents = getBlockParentsByBlockName(
clientId,
@@ -282,6 +285,7 @@ export default function PageListEdit( {
hasSelectedChild: hasSelectedInnerBlock( clientId, true ),
hasDraggedChild: hasDraggedInnerBlock( clientId, true ),
parentClientId: navigationBlockParents[ 0 ],
+ isSelected: getSelectedBlockClientId() === clientId,
};
},
[ clientId ]
@@ -294,8 +298,14 @@ export default function PageListEdit( {
parentPageID,
} );
+ // Create a custom appender that allows creating new pages
+ const customAppender = () => (
+
+ );
+
const innerBlocksProps = useInnerBlocksProps( blockProps, {
- renderAppender: false,
+ renderAppender:
+ isChildOfNavigation && isSelected ? customAppender : false,
__unstableDisableDropZone: true,
templateLock: isChildOfNavigation ? false : 'all',
onInput: NOOP,
diff --git a/packages/block-library/src/page-list/editor.scss b/packages/block-library/src/page-list/editor.scss
index 2813dc1675b8ea..0a99f35b62a673 100644
--- a/packages/block-library/src/page-list/editor.scss
+++ b/packages/block-library/src/page-list/editor.scss
@@ -61,3 +61,15 @@
.wp-block-page-list__loading-indicator-container {
padding: $grid-unit-10 $grid-unit-15;
}
+
+// Page creator appender styling
+.page-list-appender {
+ display: flex;
+ gap: $grid-unit-10;
+ align-items: center;
+ padding: $grid-unit-10;
+
+ &__button {
+ flex-shrink: 0;
+ }
+}
diff --git a/packages/block-library/src/page-list/page-creator-appender.js b/packages/block-library/src/page-list/page-creator-appender.js
new file mode 100644
index 00000000000000..19bb88a1770cbf
--- /dev/null
+++ b/packages/block-library/src/page-list/page-creator-appender.js
@@ -0,0 +1,184 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ Button,
+ TextControl,
+ Notice,
+ Modal,
+ __experimentalVStack as VStack,
+ __experimentalHStack as HStack,
+} from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
+import { store as noticesStore } from '@wordpress/notices';
+import { decodeEntities } from '@wordpress/html-entities';
+import { useState } from '@wordpress/element';
+import { plus } from '@wordpress/icons';
+import { InnerBlocks } from '@wordpress/block-editor';
+
+/**
+ * Component for creating new pages from within the Page List block.
+ *
+ * @param {Object} props Component props.
+ * @param {number} props.parentPageID The parent page ID for the new page.
+ */
+export function PageCreatorAppender( { parentPageID } ) {
+ const [ isCreating, setIsCreating ] = useState( false );
+ const [ title, setTitle ] = useState( '' );
+
+ // Check if the title is valid for submission
+ const isTitleValid = title.trim().length > 0;
+
+ // Get the last created entity record (without ID) to track creation state
+ const { lastError, isSaving } = useSelect(
+ ( select ) => ( {
+ lastError: select( coreStore ).getLastEntitySaveError(
+ 'postType',
+ 'page'
+ ),
+ isSaving: select( coreStore ).isSavingEntityRecord(
+ 'postType',
+ 'page'
+ ),
+ } ),
+ []
+ );
+
+ const { saveEntityRecord } = useDispatch( coreStore );
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+
+ async function createPage( event ) {
+ event.preventDefault();
+ if ( isSaving || ! isTitleValid ) {
+ return;
+ }
+
+ try {
+ const pageData = {
+ title,
+ status: 'publish',
+ };
+
+ // If there's a parent page ID, set it
+ if ( parentPageID ) {
+ pageData.parent = parentPageID;
+ }
+
+ const savedRecord = await saveEntityRecord(
+ 'postType',
+ 'page',
+ pageData,
+ { throwOnError: true }
+ );
+
+ if ( savedRecord ) {
+ // Show success notice
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: the title of the new page.
+ __( 'Page "%s" created successfully.' ),
+ decodeEntities( savedRecord.title.rendered )
+ ),
+ {
+ type: 'snackbar',
+ id: 'page-created-success',
+ }
+ );
+
+ // Reset the form
+ setTitle( '' );
+ setIsCreating( false );
+ }
+ } catch ( error ) {
+ // Show error notice
+ createErrorNotice(
+ __( 'Failed to create page. Please try again.' ),
+ {
+ type: 'snackbar',
+ id: 'page-created-error',
+ }
+ );
+ }
+ }
+
+ const isSubmitDisabled = isSaving || ! isTitleValid;
+
+ return (
+ <>
+
+
+
+
+
+ { isCreating && (
+ {
+ setIsCreating( false );
+ setTitle( '' );
+ } }
+ size="small"
+ >
+
+
+ ) }
+ >
+ );
+}
From 6152589fb39728f44a49b7507e3f44b4173b7c10 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:00:23 +0000
Subject: [PATCH 03/21] Refactor: hijack block appender instead of creating
separate component
Co-authored-by: scruffian <275961+scruffian@users.noreply.github.com>
---
packages/block-library/src/page-list/edit.js | 170 +++++++++++++++-
.../block-library/src/page-list/editor.scss | 12 --
.../src/page-list/page-creator-appender.js | 184 ------------------
3 files changed, 166 insertions(+), 200 deletions(-)
delete mode 100644 packages/block-library/src/page-list/page-creator-appender.js
diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js
index e85acca6f17eba..cb5194399b2981 100644
--- a/packages/block-library/src/page-list/edit.js
+++ b/packages/block-library/src/page-list/edit.js
@@ -22,13 +22,20 @@ import {
Notice,
ComboboxControl,
Button,
+ Modal,
+ TextControl,
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
+ __experimentalVStack as VStack,
+ __experimentalHStack as HStack,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { useMemo, useState, useEffect, useCallback } from '@wordpress/element';
-import { useEntityRecords } from '@wordpress/core-data';
+import { useEntityRecords, store as coreStore } from '@wordpress/core-data';
import { useSelect, useDispatch } from '@wordpress/data';
+import { store as noticesStore } from '@wordpress/notices';
+import { decodeEntities } from '@wordpress/html-entities';
+import { Icon, plus } from '@wordpress/icons';
/**
* Internal dependencies
@@ -39,7 +46,6 @@ import {
ConvertToLinksModal,
} from './convert-to-links-modal';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
-import { PageCreatorAppender } from './page-creator-appender';
// We only show the edit option when page count is <= MAX_PAGE_COUNT
// Performance of Navigation Links is not good past this value.
@@ -128,6 +134,10 @@ export default function PageListEdit( {
const closeModal = () => setOpen( false );
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
+ // Page creation state
+ const [ isCreatingPage, setIsCreatingPage ] = useState( false );
+ const [ pageTitle, setPageTitle ] = useState( '' );
+
const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords(
'postType',
'page',
@@ -142,6 +152,92 @@ export default function PageListEdit( {
}
);
+ // Page creation helpers
+ const { lastError, isSaving } = useSelect(
+ ( select ) => ( {
+ lastError: select( coreStore ).getLastEntitySaveError(
+ 'postType',
+ 'page'
+ ),
+ isSaving: select( coreStore ).isSavingEntityRecord(
+ 'postType',
+ 'page'
+ ),
+ } ),
+ []
+ );
+
+ const { saveEntityRecord } = useDispatch( coreStore );
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+
+ const isTitleValid = pageTitle.trim().length > 0;
+
+ const createPage = useCallback(
+ async ( event ) => {
+ event.preventDefault();
+ if ( isSaving || ! isTitleValid ) {
+ return;
+ }
+
+ try {
+ const pageData = {
+ title: pageTitle,
+ status: 'publish',
+ };
+
+ // If there's a parent page ID, set it
+ if ( parentPageID ) {
+ pageData.parent = parentPageID;
+ }
+
+ const savedRecord = await saveEntityRecord(
+ 'postType',
+ 'page',
+ pageData,
+ { throwOnError: true }
+ );
+
+ if ( savedRecord ) {
+ // Show success notice
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: the title of the new page.
+ __( 'Page "%s" created successfully.' ),
+ decodeEntities( savedRecord.title.rendered )
+ ),
+ {
+ type: 'snackbar',
+ id: 'page-created-success',
+ }
+ );
+
+ // Reset the form
+ setPageTitle( '' );
+ setIsCreatingPage( false );
+ }
+ } catch ( error ) {
+ // Show error notice
+ createErrorNotice(
+ __( 'Failed to create page. Please try again.' ),
+ {
+ type: 'snackbar',
+ id: 'page-created-error',
+ }
+ );
+ }
+ },
+ [
+ isSaving,
+ isTitleValid,
+ pageTitle,
+ parentPageID,
+ saveEntityRecord,
+ createSuccessNotice,
+ createErrorNotice,
+ ]
+ );
+
const allowConvertToLinks =
'showSubmenuIcon' in context &&
pages?.length > 0 &&
@@ -298,9 +394,17 @@ export default function PageListEdit( {
parentPageID,
} );
- // Create a custom appender that allows creating new pages
+ // Create a custom appender that hijacks the block appender to create pages
const customAppender = () => (
-
+
);
const innerBlocksProps = useInnerBlocksProps( blockProps, {
@@ -414,6 +518,64 @@ export default function PageListEdit( {
pages={ pages }
parentPageID={ parentPageID }
/>
+ { isCreatingPage && (
+ {
+ setIsCreatingPage( false );
+ setPageTitle( '' );
+ } }
+ size="small"
+ >
+
+
+ ) }
>
);
}
diff --git a/packages/block-library/src/page-list/editor.scss b/packages/block-library/src/page-list/editor.scss
index 0a99f35b62a673..2813dc1675b8ea 100644
--- a/packages/block-library/src/page-list/editor.scss
+++ b/packages/block-library/src/page-list/editor.scss
@@ -61,15 +61,3 @@
.wp-block-page-list__loading-indicator-container {
padding: $grid-unit-10 $grid-unit-15;
}
-
-// Page creator appender styling
-.page-list-appender {
- display: flex;
- gap: $grid-unit-10;
- align-items: center;
- padding: $grid-unit-10;
-
- &__button {
- flex-shrink: 0;
- }
-}
diff --git a/packages/block-library/src/page-list/page-creator-appender.js b/packages/block-library/src/page-list/page-creator-appender.js
deleted file mode 100644
index 19bb88a1770cbf..00000000000000
--- a/packages/block-library/src/page-list/page-creator-appender.js
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- Button,
- TextControl,
- Notice,
- Modal,
- __experimentalVStack as VStack,
- __experimentalHStack as HStack,
-} from '@wordpress/components';
-import { __, sprintf } from '@wordpress/i18n';
-import { useSelect, useDispatch } from '@wordpress/data';
-import { store as coreStore } from '@wordpress/core-data';
-import { store as noticesStore } from '@wordpress/notices';
-import { decodeEntities } from '@wordpress/html-entities';
-import { useState } from '@wordpress/element';
-import { plus } from '@wordpress/icons';
-import { InnerBlocks } from '@wordpress/block-editor';
-
-/**
- * Component for creating new pages from within the Page List block.
- *
- * @param {Object} props Component props.
- * @param {number} props.parentPageID The parent page ID for the new page.
- */
-export function PageCreatorAppender( { parentPageID } ) {
- const [ isCreating, setIsCreating ] = useState( false );
- const [ title, setTitle ] = useState( '' );
-
- // Check if the title is valid for submission
- const isTitleValid = title.trim().length > 0;
-
- // Get the last created entity record (without ID) to track creation state
- const { lastError, isSaving } = useSelect(
- ( select ) => ( {
- lastError: select( coreStore ).getLastEntitySaveError(
- 'postType',
- 'page'
- ),
- isSaving: select( coreStore ).isSavingEntityRecord(
- 'postType',
- 'page'
- ),
- } ),
- []
- );
-
- const { saveEntityRecord } = useDispatch( coreStore );
- const { createSuccessNotice, createErrorNotice } =
- useDispatch( noticesStore );
-
- async function createPage( event ) {
- event.preventDefault();
- if ( isSaving || ! isTitleValid ) {
- return;
- }
-
- try {
- const pageData = {
- title,
- status: 'publish',
- };
-
- // If there's a parent page ID, set it
- if ( parentPageID ) {
- pageData.parent = parentPageID;
- }
-
- const savedRecord = await saveEntityRecord(
- 'postType',
- 'page',
- pageData,
- { throwOnError: true }
- );
-
- if ( savedRecord ) {
- // Show success notice
- createSuccessNotice(
- sprintf(
- // translators: %s: the title of the new page.
- __( 'Page "%s" created successfully.' ),
- decodeEntities( savedRecord.title.rendered )
- ),
- {
- type: 'snackbar',
- id: 'page-created-success',
- }
- );
-
- // Reset the form
- setTitle( '' );
- setIsCreating( false );
- }
- } catch ( error ) {
- // Show error notice
- createErrorNotice(
- __( 'Failed to create page. Please try again.' ),
- {
- type: 'snackbar',
- id: 'page-created-error',
- }
- );
- }
- }
-
- const isSubmitDisabled = isSaving || ! isTitleValid;
-
- return (
- <>
-
-
-
-
-
- { isCreating && (
- {
- setIsCreating( false );
- setTitle( '' );
- } }
- size="small"
- >
-
-
- ) }
- >
- );
-}
From 54fd9e136f4913f1b8026e77ac414685d0e35d86 Mon Sep 17 00:00:00 2001
From: Ben Dwyer
Date: Fri, 27 Feb 2026 13:22:57 +0000
Subject: [PATCH 04/21] Replace Page List custom modal with reusable
PageCreatorAppender
Use the existing LinkUIPageCreator from the navigation-link block
instead of a custom Modal for creating pages. Extract a shared
PageCreatorAppender component that handles menu_order and parent
page context. Show the appender when the Page List block or its
children are selected, but not when inside a Navigation block.
Co-Authored-By: Claude Opus 4.6
---
.../navigation-link/link-ui/page-creator.js | 13 +-
packages/block-library/src/page-list/edit.js | 224 +++---------------
.../src/page-list/page-creator-appender.js | 76 ++++++
3 files changed, 117 insertions(+), 196 deletions(-)
create mode 100644 packages/block-library/src/page-list/page-creator-appender.js
diff --git a/packages/block-library/src/navigation-link/link-ui/page-creator.js b/packages/block-library/src/navigation-link/link-ui/page-creator.js
index 30d1799f190979..33631348628ec2 100644
--- a/packages/block-library/src/navigation-link/link-ui/page-creator.js
+++ b/packages/block-library/src/navigation-link/link-ui/page-creator.js
@@ -24,17 +24,19 @@ import DialogWrapper from './dialog-wrapper';
/**
* Component for creating new pages within the Navigation Link UI.
*
- * @param {Object} props Component props.
- * @param {string} props.postType The post type to create.
- * @param {Function} props.onBack Callback when user wants to go back.
- * @param {Function} props.onPageCreated Callback when page is successfully created.
- * @param {string} [props.initialTitle] Initial title to pre-fill the form.
+ * @param {Object} props Component props.
+ * @param {string} props.postType The post type to create.
+ * @param {Function} props.onBack Callback when user wants to go back.
+ * @param {Function} props.onPageCreated Callback when page is successfully created.
+ * @param {string} [props.initialTitle] Initial title to pre-fill the form.
+ * @param {Object} [props.additionalData] Additional data to include when saving the entity record.
*/
export function LinkUIPageCreator( {
postType,
onBack,
onPageCreated,
initialTitle = '',
+ additionalData = {},
} ) {
const [ title, setTitle ] = useState( initialTitle );
const [ shouldPublish, setShouldPublish ] = useState( true );
@@ -74,6 +76,7 @@ export function LinkUIPageCreator( {
{
title,
status: shouldPublish ? 'publish' : 'draft',
+ ...additionalData,
},
{ throwOnError: true }
);
diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js
index cb5194399b2981..0364e15fd8491c 100644
--- a/packages/block-library/src/page-list/edit.js
+++ b/packages/block-library/src/page-list/edit.js
@@ -22,20 +22,13 @@ import {
Notice,
ComboboxControl,
Button,
- Modal,
- TextControl,
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
- __experimentalVStack as VStack,
- __experimentalHStack as HStack,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { useMemo, useState, useEffect, useCallback } from '@wordpress/element';
-import { useEntityRecords, store as coreStore } from '@wordpress/core-data';
-import { useSelect, useDispatch } from '@wordpress/data';
-import { store as noticesStore } from '@wordpress/notices';
-import { decodeEntities } from '@wordpress/html-entities';
-import { Icon, plus } from '@wordpress/icons';
+import { useEntityRecords } from '@wordpress/core-data';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -46,11 +39,15 @@ import {
ConvertToLinksModal,
} from './convert-to-links-modal';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
+import PageCreatorAppender, {
+ PageCreatorContext,
+} from './page-creator-appender';
// We only show the edit option when page count is <= MAX_PAGE_COUNT
// Performance of Navigation Links is not good past this value.
const MAX_PAGE_COUNT = 100;
const NOOP = () => {};
+
function BlockContent( {
blockProps,
innerBlocksProps,
@@ -134,10 +131,6 @@ export default function PageListEdit( {
const closeModal = () => setOpen( false );
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
- // Page creation state
- const [ isCreatingPage, setIsCreatingPage ] = useState( false );
- const [ pageTitle, setPageTitle ] = useState( '' );
-
const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords(
'postType',
'page',
@@ -152,92 +145,6 @@ export default function PageListEdit( {
}
);
- // Page creation helpers
- const { lastError, isSaving } = useSelect(
- ( select ) => ( {
- lastError: select( coreStore ).getLastEntitySaveError(
- 'postType',
- 'page'
- ),
- isSaving: select( coreStore ).isSavingEntityRecord(
- 'postType',
- 'page'
- ),
- } ),
- []
- );
-
- const { saveEntityRecord } = useDispatch( coreStore );
- const { createSuccessNotice, createErrorNotice } =
- useDispatch( noticesStore );
-
- const isTitleValid = pageTitle.trim().length > 0;
-
- const createPage = useCallback(
- async ( event ) => {
- event.preventDefault();
- if ( isSaving || ! isTitleValid ) {
- return;
- }
-
- try {
- const pageData = {
- title: pageTitle,
- status: 'publish',
- };
-
- // If there's a parent page ID, set it
- if ( parentPageID ) {
- pageData.parent = parentPageID;
- }
-
- const savedRecord = await saveEntityRecord(
- 'postType',
- 'page',
- pageData,
- { throwOnError: true }
- );
-
- if ( savedRecord ) {
- // Show success notice
- createSuccessNotice(
- sprintf(
- // translators: %s: the title of the new page.
- __( 'Page "%s" created successfully.' ),
- decodeEntities( savedRecord.title.rendered )
- ),
- {
- type: 'snackbar',
- id: 'page-created-success',
- }
- );
-
- // Reset the form
- setPageTitle( '' );
- setIsCreatingPage( false );
- }
- } catch ( error ) {
- // Show error notice
- createErrorNotice(
- __( 'Failed to create page. Please try again.' ),
- {
- type: 'snackbar',
- id: 'page-created-error',
- }
- );
- }
- },
- [
- isSaving,
- isTitleValid,
- pageTitle,
- parentPageID,
- saveEntityRecord,
- createSuccessNotice,
- createErrorNotice,
- ]
- );
-
const allowConvertToLinks =
'showSubmenuIcon' in context &&
pages?.length > 0 &&
@@ -354,7 +261,6 @@ export default function PageListEdit( {
isNested,
hasSelectedChild,
parentClientId,
- hasDraggedChild,
isChildOfNavigation,
isSelected,
} = useSelect(
@@ -362,7 +268,6 @@ export default function PageListEdit( {
const {
getBlockParentsByBlockName,
hasSelectedInnerBlock,
- hasDraggedInnerBlock,
getSelectedBlockClientId,
} = select( blockEditorStore );
const blockParents = getBlockParentsByBlockName(
@@ -379,7 +284,6 @@ export default function PageListEdit( {
isNested: blockParents.length > 0,
isChildOfNavigation: navigationBlockParents.length > 0,
hasSelectedChild: hasSelectedInnerBlock( clientId, true ),
- hasDraggedChild: hasDraggedInnerBlock( clientId, true ),
parentClientId: navigationBlockParents[ 0 ],
isSelected: getSelectedBlockClientId() === clientId,
};
@@ -394,50 +298,46 @@ export default function PageListEdit( {
parentPageID,
} );
- // Create a custom appender that hijacks the block appender to create pages
- const customAppender = () => (
-
+ const showAppender =
+ ! isChildOfNavigation && ( isSelected || hasSelectedChild );
+
+ const nextMenuOrder = useMemo( () => {
+ if ( ! pages?.length ) {
+ return 0;
+ }
+ return (
+ pages.reduce(
+ ( max, page ) => Math.max( max, page.menu_order ),
+ 0
+ ) + 1
+ );
+ }, [ pages ] );
+
+ const additionalPageData = useMemo(
+ () => ( {
+ menu_order: nextMenuOrder,
+ ...( parentPageID ? { parent: parentPageID } : {} ),
+ } ),
+ [ nextMenuOrder, parentPageID ]
);
const innerBlocksProps = useInnerBlocksProps( blockProps, {
- renderAppender:
- isChildOfNavigation && isSelected ? customAppender : false,
+ renderAppender: showAppender ? PageCreatorAppender : false,
__unstableDisableDropZone: true,
- templateLock: isChildOfNavigation ? false : 'all',
+ templateLock: showAppender ? false : 'all',
onInput: NOOP,
onChange: NOOP,
value: blockList,
} );
- const { selectBlock } = useDispatch( blockEditorStore );
-
- useEffect( () => {
- if ( hasSelectedChild || hasDraggedChild ) {
- openModal();
- selectBlock( parentClientId );
- }
- }, [
- hasSelectedChild,
- hasDraggedChild,
- parentClientId,
- selectBlock,
- openModal,
- ] );
-
useEffect( () => {
setAttributes( { isNested } );
}, [ isNested, setAttributes ] );
return (
- <>
+
{ ( pagesTree.length > 0 || allowConvertToLinks ) && (
- { isCreatingPage && (
- {
- setIsCreatingPage( false );
- setPageTitle( '' );
- } }
- size="small"
- >
-
-
- ) }
- >
+
);
}
diff --git a/packages/block-library/src/page-list/page-creator-appender.js b/packages/block-library/src/page-list/page-creator-appender.js
new file mode 100644
index 00000000000000..85cb1da2db71d5
--- /dev/null
+++ b/packages/block-library/src/page-list/page-creator-appender.js
@@ -0,0 +1,76 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ createContext,
+ useContext,
+ useState,
+ useMemo,
+ useRef,
+} from '@wordpress/element';
+import { Button, Popover } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { Icon, plus } from '@wordpress/icons';
+import { useEntityRecords } from '@wordpress/core-data';
+
+/**
+ * Internal dependencies
+ */
+import { LinkUIPageCreator } from '../navigation-link/link-ui/page-creator';
+
+const MAX_PAGE_COUNT = 100;
+
+export const PageCreatorContext = createContext( null );
+
+export default function PageCreatorAppender() {
+ const [ showCreator, setShowCreator ] = useState( false );
+ const ref = useRef();
+ const contextData = useContext( PageCreatorContext );
+
+ const { records: pages } = useEntityRecords( 'postType', 'page', {
+ per_page: MAX_PAGE_COUNT,
+ _fields: [ 'id', 'link', 'menu_order', 'parent', 'title', 'type' ],
+ orderby: 'menu_order',
+ order: 'asc',
+ } );
+
+ const additionalData = useMemo( () => {
+ // If context provides data (e.g., from Page List with parentPageID),
+ // use it directly.
+ if ( contextData?.additionalData ) {
+ return contextData.additionalData;
+ }
+ // Otherwise compute menu_order from pages.
+ const maxMenuOrder =
+ pages?.reduce(
+ ( max, page ) => Math.max( max, page.menu_order ),
+ 0
+ ) ?? 0;
+ return { menu_order: maxMenuOrder + 1 };
+ }, [ contextData, pages ] );
+
+ return (
+ <>
+
+ { showCreator && (
+
+ setShowCreator( false ) }
+ onPageCreated={ () => setShowCreator( false ) }
+ additionalData={ additionalData }
+ />
+
+ ) }
+ >
+ );
+}
From 4bb5e6ea6eaea355595030208ce41ccc8a30259e Mon Sep 17 00:00:00 2001
From: Ben Dwyer
Date: Fri, 27 Feb 2026 14:01:04 +0000
Subject: [PATCH 05/21] Address review feedback on Page List page creator
appender
- Replace generic additionalData with explicit menuOrder and parent props
- Remove redundant useEntityRecords query from PageCreatorAppender
- Make DialogWrapper back button conditional (only when onBack provided)
- Add onClose prop to LinkUIPageCreator for dismissing without back nav
- Revert templateLock to trunk behavior to prevent page reordering
- Render appender inside block root element alongside inner blocks children
Co-Authored-By: Claude Opus 4.6
---
.../navigation-link/link-ui/dialog-wrapper.js | 7 ++++-
.../navigation-link/link-ui/page-creator.js | 27 ++++++++++------
packages/block-library/src/page-list/edit.js | 22 ++++++++-----
.../src/page-list/page-creator-appender.js | 31 ++-----------------
4 files changed, 41 insertions(+), 46 deletions(-)
diff --git a/packages/block-library/src/navigation-link/link-ui/dialog-wrapper.js b/packages/block-library/src/navigation-link/link-ui/dialog-wrapper.js
index e37166a1dfc7c2..c470ac78de037a 100644
--- a/packages/block-library/src/navigation-link/link-ui/dialog-wrapper.js
+++ b/packages/block-library/src/navigation-link/link-ui/dialog-wrapper.js
@@ -64,7 +64,12 @@ function DialogWrapper( { className, title, description, onBack, children } ) {
{ description }
-
+ { onBack && (
+
+ ) }
{ children }
diff --git a/packages/block-library/src/navigation-link/link-ui/page-creator.js b/packages/block-library/src/navigation-link/link-ui/page-creator.js
index 33631348628ec2..03ae91197105e2 100644
--- a/packages/block-library/src/navigation-link/link-ui/page-creator.js
+++ b/packages/block-library/src/navigation-link/link-ui/page-creator.js
@@ -24,19 +24,23 @@ import DialogWrapper from './dialog-wrapper';
/**
* Component for creating new pages within the Navigation Link UI.
*
- * @param {Object} props Component props.
- * @param {string} props.postType The post type to create.
- * @param {Function} props.onBack Callback when user wants to go back.
- * @param {Function} props.onPageCreated Callback when page is successfully created.
- * @param {string} [props.initialTitle] Initial title to pre-fill the form.
- * @param {Object} [props.additionalData] Additional data to include when saving the entity record.
+ * @param {Object} props Component props.
+ * @param {string} props.postType The post type to create.
+ * @param {Function} [props.onBack] Callback when user wants to go back. Shows a back button when provided.
+ * @param {Function} props.onClose Callback to close the creator without going back.
+ * @param {Function} props.onPageCreated Callback when page is successfully created.
+ * @param {string} [props.initialTitle] Initial title to pre-fill the form.
+ * @param {number} [props.menuOrder] Menu order for the new page.
+ * @param {number} [props.parent] Parent page ID for the new page.
*/
export function LinkUIPageCreator( {
postType,
onBack,
+ onClose,
onPageCreated,
initialTitle = '',
- additionalData = {},
+ menuOrder,
+ parent,
} ) {
const [ title, setTitle ] = useState( initialTitle );
const [ shouldPublish, setShouldPublish ] = useState( true );
@@ -76,7 +80,10 @@ export function LinkUIPageCreator( {
{
title,
status: shouldPublish ? 'publish' : 'draft',
- ...additionalData,
+ ...( menuOrder !== undefined && {
+ menu_order: menuOrder,
+ } ),
+ ...( parent !== undefined && { parent } ),
},
{ throwOnError: true }
);
@@ -120,6 +127,8 @@ export function LinkUIPageCreator( {
const isSubmitDisabled = isSaving || ! isTitleValid;
+ const handleDismiss = onBack || onClose;
+
return (
diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js
index 0364e15fd8491c..9707d57ab1d6c8 100644
--- a/packages/block-library/src/page-list/edit.js
+++ b/packages/block-library/src/page-list/edit.js
@@ -55,6 +55,7 @@ function BlockContent( {
blockList,
pages,
parentPageID,
+ showAppender,
} ) {
if ( ! hasResolvedPages ) {
return (
@@ -115,7 +116,13 @@ function BlockContent( {
}
if ( pages.length > 0 ) {
- return ;
+ const { children, ...ulProps } = innerBlocksProps;
+ return (
+
+ { children }
+ { showAppender && }
+
+ );
}
}
@@ -313,18 +320,18 @@ export default function PageListEdit( {
);
}, [ pages ] );
- const additionalPageData = useMemo(
+ const pageCreatorContextValue = useMemo(
() => ( {
- menu_order: nextMenuOrder,
+ menuOrder: nextMenuOrder,
...( parentPageID ? { parent: parentPageID } : {} ),
} ),
[ nextMenuOrder, parentPageID ]
);
const innerBlocksProps = useInnerBlocksProps( blockProps, {
- renderAppender: showAppender ? PageCreatorAppender : false,
+ renderAppender: false,
__unstableDisableDropZone: true,
- templateLock: showAppender ? false : 'all',
+ templateLock: isChildOfNavigation ? false : 'all',
onInput: NOOP,
onChange: NOOP,
value: blockList,
@@ -335,9 +342,7 @@ export default function PageListEdit( {
}, [ isNested, setAttributes ] );
return (
-
+
{ ( pagesTree.length > 0 || allowConvertToLinks ) && (
);
diff --git a/packages/block-library/src/page-list/page-creator-appender.js b/packages/block-library/src/page-list/page-creator-appender.js
index 85cb1da2db71d5..ada8ed5cbc209e 100644
--- a/packages/block-library/src/page-list/page-creator-appender.js
+++ b/packages/block-library/src/page-list/page-creator-appender.js
@@ -5,21 +5,17 @@ import {
createContext,
useContext,
useState,
- useMemo,
useRef,
} from '@wordpress/element';
import { Button, Popover } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Icon, plus } from '@wordpress/icons';
-import { useEntityRecords } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { LinkUIPageCreator } from '../navigation-link/link-ui/page-creator';
-const MAX_PAGE_COUNT = 100;
-
export const PageCreatorContext = createContext( null );
export default function PageCreatorAppender() {
@@ -27,28 +23,6 @@ export default function PageCreatorAppender() {
const ref = useRef();
const contextData = useContext( PageCreatorContext );
- const { records: pages } = useEntityRecords( 'postType', 'page', {
- per_page: MAX_PAGE_COUNT,
- _fields: [ 'id', 'link', 'menu_order', 'parent', 'title', 'type' ],
- orderby: 'menu_order',
- order: 'asc',
- } );
-
- const additionalData = useMemo( () => {
- // If context provides data (e.g., from Page List with parentPageID),
- // use it directly.
- if ( contextData?.additionalData ) {
- return contextData.additionalData;
- }
- // Otherwise compute menu_order from pages.
- const maxMenuOrder =
- pages?.reduce(
- ( max, page ) => Math.max( max, page.menu_order ),
- 0
- ) ?? 0;
- return { menu_order: maxMenuOrder + 1 };
- }, [ contextData, pages ] );
-
return (
<>