From 17924a538f38302b134386fd49e9c8e97ddf0c42 Mon Sep 17 00:00:00 2001 From: sarthaknagoshe2002 Date: Mon, 27 Apr 2026 18:56:55 +0530 Subject: [PATCH 1/3] Feat: Enhanced inline page creation flow --- .../components/link-control/page-creator.js | 169 ++++++++++++++++++ .../components/link-control/search-input.js | 30 +++- .../src/components/link-control/style.scss | 33 +++- .../link-control/use-create-page.js | 14 +- packages/block-library/src/button/edit.js | 13 +- packages/format-library/src/link/inline.js | 13 +- 6 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 packages/block-editor/src/components/link-control/page-creator.js diff --git a/packages/block-editor/src/components/link-control/page-creator.js b/packages/block-editor/src/components/link-control/page-creator.js new file mode 100644 index 00000000000000..ec21999dc963b0 --- /dev/null +++ b/packages/block-editor/src/components/link-control/page-creator.js @@ -0,0 +1,169 @@ +/** + * WordPress dependencies + */ +import { useState, useRef, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + Button, + TextControl, + Notice, + CheckboxControl, + __experimentalVStack as VStack, + __experimentalHStack as HStack, +} from '@wordpress/components'; +import { chevronLeftSmall } from '@wordpress/icons'; + +function generateSlug( text ) { + const cleanSlug = text + .toLowerCase() + .replace( /[^a-z0-9\s-]/g, '' ) + .replace( /\s+/g, '-' ) + .replace( /-+/g, '-' ) + .trim(); + + return cleanSlug ? '/' + cleanSlug : ''; +} + +/** + * Inline page creation form for LinkControl. + * + * Page creation is delegated to the consumer via onCreateSuggestion, which + * already carries the saveEntityRecord call. + * + * @param {Object} props + * @param {string} props.initialTitle Pre-populated from the search input value. + * @param {Function} props.onCreateSuggestion Consumer-provided async creator: (title) => suggestion. + * @param {Function} props.onPageCreated Called with the resulting link object on success. + * @param {Function} props.onCancel Called when the user cancels. + */ +export function LinkControlPageCreator( { + initialTitle = '', + onCreateSuggestion, + onPageCreated, + onCancel, +} ) { + const [ title, setTitle ] = useState( initialTitle ); + const [ isCreating, setIsCreating ] = useState( false ); + const [ errorMessage, setErrorMessage ] = useState( null ); + const [ publishImmediately, setPublishImmediately ] = useState( true ); + const [ slug, setSlug ] = useState( generateSlug( initialTitle ) ); + const [ isSlugDirty, setIsSlugDirty ] = useState( false ); + const backButtonRef = useRef(); + + useEffect( () => { + backButtonRef.current?.focus(); + }, [] ); + + const handleTitleChange = ( newTitle ) => { + setTitle( newTitle ); + if ( ! isSlugDirty ) { + setSlug( generateSlug( newTitle ) ); + } + }; + + const handleSlugChange = ( newSlug ) => { + setSlug( newSlug ); + setIsSlugDirty( true ); + }; + + const handleCreate = async () => { + if ( ! title?.trim() ) { + return; + } + + setIsCreating( true ); + setErrorMessage( null ); + try { + const suggestion = await onCreateSuggestion( + title.trim(), + publishImmediately, + slug.trim() + ); + if ( suggestion?.url ) { + onPageCreated( suggestion ); + } + } catch ( _e ) { + setErrorMessage( + __( 'There was an error creating the page. Please try again.' ) + ); + setIsCreating( false ); + } + }; + + return ( +
+ +
+ { errorMessage && ( + + { errorMessage } + + ) } + + { + if ( event.key === 'Enter' ) { + event.preventDefault(); + handleCreate(); + } + } } + disabled={ isCreating } + /> + + + + + + + +
+
+ ); +} diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index c72e7f69166ce6..2f7f894f43aad4 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -3,7 +3,10 @@ */ import { forwardRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { plus } from '@wordpress/icons'; +import { Button } from '@wordpress/components'; import deprecated from '@wordpress/deprecated'; +import { LinkControlPageCreator } from './page-creator'; /** * Internal dependencies @@ -46,13 +49,14 @@ const LinkControlSearchInput = forwardRef( suffix, isEntity = false, customValidity: customValidityProp, + showCreateSuggestionInDropdown = false, }, ref ) => { const genericSearchHandler = useSearchHandler( suggestionsQuery, allowDirectEntry, - withCreateSuggestion, + withCreateSuggestion && showCreateSuggestionInDropdown, withURLSuggestion ); @@ -61,6 +65,7 @@ const LinkControlSearchInput = forwardRef( : noopSearchHandler; const [ focusedSuggestion, setFocusedSuggestion ] = useState(); + const [ isCreatingPage, setIsCreatingPage ] = useState( false ); /** * Handles the user moving between different suggestions. Does not handle @@ -129,6 +134,17 @@ const LinkControlSearchInput = forwardRef( ? _placeholder : __( 'Link' ); + if ( withCreateSuggestion && isCreatingPage ) { + return ( + setIsCreatingPage( false ) } + onCancel={ () => setIsCreatingPage( false ) } + /> + ); + } + return (
+ { withCreateSuggestion && ! showCreateSuggestionInDropdown && ( + + ) } { children }
); diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index 061ac588c3d774..c5d4387224bb83 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -56,6 +56,22 @@ $block-editor-link-control-number-of-actions: 1; } } } + + &__page-creator { + max-width: 350px; + min-width: auto; + width: 90vw; + padding-top: $grid-unit-10; + + &__back { + margin-left: $grid-unit-10; + text-transform: uppercase; + } + + &__inner { + padding: $grid-unit-20; + } + } } // Provides positioning context for reset button. Without this then when an @@ -276,7 +292,7 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__loading { - margin: $grid-unit-20; // when only loading control is shown it requires it's own spacing. + margin: $grid-unit-20; display: flex; align-items: center; @@ -304,6 +320,21 @@ $block-editor-link-control-number-of-actions: 1; .block-editor-link-control__search-create { align-items: center; // align text with icon. + width: 100%; + border-top: 1px solid $gray-200; + padding: $grid-unit-10 $grid-unit-15; + color: $gray-900; + + &:hover, + &:focus { + background-color: $gray-100; + } + + mark { + font-weight: 600; + color: inherit; + background-color: transparent; + } .block-editor-link-control__preview-title { margin-bottom: 0; diff --git a/packages/block-editor/src/components/link-control/use-create-page.js b/packages/block-editor/src/components/link-control/use-create-page.js index 42e243a2625fd4..1816eecb4c9986 100644 --- a/packages/block-editor/src/components/link-control/use-create-page.js +++ b/packages/block-editor/src/components/link-control/use-create-page.js @@ -9,7 +9,11 @@ export default function useCreatePage( handleCreatePage ) { const [ isCreatingPage, setIsCreatingPage ] = useState( false ); const [ errorMessage, setErrorMessage ] = useState( null ); - const createPage = async function ( suggestionTitle ) { + const createPage = async function ( + suggestionTitle, + publishImmediately, + Slug + ) { setIsCreatingPage( true ); setErrorMessage( null ); @@ -19,7 +23,13 @@ export default function useCreatePage( handleCreatePage ) { cancelableCreateSuggestion.current = makeCancelable( // Using Promise.resolve to allow createSuggestion to return a // non-Promise based value. - Promise.resolve( handleCreatePage( suggestionTitle ) ) + Promise.resolve( + handleCreatePage( + suggestionTitle, + publishImmediately, + Slug + ) + ) ); return await cancelableCreateSuggestion.current.promise; diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index b89580164178f9..b2b40263d1bd5b 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -204,11 +204,16 @@ function ButtonEdit( props ) { [ context, isSelected, metadata?.bindings?.url ] ); - async function handleCreate( pageTitle ) { - const page = await createPageEntity( { + async function handleCreate( pageTitle, publishImmediately, Slug ) { + const payload = { title: pageTitle, - status: 'draft', - } ); + status: publishImmediately ? 'publish' : 'draft', + }; + + if ( Slug ) { + payload.slug = Slug; + } + const page = await createPageEntity( payload ); return { id: page.id, diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 9b9129be340713..40e7eea9ee1aee 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -251,11 +251,16 @@ function InlineLinkUI( { }, } ); - async function handleCreate( pageTitle ) { - const page = await createPageEntity( { + async function handleCreate( pageTitle, publishImmediately, Slug ) { + const payload = { title: pageTitle, - status: 'draft', - } ); + status: publishImmediately ? 'publish' : 'draft', + }; + + if ( Slug ) { + payload.slug = Slug; + } + const page = await createPageEntity( payload ); return { id: page.id, From 02748573af7bca5c11d2c2605773173f4a4b16c1 Mon Sep 17 00:00:00 2001 From: sarthaknagoshe2002 Date: Mon, 27 Apr 2026 19:17:17 +0530 Subject: [PATCH 2/3] Fix: added showCreateSuggestionInDropdown prop --- packages/block-editor/src/components/link-control/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index bffeb929c921e7..c9dee401164a76 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -181,6 +181,7 @@ function LinkControl( { hasTextControl = false, renderControlBottom = null, handleEntities = false, + showCreateSuggestionInDropdown, } ) { if ( withCreateSuggestion === undefined && createSuggestion ) { withCreateSuggestion = true; @@ -711,6 +712,9 @@ function LinkControl( { helpTextId={ helpTextId } /> } + showCreateSuggestionInDropdown={ + showCreateSuggestionInDropdown + } /> { isEntity && helpTextId && (

Date: Mon, 27 Apr 2026 19:26:58 +0530 Subject: [PATCH 3/3] Fix: doc comments --- .../block-editor/src/components/link-control/page-creator.js | 2 +- packages/block-editor/src/components/link-control/style.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/link-control/page-creator.js b/packages/block-editor/src/components/link-control/page-creator.js index ec21999dc963b0..e9618bc3ae9980 100644 --- a/packages/block-editor/src/components/link-control/page-creator.js +++ b/packages/block-editor/src/components/link-control/page-creator.js @@ -32,7 +32,7 @@ function generateSlug( text ) { * * @param {Object} props * @param {string} props.initialTitle Pre-populated from the search input value. - * @param {Function} props.onCreateSuggestion Consumer-provided async creator: (title) => suggestion. + * @param {Function} props.onCreateSuggestion Consumer-provided async creator. * @param {Function} props.onPageCreated Called with the resulting link object on success. * @param {Function} props.onCancel Called when the user cancels. */ diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index c5d4387224bb83..abd0881612c3e4 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -292,7 +292,7 @@ $block-editor-link-control-number-of-actions: 1; } .block-editor-link-control__loading { - margin: $grid-unit-20; + margin: $grid-unit-20; // when only loading control is shown it requires it's own spacing. display: flex; align-items: center;