@wordpress/block-library 9.30.0 → 9.30.1-next.6870dfe5b.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/build/accordion-content/edit.js +8 -8
  2. package/build/accordion-content/edit.js.map +1 -1
  3. package/build/accordion-content/index.js +2 -1
  4. package/build/accordion-content/index.js.map +1 -1
  5. package/build/accordion-panel/index.js +2 -1
  6. package/build/accordion-panel/index.js.map +1 -1
  7. package/build/navigation/constants.js +5 -1
  8. package/build/navigation/constants.js.map +1 -1
  9. package/build/navigation/edit/index.js +45 -1
  10. package/build/navigation/edit/index.js.map +1 -1
  11. package/build/navigation/edit/leaf-more-menu.js +0 -1
  12. package/build/navigation/edit/leaf-more-menu.js.map +1 -1
  13. package/build/navigation/edit/menu-inspector-controls.js +40 -5
  14. package/build/navigation/edit/menu-inspector-controls.js.map +1 -1
  15. package/build/navigation-link/block-inserter.js +69 -0
  16. package/build/navigation-link/block-inserter.js.map +1 -0
  17. package/build/navigation-link/dialog-wrapper.js +80 -0
  18. package/build/navigation-link/dialog-wrapper.js.map +1 -0
  19. package/build/navigation-link/link-ui.js +80 -120
  20. package/build/navigation-link/link-ui.js.map +1 -1
  21. package/build/navigation-link/page-creator.js +137 -0
  22. package/build/navigation-link/page-creator.js.map +1 -0
  23. package/build/search/edit.js +22 -14
  24. package/build/search/edit.js.map +1 -1
  25. package/build-module/accordion-content/edit.js +8 -8
  26. package/build-module/accordion-content/edit.js.map +1 -1
  27. package/build-module/accordion-content/index.js +2 -1
  28. package/build-module/accordion-content/index.js.map +1 -1
  29. package/build-module/accordion-panel/index.js +2 -1
  30. package/build-module/accordion-panel/index.js.map +1 -1
  31. package/build-module/navigation/constants.js +5 -1
  32. package/build-module/navigation/constants.js.map +1 -1
  33. package/build-module/navigation/edit/index.js +50 -4
  34. package/build-module/navigation/edit/index.js.map +1 -1
  35. package/build-module/navigation/edit/leaf-more-menu.js +0 -1
  36. package/build-module/navigation/edit/leaf-more-menu.js.map +1 -1
  37. package/build-module/navigation/edit/menu-inspector-controls.js +40 -5
  38. package/build-module/navigation/edit/menu-inspector-controls.js.map +1 -1
  39. package/build-module/navigation-link/block-inserter.js +61 -0
  40. package/build-module/navigation-link/block-inserter.js.map +1 -0
  41. package/build-module/navigation-link/dialog-wrapper.js +75 -0
  42. package/build-module/navigation-link/dialog-wrapper.js.map +1 -0
  43. package/build-module/navigation-link/link-ui.js +85 -125
  44. package/build-module/navigation-link/link-ui.js.map +1 -1
  45. package/build-module/navigation-link/page-creator.js +130 -0
  46. package/build-module/navigation-link/page-creator.js.map +1 -0
  47. package/build-module/search/edit.js +22 -14
  48. package/build-module/search/edit.js.map +1 -1
  49. package/build-style/accordion/style-rtl.css +5 -6
  50. package/build-style/accordion/style.css +5 -6
  51. package/build-style/editor-rtl.css +14 -0
  52. package/build-style/editor.css +14 -0
  53. package/build-style/form-input/style-rtl.css +4 -3
  54. package/build-style/form-input/style.css +4 -3
  55. package/build-style/navigation-link/editor-rtl.css +14 -0
  56. package/build-style/navigation-link/editor.css +14 -0
  57. package/build-style/navigation-link/style-rtl.css +1 -1
  58. package/build-style/navigation-link/style.css +1 -1
  59. package/build-style/post-comments-form/style-rtl.css +8 -5
  60. package/build-style/post-comments-form/style.css +8 -5
  61. package/build-style/search/style-rtl.css +11 -12
  62. package/build-style/search/style.css +11 -12
  63. package/build-style/style-rtl.css +29 -27
  64. package/build-style/style.css +29 -27
  65. package/package.json +35 -35
  66. package/src/accordion/style.scss +6 -6
  67. package/src/accordion-content/block.json +2 -1
  68. package/src/accordion-content/edit.js +21 -27
  69. package/src/accordion-panel/block.json +2 -1
  70. package/src/cover/test/edit.js +1 -5
  71. package/src/form-input/style.scss +3 -2
  72. package/src/navigation/constants.js +4 -0
  73. package/src/navigation/edit/index.js +50 -1
  74. package/src/navigation/edit/leaf-more-menu.js +0 -1
  75. package/src/navigation/edit/menu-inspector-controls.js +40 -5
  76. package/src/navigation-link/block-inserter.js +65 -0
  77. package/src/navigation-link/dialog-wrapper.js +74 -0
  78. package/src/navigation-link/editor.scss +17 -0
  79. package/src/navigation-link/link-ui.js +108 -164
  80. package/src/navigation-link/page-creator.js +157 -0
  81. package/src/navigation-link/style.scss +1 -1
  82. package/src/post-comments-form/style.scss +11 -11
  83. package/src/search/edit.js +44 -13
  84. package/src/search/index.php +16 -2
  85. package/src/search/style.scss +15 -16
@@ -35,7 +35,8 @@ const BLOCKS_WITH_LINK_UI_SUPPORT = [
35
35
  const { PrivateListView } = unlock( blockEditorPrivateApis );
36
36
 
37
37
  function AdditionalBlockContent( { block, insertedBlock, setInsertedBlock } ) {
38
- const { updateBlockAttributes } = useDispatch( blockEditorStore );
38
+ const { updateBlockAttributes, removeBlock } =
39
+ useDispatch( blockEditorStore );
39
40
 
40
41
  const supportsLinkControls = BLOCKS_WITH_LINK_UI_SUPPORT?.includes(
41
42
  insertedBlock?.name
@@ -47,6 +48,27 @@ function AdditionalBlockContent( { block, insertedBlock, setInsertedBlock } ) {
47
48
  return null;
48
49
  }
49
50
 
51
+ /**
52
+ * Cleanup function for auto-inserted Navigation Link blocks.
53
+ *
54
+ * Removes the block if it has no URL and clears the inserted block state.
55
+ * This ensures consistent cleanup behavior across different contexts.
56
+ */
57
+ const cleanupInsertedBlock = () => {
58
+ // Prevent automatic block selection when removing blocks in list view context
59
+ // This avoids focus stealing that would close the list view and switch to canvas
60
+ const shouldAutoSelectBlock = false;
61
+
62
+ // Follows the exact same pattern as Navigation Link block's onClose handler
63
+ // If there is no URL then remove the auto-inserted block to avoid empty blocks
64
+ if ( ! insertedBlock?.attributes?.url && insertedBlock?.clientId ) {
65
+ // Remove the block entirely to avoid poor UX
66
+ // This matches the Navigation Link block's behavior
67
+ removeBlock( insertedBlock.clientId, shouldAutoSelectBlock );
68
+ }
69
+ setInsertedBlock( null );
70
+ };
71
+
50
72
  const setInsertedBlockAttributes =
51
73
  ( _insertedBlockClientId ) => ( _updatedAttributes ) => {
52
74
  if ( ! _insertedBlockClientId ) {
@@ -55,12 +77,28 @@ function AdditionalBlockContent( { block, insertedBlock, setInsertedBlock } ) {
55
77
  updateBlockAttributes( _insertedBlockClientId, _updatedAttributes );
56
78
  };
57
79
 
80
+ // Wrapper function to clean up original block when a new block is selected
81
+ const handleSetInsertedBlock = ( newBlock ) => {
82
+ // Prevent automatic block selection when removing blocks in list view context
83
+ // This avoids focus stealing that would close the list view and switch to canvas
84
+ const shouldAutoSelectBlock = false;
85
+
86
+ // If we have an existing inserted block and a new block is being set,
87
+ // remove the original block to avoid duplicates
88
+ if ( insertedBlock?.clientId && newBlock ) {
89
+ removeBlock( insertedBlock.clientId, shouldAutoSelectBlock );
90
+ }
91
+ setInsertedBlock( newBlock );
92
+ };
93
+
58
94
  return (
59
95
  <LinkUI
60
96
  clientId={ insertedBlock?.clientId }
61
97
  link={ insertedBlock?.attributes }
98
+ onBlockInsert={ handleSetInsertedBlock }
62
99
  onClose={ () => {
63
- setInsertedBlock( null );
100
+ // Use cleanup function
101
+ cleanupInsertedBlock();
64
102
  } }
65
103
  onChange={ ( updatedValue ) => {
66
104
  updateAttributes(
@@ -70,9 +108,6 @@ function AdditionalBlockContent( { block, insertedBlock, setInsertedBlock } ) {
70
108
  );
71
109
  setInsertedBlock( null );
72
110
  } }
73
- onCancel={ () => {
74
- setInsertedBlock( null );
75
- } }
76
111
  />
77
112
  );
78
113
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import { useSelect } from '@wordpress/data';
6
+ import {
7
+ store as blockEditorStore,
8
+ privateApis as blockEditorPrivateApis,
9
+ } from '@wordpress/block-editor';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import DialogWrapper from './dialog-wrapper';
15
+ import { unlock } from '../lock-unlock';
16
+
17
+ const { PrivateQuickInserter: QuickInserter } = unlock(
18
+ blockEditorPrivateApis
19
+ );
20
+
21
+ /**
22
+ * Component for inserting blocks within the Navigation Link UI.
23
+ *
24
+ * @param {Object} props Component props.
25
+ * @param {string} props.clientId Client ID of the navigation link block.
26
+ * @param {Function} props.onBack Callback when user wants to go back.
27
+ * @param {Function} props.onBlockInsert Callback when a block is inserted.
28
+ */
29
+ function LinkUIBlockInserter( { clientId, onBack, onBlockInsert } ) {
30
+ const { rootBlockClientId } = useSelect(
31
+ ( select ) => {
32
+ const { getBlockRootClientId } = select( blockEditorStore );
33
+
34
+ return {
35
+ rootBlockClientId: getBlockRootClientId( clientId ),
36
+ };
37
+ },
38
+ [ clientId ]
39
+ );
40
+
41
+ if ( ! clientId ) {
42
+ return null;
43
+ }
44
+
45
+ return (
46
+ <DialogWrapper
47
+ className="link-ui-block-inserter"
48
+ title={ __( 'Add block' ) }
49
+ description={ __( 'Choose a block to add to your Navigation.' ) }
50
+ onBack={ onBack }
51
+ >
52
+ <QuickInserter
53
+ rootClientId={ rootBlockClientId }
54
+ clientId={ clientId }
55
+ isAppender={ false }
56
+ prioritizePatterns={ false }
57
+ selectBlockOnInsert={ ! onBlockInsert }
58
+ onSelect={ onBlockInsert ? onBlockInsert : undefined }
59
+ hasSearch={ false }
60
+ />
61
+ </DialogWrapper>
62
+ );
63
+ }
64
+
65
+ export default LinkUIBlockInserter;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Button, VisuallyHidden } from '@wordpress/components';
5
+ import { __, isRTL } from '@wordpress/i18n';
6
+ import { chevronLeftSmall, chevronRightSmall } from '@wordpress/icons';
7
+ import { useInstanceId, useFocusOnMount } from '@wordpress/compose';
8
+
9
+ /**
10
+ * Shared BackButton component for consistent navigation across LinkUI sub-components.
11
+ *
12
+ * @param {Object} props Component props.
13
+ * @param {string} props.className CSS class name for the button.
14
+ * @param {Function} props.onBack Callback when user wants to go back.
15
+ */
16
+ function BackButton( { className, onBack } ) {
17
+ return (
18
+ <Button
19
+ className={ className }
20
+ icon={ isRTL() ? chevronRightSmall : chevronLeftSmall }
21
+ onClick={ ( e ) => {
22
+ e.preventDefault();
23
+ onBack();
24
+ } }
25
+ size="small"
26
+ >
27
+ { __( 'Back' ) }
28
+ </Button>
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Shared DialogWrapper component for consistent dialog structure across LinkUI sub-components.
34
+ *
35
+ * @param {Object} props Component props.
36
+ * @param {string} props.className CSS class name for the dialog container.
37
+ * @param {string} props.title Dialog title for accessibility.
38
+ * @param {string} props.description Dialog description for accessibility.
39
+ * @param {Function} props.onBack Callback when user wants to go back.
40
+ * @param {Object} props.children Child components to render inside the dialog.
41
+ */
42
+ function DialogWrapper( { className, title, description, onBack, children } ) {
43
+ const dialogTitleId = useInstanceId(
44
+ DialogWrapper,
45
+ 'link-ui-dialog-title'
46
+ );
47
+ const dialogDescriptionId = useInstanceId(
48
+ DialogWrapper,
49
+ 'link-ui-dialog-description'
50
+ );
51
+ const focusOnMountRef = useFocusOnMount( 'firstElement' );
52
+ const backButtonClassName = `${ className }__back`;
53
+
54
+ return (
55
+ <div
56
+ className={ className }
57
+ role="dialog"
58
+ aria-labelledby={ dialogTitleId }
59
+ aria-describedby={ dialogDescriptionId }
60
+ ref={ focusOnMountRef }
61
+ >
62
+ <VisuallyHidden>
63
+ <h2 id={ dialogTitleId }>{ title }</h2>
64
+ <p id={ dialogDescriptionId }>{ description }</p>
65
+ </VisuallyHidden>
66
+
67
+ <BackButton className={ backButtonClassName } onBack={ onBack } />
68
+
69
+ { children }
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export default DialogWrapper;
@@ -122,3 +122,20 @@
122
122
  gap: $grid-unit-10;
123
123
  height: auto;
124
124
  }
125
+
126
+ .link-ui-page-creator {
127
+ // Match LinkControl width constraints for consistent UI sizing
128
+ max-width: 350px;
129
+ min-width: auto;
130
+ width: 90vw;
131
+ padding-top: $grid-unit-10;
132
+
133
+ &__inner {
134
+ padding: $grid-unit-20;
135
+ }
136
+
137
+ &__back {
138
+ margin-left: $grid-unit-10;
139
+ text-transform: uppercase;
140
+ }
141
+ }
@@ -8,38 +8,24 @@ import {
8
8
  VisuallyHidden,
9
9
  __experimentalVStack as VStack,
10
10
  } from '@wordpress/components';
11
- import { __, sprintf, isRTL } from '@wordpress/i18n';
11
+ import { __ } from '@wordpress/i18n';
12
+ import { LinkControl, useBlockEditingMode } from '@wordpress/block-editor';
12
13
  import {
13
- LinkControl,
14
- store as blockEditorStore,
15
- privateApis as blockEditorPrivateApis,
16
- useBlockEditingMode,
17
- } from '@wordpress/block-editor';
18
- import {
19
- createInterpolateElement,
20
14
  useMemo,
21
15
  useState,
22
16
  useRef,
23
17
  useEffect,
24
18
  forwardRef,
25
19
  } from '@wordpress/element';
26
- import {
27
- store as coreStore,
28
- useResourcePermissions,
29
- } from '@wordpress/core-data';
30
- import { decodeEntities } from '@wordpress/html-entities';
31
- import { useSelect, useDispatch } from '@wordpress/data';
32
- import { chevronLeftSmall, chevronRightSmall, plus } from '@wordpress/icons';
33
- import { useInstanceId, useFocusOnMount } from '@wordpress/compose';
20
+ import { useResourcePermissions } from '@wordpress/core-data';
21
+ import { plus } from '@wordpress/icons';
22
+ import { useInstanceId } from '@wordpress/compose';
34
23
 
35
24
  /**
36
25
  * Internal dependencies
37
26
  */
38
- import { unlock } from '../lock-unlock';
39
-
40
- const { PrivateQuickInserter: QuickInserter } = unlock(
41
- blockEditorPrivateApis
42
- );
27
+ import { LinkUIPageCreator } from './page-creator';
28
+ import LinkUIBlockInserter from './block-inserter';
43
29
 
44
30
  /**
45
31
  * Given the Link block's type attribute, return the query params to give to
@@ -79,114 +65,19 @@ export function getSuggestionsQuery( type, kind ) {
79
65
  }
80
66
  }
81
67
 
82
- function LinkUIBlockInserter( { clientId, onBack } ) {
83
- const { rootBlockClientId } = useSelect(
84
- ( select ) => {
85
- const { getBlockRootClientId } = select( blockEditorStore );
86
-
87
- return {
88
- rootBlockClientId: getBlockRootClientId( clientId ),
89
- };
90
- },
91
- [ clientId ]
92
- );
93
-
94
- const focusOnMountRef = useFocusOnMount( 'firstElement' );
95
-
96
- const dialogTitleId = useInstanceId(
97
- LinkControl,
98
- `link-ui-block-inserter__title`
99
- );
100
- const dialogDescriptionId = useInstanceId(
101
- LinkControl,
102
- `link-ui-block-inserter__description`
103
- );
104
-
105
- if ( ! clientId ) {
106
- return null;
107
- }
108
-
109
- return (
110
- <div
111
- className="link-ui-block-inserter"
112
- role="dialog"
113
- aria-labelledby={ dialogTitleId }
114
- aria-describedby={ dialogDescriptionId }
115
- ref={ focusOnMountRef }
116
- >
117
- <VisuallyHidden>
118
- <h2 id={ dialogTitleId }>{ __( 'Add block' ) }</h2>
119
-
120
- <p id={ dialogDescriptionId }>
121
- { __( 'Choose a block to add to your Navigation.' ) }
122
- </p>
123
- </VisuallyHidden>
124
-
125
- <Button
126
- className="link-ui-block-inserter__back"
127
- icon={ isRTL() ? chevronRightSmall : chevronLeftSmall }
128
- onClick={ ( e ) => {
129
- e.preventDefault();
130
- onBack();
131
- } }
132
- size="small"
133
- >
134
- { __( 'Back' ) }
135
- </Button>
136
-
137
- <QuickInserter
138
- rootClientId={ rootBlockClientId }
139
- clientId={ clientId }
140
- isAppender={ false }
141
- prioritizePatterns={ false }
142
- selectBlockOnInsert
143
- hasSearch={ false }
144
- />
145
- </div>
146
- );
147
- }
148
-
149
68
  function UnforwardedLinkUI( props, ref ) {
150
69
  const { label, url, opensInNewTab, type, kind } = props.link;
151
70
  const postType = type || 'page';
152
71
 
153
72
  const [ addingBlock, setAddingBlock ] = useState( false );
73
+ const [ addingPage, setAddingPage ] = useState( false );
154
74
  const [ focusAddBlockButton, setFocusAddBlockButton ] = useState( false );
155
- const { saveEntityRecord } = useDispatch( coreStore );
75
+ const [ focusAddPageButton, setFocusAddPageButton ] = useState( false );
156
76
  const permissions = useResourcePermissions( {
157
77
  kind: 'postType',
158
78
  name: postType,
159
79
  } );
160
80
 
161
- // Check if we're in contentOnly mode
162
- const blockEditingMode = useBlockEditingMode();
163
- const isDefaultBlockEditingMode = blockEditingMode === 'default';
164
-
165
- async function handleCreate( pageTitle ) {
166
- const page = await saveEntityRecord( 'postType', postType, {
167
- title: pageTitle,
168
- status: 'draft',
169
- } );
170
-
171
- return {
172
- id: page.id,
173
- type: postType,
174
- // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities)
175
- // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code.
176
- // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts.
177
- // e.g.
178
- // - title.raw = "Yes & No"
179
- // - title.rendered = "Yes &#038; No"
180
- // - decodeEntities( title.rendered ) = "Yes & No"
181
- // See:
182
- // - https://github.com/WordPress/gutenberg/pull/41063
183
- // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218
184
- title: decodeEntities( page.title.rendered ),
185
- url: page.link,
186
- kind: 'post-type',
187
- };
188
- }
189
-
190
81
  // Memoize link value to avoid overriding the LinkControl's internal state.
191
82
  // This is a temporary fix. See https://github.com/WordPress/gutenberg/issues/50976#issuecomment-1568226407.
192
83
  const link = useMemo(
@@ -198,15 +89,24 @@ function UnforwardedLinkUI( props, ref ) {
198
89
  [ label, opensInNewTab, url ]
199
90
  );
200
91
 
92
+ const handlePageCreated = ( pageLink ) => {
93
+ // Set the new page as the current link
94
+ props.onChange( pageLink );
95
+ // Return to main Link UI
96
+ setAddingPage( false );
97
+ };
98
+
201
99
  const dialogTitleId = useInstanceId(
202
100
  LinkUI,
203
- `link-ui-link-control__title`
101
+ 'link-ui-link-control__title'
204
102
  );
205
103
  const dialogDescriptionId = useInstanceId(
206
104
  LinkUI,
207
- `link-ui-link-control__description`
105
+ 'link-ui-link-control__description'
208
106
  );
209
107
 
108
+ const blockEditingMode = useBlockEditingMode();
109
+
210
110
  return (
211
111
  <Popover
212
112
  ref={ ref }
@@ -215,7 +115,7 @@ function UnforwardedLinkUI( props, ref ) {
215
115
  anchor={ props.anchor }
216
116
  shift
217
117
  >
218
- { ! addingBlock && (
118
+ { ! addingBlock && ! addingPage && (
219
119
  <div
220
120
  role="dialog"
221
121
  aria-labelledby={ dialogTitleId }
@@ -235,48 +135,41 @@ function UnforwardedLinkUI( props, ref ) {
235
135
  hasRichPreviews
236
136
  value={ link }
237
137
  showInitialSuggestions
238
- withCreateSuggestion={ permissions.canCreate }
239
- createSuggestion={ handleCreate }
240
- createSuggestionButtonText={ ( searchTerm ) => {
241
- let format;
242
-
243
- if ( type === 'post' ) {
244
- /* translators: %s: search term. */
245
- format = __(
246
- 'Create draft post: <mark>%s</mark>'
247
- );
248
- } else {
249
- /* translators: %s: search term. */
250
- format = __(
251
- 'Create draft page: <mark>%s</mark>'
252
- );
253
- }
254
-
255
- return createInterpolateElement(
256
- sprintf( format, searchTerm ),
257
- {
258
- mark: <mark />,
259
- }
260
- );
261
- } }
138
+ withCreateSuggestion={ false }
262
139
  noDirectEntry={ !! type }
263
140
  noURLSuggestion={ !! type }
264
141
  suggestionsQuery={ getSuggestionsQuery( type, kind ) }
265
142
  onChange={ props.onChange }
266
143
  onRemove={ props.onRemove }
267
144
  onCancel={ props.onCancel }
268
- renderControlBottom={ () =>
269
- ! link?.url?.length &&
270
- isDefaultBlockEditingMode && (
145
+ renderControlBottom={ () => {
146
+ // Don't show the tools when there is submitted link (preview state).
147
+ if ( link?.url?.length ) {
148
+ return null;
149
+ }
150
+
151
+ return (
271
152
  <LinkUITools
272
153
  focusAddBlockButton={ focusAddBlockButton }
154
+ focusAddPageButton={ focusAddPageButton }
273
155
  setAddingBlock={ () => {
274
156
  setAddingBlock( true );
275
157
  setFocusAddBlockButton( false );
276
158
  } }
159
+ setAddingPage={ () => {
160
+ setAddingPage( true );
161
+ setFocusAddPageButton( false );
162
+ } }
163
+ canAddPage={
164
+ permissions?.canCreate &&
165
+ type === 'page'
166
+ }
167
+ canAddBlock={
168
+ blockEditingMode === 'default'
169
+ }
277
170
  />
278
- )
279
- }
171
+ );
172
+ } }
280
173
  />
281
174
  </div>
282
175
  ) }
@@ -287,7 +180,22 @@ function UnforwardedLinkUI( props, ref ) {
287
180
  onBack={ () => {
288
181
  setAddingBlock( false );
289
182
  setFocusAddBlockButton( true );
183
+ setFocusAddPageButton( false );
184
+ } }
185
+ onBlockInsert={ props?.onBlockInsert }
186
+ />
187
+ ) }
188
+
189
+ { addingPage && (
190
+ <LinkUIPageCreator
191
+ postType={ postType }
192
+ onBack={ () => {
193
+ setAddingPage( false );
194
+ setFocusAddPageButton( true );
195
+ setFocusAddBlockButton( false );
290
196
  } }
197
+ onPageCreated={ handlePageCreated }
198
+ initialTitle={ link?.url || '' }
291
199
  />
292
200
  ) }
293
201
  </Popover>
@@ -296,9 +204,17 @@ function UnforwardedLinkUI( props, ref ) {
296
204
 
297
205
  export const LinkUI = forwardRef( UnforwardedLinkUI );
298
206
 
299
- const LinkUITools = ( { setAddingBlock, focusAddBlockButton } ) => {
207
+ const LinkUITools = ( {
208
+ setAddingBlock,
209
+ setAddingPage,
210
+ focusAddBlockButton,
211
+ focusAddPageButton,
212
+ canAddPage,
213
+ canAddBlock,
214
+ } ) => {
300
215
  const blockInserterAriaRole = 'listbox';
301
216
  const addBlockButtonRef = useRef();
217
+ const addPageButtonRef = useRef();
302
218
 
303
219
  // Focus the add block button when the popover is opened.
304
220
  useEffect( () => {
@@ -307,20 +223,48 @@ const LinkUITools = ( { setAddingBlock, focusAddBlockButton } ) => {
307
223
  }
308
224
  }, [ focusAddBlockButton ] );
309
225
 
226
+ // Focus the add page button when the popover is opened.
227
+ useEffect( () => {
228
+ if ( focusAddPageButton ) {
229
+ addPageButtonRef.current?.focus();
230
+ }
231
+ }, [ focusAddPageButton ] );
232
+
233
+ // Don't render anything if neither button should be shown
234
+ if ( ! canAddPage && ! canAddBlock ) {
235
+ return null;
236
+ }
237
+
310
238
  return (
311
- <VStack className="link-ui-tools">
312
- <Button
313
- __next40pxDefaultSize
314
- ref={ addBlockButtonRef }
315
- icon={ plus }
316
- onClick={ ( e ) => {
317
- e.preventDefault();
318
- setAddingBlock( true );
319
- } }
320
- aria-haspopup={ blockInserterAriaRole }
321
- >
322
- { __( 'Add block' ) }
323
- </Button>
239
+ <VStack spacing={ 0 } className="link-ui-tools">
240
+ { canAddPage && (
241
+ <Button
242
+ __next40pxDefaultSize
243
+ ref={ addPageButtonRef }
244
+ icon={ plus }
245
+ onClick={ ( e ) => {
246
+ e.preventDefault();
247
+ setAddingPage( true );
248
+ } }
249
+ aria-haspopup={ blockInserterAriaRole }
250
+ >
251
+ { __( 'Create page' ) }
252
+ </Button>
253
+ ) }
254
+ { canAddBlock && (
255
+ <Button
256
+ __next40pxDefaultSize
257
+ ref={ addBlockButtonRef }
258
+ icon={ plus }
259
+ onClick={ ( e ) => {
260
+ e.preventDefault();
261
+ setAddingBlock( true );
262
+ } }
263
+ aria-haspopup={ blockInserterAriaRole }
264
+ >
265
+ { __( 'Add block' ) }
266
+ </Button>
267
+ ) }
324
268
  </VStack>
325
269
  );
326
270
  };