@wordpress/block-library 8.31.0 → 8.32.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 (157) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/block/edit.js +5 -8
  3. package/build/block/edit.js.map +1 -1
  4. package/build/button/edit.native.js +1 -1
  5. package/build/button/edit.native.js.map +1 -1
  6. package/build/image/edit.js +10 -39
  7. package/build/image/edit.js.map +1 -1
  8. package/build/image/image.js +27 -6
  9. package/build/image/image.js.map +1 -1
  10. package/build/navigation/view.js +12 -2
  11. package/build/navigation/view.js.map +1 -1
  12. package/build/navigation-link/edit.js +41 -18
  13. package/build/navigation-link/edit.js.map +1 -1
  14. package/build/navigation-submenu/edit.js +27 -9
  15. package/build/navigation-submenu/edit.js.map +1 -1
  16. package/build/pattern/edit.js +3 -1
  17. package/build/pattern/edit.js.map +1 -1
  18. package/build/post-featured-image/edit.js +12 -3
  19. package/build/post-featured-image/edit.js.map +1 -1
  20. package/build/post-featured-image/index.js +8 -3
  21. package/build/post-featured-image/index.js.map +1 -1
  22. package/build/post-featured-image/overlay-controls.js +82 -0
  23. package/build/post-featured-image/overlay-controls.js.map +1 -0
  24. package/build/post-featured-image/overlay.js +5 -54
  25. package/build/post-featured-image/overlay.js.map +1 -1
  26. package/build/quote/edit.js +18 -23
  27. package/build/quote/edit.js.map +1 -1
  28. package/build/site-tagline/edit.js +13 -4
  29. package/build/site-tagline/edit.js.map +1 -1
  30. package/build/site-tagline/index.js +4 -0
  31. package/build/site-tagline/index.js.map +1 -1
  32. package/build/template-part/edit/index.js +55 -47
  33. package/build/template-part/edit/index.js.map +1 -1
  34. package/build/template-part/edit/inner-blocks.js +106 -10
  35. package/build/template-part/edit/inner-blocks.js.map +1 -1
  36. package/build/template-part/edit/selection-modal.js +1 -9
  37. package/build/template-part/edit/selection-modal.js.map +1 -1
  38. package/build/utils/caption.js +19 -13
  39. package/build/utils/caption.js.map +1 -1
  40. package/build/utils/hooks.js +1 -0
  41. package/build/utils/hooks.js.map +1 -1
  42. package/build-module/block/edit.js +5 -8
  43. package/build-module/block/edit.js.map +1 -1
  44. package/build-module/button/edit.native.js +1 -1
  45. package/build-module/button/edit.native.js.map +1 -1
  46. package/build-module/image/edit.js +11 -40
  47. package/build-module/image/edit.js.map +1 -1
  48. package/build-module/image/image.js +27 -6
  49. package/build-module/image/image.js.map +1 -1
  50. package/build-module/navigation/view.js +12 -2
  51. package/build-module/navigation/view.js.map +1 -1
  52. package/build-module/navigation-link/edit.js +43 -20
  53. package/build-module/navigation-link/edit.js.map +1 -1
  54. package/build-module/navigation-submenu/edit.js +27 -9
  55. package/build-module/navigation-submenu/edit.js.map +1 -1
  56. package/build-module/pattern/edit.js +3 -1
  57. package/build-module/pattern/edit.js.map +1 -1
  58. package/build-module/post-featured-image/edit.js +12 -3
  59. package/build-module/post-featured-image/edit.js.map +1 -1
  60. package/build-module/post-featured-image/index.js +8 -3
  61. package/build-module/post-featured-image/index.js.map +1 -1
  62. package/build-module/post-featured-image/overlay-controls.js +75 -0
  63. package/build-module/post-featured-image/overlay-controls.js.map +1 -0
  64. package/build-module/post-featured-image/overlay.js +7 -56
  65. package/build-module/post-featured-image/overlay.js.map +1 -1
  66. package/build-module/quote/edit.js +20 -25
  67. package/build-module/quote/edit.js.map +1 -1
  68. package/build-module/site-tagline/edit.js +14 -5
  69. package/build-module/site-tagline/edit.js.map +1 -1
  70. package/build-module/site-tagline/index.js +4 -0
  71. package/build-module/site-tagline/index.js.map +1 -1
  72. package/build-module/template-part/edit/index.js +58 -50
  73. package/build-module/template-part/edit/index.js.map +1 -1
  74. package/build-module/template-part/edit/inner-blocks.js +108 -12
  75. package/build-module/template-part/edit/inner-blocks.js.map +1 -1
  76. package/build-module/template-part/edit/selection-modal.js +2 -10
  77. package/build-module/template-part/edit/selection-modal.js.map +1 -1
  78. package/build-module/utils/caption.js +19 -13
  79. package/build-module/utils/caption.js.map +1 -1
  80. package/build-module/utils/hooks.js +1 -0
  81. package/build-module/utils/hooks.js.map +1 -1
  82. package/build-style/audio/theme-rtl.css +1 -1
  83. package/build-style/audio/theme.css +1 -1
  84. package/build-style/cover/style-rtl.css +5 -2
  85. package/build-style/cover/style.css +5 -2
  86. package/build-style/editor-rtl.css +12 -8
  87. package/build-style/editor.css +12 -8
  88. package/build-style/embed/theme-rtl.css +1 -1
  89. package/build-style/embed/theme.css +1 -1
  90. package/build-style/image/theme-rtl.css +1 -1
  91. package/build-style/image/theme.css +1 -1
  92. package/build-style/pullquote/theme-rtl.css +2 -1
  93. package/build-style/pullquote/theme.css +2 -1
  94. package/build-style/quote/theme-rtl.css +6 -6
  95. package/build-style/quote/theme.css +6 -6
  96. package/build-style/search/style-rtl.css +10 -0
  97. package/build-style/search/style.css +10 -0
  98. package/build-style/social-links/editor-rtl.css +0 -4
  99. package/build-style/social-links/editor.css +0 -4
  100. package/build-style/style-rtl.css +15 -2
  101. package/build-style/style.css +15 -2
  102. package/build-style/table/theme-rtl.css +4 -3
  103. package/build-style/table/theme.css +4 -3
  104. package/build-style/template-part/editor-rtl.css +12 -4
  105. package/build-style/template-part/editor.css +12 -4
  106. package/build-style/template-part/theme-rtl.css +1 -1
  107. package/build-style/template-part/theme.css +1 -1
  108. package/build-style/theme-rtl.css +17 -15
  109. package/build-style/theme.css +17 -15
  110. package/build-style/video/theme-rtl.css +1 -1
  111. package/build-style/video/theme.css +1 -1
  112. package/package.json +34 -34
  113. package/src/audio/theme.scss +1 -1
  114. package/src/block/edit.js +5 -17
  115. package/src/button/edit.native.js +1 -1
  116. package/src/cover/style.scss +6 -2
  117. package/src/embed/theme.scss +1 -1
  118. package/src/gallery/editor.scss +1 -1
  119. package/src/gallery/index.php +1 -1
  120. package/src/image/edit.js +11 -40
  121. package/src/image/editor.scss +2 -2
  122. package/src/image/image.js +25 -7
  123. package/src/image/theme.scss +1 -1
  124. package/src/navigation/index.php +8 -0
  125. package/src/navigation/view.js +11 -2
  126. package/src/navigation-link/edit.js +53 -27
  127. package/src/navigation-submenu/edit.js +30 -10
  128. package/src/pattern/edit.js +4 -0
  129. package/src/post-featured-image/block.json +8 -3
  130. package/src/post-featured-image/edit.js +12 -1
  131. package/src/post-featured-image/editor.scss +1 -1
  132. package/src/post-featured-image/overlay-controls.js +88 -0
  133. package/src/post-featured-image/overlay.js +17 -84
  134. package/src/pullquote/theme.scss +3 -1
  135. package/src/query-no-results/index.php +2 -0
  136. package/src/query-pagination-next/index.php +2 -0
  137. package/src/query-pagination-numbers/index.php +2 -0
  138. package/src/quote/edit.js +27 -43
  139. package/src/quote/test/edit.native.js +4 -6
  140. package/src/quote/theme.scss +1 -2
  141. package/src/search/style.scss +11 -0
  142. package/src/site-logo/editor.scss +2 -2
  143. package/src/site-tagline/block.json +4 -0
  144. package/src/site-tagline/edit.js +16 -3
  145. package/src/site-tagline/index.php +9 -1
  146. package/src/social-links/editor.scss +1 -9
  147. package/src/table/theme.scss +4 -2
  148. package/src/template-part/edit/index.js +87 -79
  149. package/src/template-part/edit/inner-blocks.js +126 -13
  150. package/src/template-part/edit/selection-modal.js +1 -22
  151. package/src/template-part/editor.scss +11 -3
  152. package/src/template-part/index.php +2 -0
  153. package/src/template-part/theme.scss +1 -1
  154. package/src/utils/caption.js +19 -16
  155. package/src/utils/hooks.js +1 -0
  156. package/src/video/editor.scss +2 -2
  157. package/src/video/theme.scss +1 -1
package/src/block/edit.js CHANGED
@@ -38,7 +38,7 @@ import { name as patternBlockName } from './index';
38
38
  import { unlock } from '../lock-unlock';
39
39
 
40
40
  const { useLayoutClasses } = unlock( blockEditorPrivateApis );
41
- const { PARTIAL_SYNCING_SUPPORTED_BLOCKS } = unlock( patternsPrivateApis );
41
+ const { isOverridableBlock } = unlock( patternsPrivateApis );
42
42
 
43
43
  const fullAlignments = [ 'full', 'wide', 'left', 'right' ];
44
44
 
@@ -90,21 +90,9 @@ const useInferredLayout = ( blocks, parentLayout ) => {
90
90
  }, [ blocks, parentLayout ] );
91
91
  };
92
92
 
93
- function hasOverridableAttributes( block ) {
94
- return (
95
- Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes(
96
- block.name
97
- ) &&
98
- !! block.attributes.metadata?.bindings &&
99
- Object.values( block.attributes.metadata.bindings ).some(
100
- ( binding ) => binding.source === 'core/pattern-overrides'
101
- )
102
- );
103
- }
104
-
105
93
  function hasOverridableBlocks( blocks ) {
106
94
  return blocks.some( ( block ) => {
107
- if ( hasOverridableAttributes( block ) ) return true;
95
+ if ( isOverridableBlock( block ) ) return true;
108
96
  return hasOverridableBlocks( block.innerBlocks );
109
97
  } );
110
98
  }
@@ -133,7 +121,7 @@ function applyInitialContentValuesToInnerBlocks(
133
121
  const metadataName =
134
122
  legacyIdMap?.[ block.clientId ] ?? block.attributes.metadata?.name;
135
123
 
136
- if ( ! metadataName || ! hasOverridableAttributes( block ) ) {
124
+ if ( ! metadataName || ! isOverridableBlock( block ) ) {
137
125
  return { ...block, innerBlocks };
138
126
  }
139
127
 
@@ -184,7 +172,7 @@ function getContentValuesFromInnerBlocks( blocks, defaultValues, legacyIdMap ) {
184
172
  }
185
173
  const metadataName =
186
174
  legacyIdMap?.[ block.clientId ] ?? block.attributes.metadata?.name;
187
- if ( ! metadataName || ! hasOverridableAttributes( block ) ) {
175
+ if ( ! metadataName || ! isOverridableBlock( block ) ) {
188
176
  continue;
189
177
  }
190
178
 
@@ -217,7 +205,7 @@ function setBlockEditMode( setEditMode, blocks, mode ) {
217
205
  blocks.forEach( ( block ) => {
218
206
  const editMode =
219
207
  mode ||
220
- ( hasOverridableAttributes( block ) ? 'contentOnly' : 'disabled' );
208
+ ( isOverridableBlock( block ) ? 'contentOnly' : 'disabled' );
221
209
  setEditMode( block.clientId, editMode );
222
210
 
223
211
  setBlockEditMode(
@@ -494,7 +494,7 @@ function ButtonEdit( props ) {
494
494
  <View pointerEvents="none" style={ outLineStyles } />
495
495
  ) }
496
496
  <RichText
497
- setRef={ onSetRef }
497
+ ref={ onSetRef }
498
498
  placeholder={ placeholderText }
499
499
  value={ text }
500
500
  onChange={ onChangeText }
@@ -1,12 +1,16 @@
1
+ // Lowest specificity styles are used to ensure that the default styles for the cover block can be overridden by global styles.
2
+ :where(.wp-block-cover-image, .wp-block-cover) {
3
+ min-height: 430px;
4
+ padding: 1em;
5
+ }
6
+
1
7
  .wp-block-cover-image,
2
8
  .wp-block-cover {
3
9
  position: relative;
4
10
  background-position: center center;
5
- min-height: 430px;
6
11
  display: flex;
7
12
  justify-content: center;
8
13
  align-items: center;
9
- padding: 1em;
10
14
  // Prevent the `wp-block-cover__background` span from overflowing the container when border-radius is applied.
11
15
  // `overflow: hidden` is provided as a fallback for browsers that don't support `overflow: clip`.
12
16
  overflow: hidden;
@@ -2,6 +2,6 @@
2
2
  @include caption-style-theme();
3
3
  }
4
4
 
5
- .wp-block-embed {
5
+ :where(.wp-block-embed) {
6
6
  margin: 0 0 1em 0;
7
7
  }
@@ -27,7 +27,7 @@ figure.wp-block-gallery {
27
27
  }
28
28
  }
29
29
 
30
- // @todo: this deserves a refactor, by being moved to the toolbar.
30
+ // @todo this deserves a refactor, by being moved to the toolbar.
31
31
  .block-editor-media-placeholder.is-appender {
32
32
  .components-placeholder__label {
33
33
  display: none;
@@ -131,7 +131,7 @@ function block_core_gallery_render( $attributes, $content ) {
131
131
  * the `$parsed_block['innerBlocks']` via the `render_block_data` hook.
132
132
  * However, this hook doesn't apply inner block updates when blocks are
133
133
  * nested.
134
- * @todo: In the future, if this hook supports updating innerBlocks in
134
+ * @todo In the future, if this hook supports updating innerBlocks in
135
135
  * nested blocks, it should be refactored.
136
136
  *
137
137
  * @see: https://github.com/WordPress/gutenberg/pull/58733
package/src/image/edit.js CHANGED
@@ -6,7 +6,7 @@ import classnames from 'classnames';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob';
9
+ import { isBlobURL } from '@wordpress/blob';
10
10
  import { store as blocksStore } from '@wordpress/blocks';
11
11
  import { Placeholder } from '@wordpress/components';
12
12
  import { useDispatch, useSelect } from '@wordpress/data';
@@ -28,6 +28,7 @@ import { store as noticesStore } from '@wordpress/notices';
28
28
  * Internal dependencies
29
29
  */
30
30
  import { unlock } from '../lock-unlock';
31
+ import { useUploadMediaFromBlobURL } from '../utils/hooks';
31
32
  import Image from './image';
32
33
 
33
34
  /**
@@ -116,7 +117,9 @@ export function ImageEdit( {
116
117
  align,
117
118
  metadata,
118
119
  } = attributes;
119
- const [ temporaryURL, setTemporaryURL ] = useState();
120
+ const [ temporaryURL, setTemporaryURL ] = useState( () => {
121
+ return isTemporaryImage( id, url ) ? url : undefined;
122
+ } );
120
123
 
121
124
  const altRef = useRef();
122
125
  useEffect( () => {
@@ -267,44 +270,12 @@ export function ImageEdit( {
267
270
  }
268
271
  }
269
272
 
270
- let isTemp = isTemporaryImage( id, url );
271
-
272
- // Upload a temporary image on mount.
273
- useEffect( () => {
274
- if ( ! isTemp ) {
275
- return;
276
- }
277
-
278
- const file = getBlobByURL( url );
279
-
280
- if ( file ) {
281
- const { mediaUpload } = getSettings();
282
- if ( ! mediaUpload ) {
283
- return;
284
- }
285
- mediaUpload( {
286
- filesList: [ file ],
287
- onFileChange: ( [ img ] ) => {
288
- onSelectImage( img );
289
- },
290
- allowedTypes: ALLOWED_MEDIA_TYPES,
291
- onError: ( message ) => {
292
- isTemp = false;
293
- onUploadError( message );
294
- },
295
- } );
296
- }
297
- }, [] );
298
-
299
- // If an image is temporary, revoke the Blob url when it is uploaded (and is
300
- // no longer temporary).
301
- useEffect( () => {
302
- if ( isTemp ) {
303
- setTemporaryURL( url );
304
- return;
305
- }
306
- revokeBlobURL( temporaryURL );
307
- }, [ isTemp, url ] );
273
+ useUploadMediaFromBlobURL( {
274
+ url,
275
+ allowedTypes: ALLOWED_MEDIA_TYPES,
276
+ onChange: onSelectImage,
277
+ onError: onUploadError,
278
+ } );
308
279
 
309
280
  const isExternal = isExternalImage( id, url );
310
281
  const src = isExternal ? url : undefined;
@@ -1,5 +1,5 @@
1
1
  // Provide special styling for the placeholder.
2
- // @todo: this particular minimal style of placeholder could be componentized further.
2
+ // @todo this particular minimal style of placeholder could be componentized further.
3
3
  .wp-block-image.wp-block-image {
4
4
 
5
5
  // Show Placeholder style on-select.
@@ -13,7 +13,7 @@
13
13
  // Disable any duotone filter applied in the selected state.
14
14
  filter: none !important;
15
15
 
16
- // @todo: this should eventually be overridden by a custom border-radius set in the inspector.
16
+ // @todo this should eventually be overridden by a custom border-radius set in the inspector.
17
17
  border-radius: $radius-block-ui;
18
18
 
19
19
  > svg {
@@ -83,8 +83,7 @@ const ImageWrapper = ( { href, children } ) => {
83
83
  // When the Image block is linked,
84
84
  // it's wrapped with a disabled <a /> tag.
85
85
  // Restore cursor style so it doesn't appear 'clickable'
86
- // and remove pointer events. Safari needs the display property.
87
- pointerEvents: 'none',
86
+ // Safari needs the display property.
88
87
  cursor: 'default',
89
88
  display: 'inline',
90
89
  } }
@@ -274,6 +273,22 @@ export default function Image( {
274
273
  }
275
274
  }
276
275
 
276
+ function resetLightbox() {
277
+ // When deleting a link from an image while lightbox settings
278
+ // are enabled by default, we should disable the lightbox,
279
+ // otherwise the resulting UX looks like a mistake.
280
+ // See https://github.com/WordPress/gutenberg/pull/59890/files#r1532286123.
281
+ if ( lightboxSetting?.enabled && lightboxSetting?.allowEditing ) {
282
+ setAttributes( {
283
+ lightbox: { enabled: false },
284
+ } );
285
+ } else {
286
+ setAttributes( {
287
+ lightbox: undefined,
288
+ } );
289
+ }
290
+ }
291
+
277
292
  function onSetTitle( value ) {
278
293
  // This is the HTML title attribute, separate from the media object
279
294
  // title.
@@ -348,7 +363,10 @@ export default function Image( {
348
363
  const [ lightboxSetting ] = useSettings( 'lightbox' );
349
364
 
350
365
  const showLightboxSetting =
351
- !! lightbox || lightboxSetting?.allowEditing === true;
366
+ // If a block-level override is set, we should give users the option to
367
+ // remove that override, even if the lightbox UI is disabled in the settings.
368
+ ( !! lightbox && lightbox?.enabled !== lightboxSetting?.enabled ) ||
369
+ lightboxSetting?.allowEditing;
352
370
 
353
371
  const lightboxChecked =
354
372
  !! lightbox?.enabled || ( ! lightbox && !! lightboxSetting?.enabled );
@@ -498,6 +516,7 @@ export default function Image( {
498
516
  showLightboxSetting={ showLightboxSetting }
499
517
  lightboxEnabled={ lightboxChecked }
500
518
  onSetLightbox={ onSetLightbox }
519
+ resetLightbox={ resetLightbox }
501
520
  />
502
521
  ) }
503
522
  { allowCrop && (
@@ -692,6 +711,7 @@ export default function Image( {
692
711
  <InspectorControls group="advanced">
693
712
  <TextControl
694
713
  __nextHasNoMarginBottom
714
+ __next40pxDefaultSize
695
715
  label={ __( 'Title attribute' ) }
696
716
  value={ title || '' }
697
717
  onChange={ onSetTitle }
@@ -913,9 +933,7 @@ export default function Image( {
913
933
 
914
934
  return (
915
935
  <>
916
- { /* Hide controls during upload to avoid component remount,
917
- which causes duplicated image upload. */ }
918
- { ! temporaryURL && controls }
936
+ { controls }
919
937
  { img }
920
938
 
921
939
  <Caption
@@ -925,7 +943,7 @@ export default function Image( {
925
943
  insertBlocksAfter={ insertBlocksAfter }
926
944
  label={ __( 'Image caption text' ) }
927
945
  showToolbarButton={ isSingleSelected && hasNonContentControls }
928
- disableEditing={ lockCaption }
946
+ readOnly={ lockCaption }
929
947
  />
930
948
  </>
931
949
  );
@@ -2,6 +2,6 @@
2
2
  @include caption-style-theme();
3
3
  }
4
4
 
5
- .wp-block-image {
5
+ :where(.wp-block-image) {
6
6
  margin: 0 0 1em 0;
7
7
  }
@@ -1520,6 +1520,14 @@ function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
1520
1520
  return $post;
1521
1521
  }
1522
1522
 
1523
+ /**
1524
+ * Skip meta generation when consumers intentionally update specific Navigation fields
1525
+ * and omit the content update.
1526
+ */
1527
+ if ( ! isset( $post->post_content ) ) {
1528
+ return $post;
1529
+ }
1530
+
1523
1531
  /*
1524
1532
  * We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into
1525
1533
  * all anchor blocks. For the root level, we create a mock Navigation and extract them from there.
@@ -62,11 +62,20 @@ const { state, actions } = store(
62
62
  // Only open on hover if the overlay is closed.
63
63
  Object.values( overlayOpenedBy || {} ).filter( Boolean )
64
64
  .length === 0
65
- )
65
+ ) {
66
66
  actions.openMenu( 'hover' );
67
+ }
67
68
  },
68
69
  closeMenuOnHover() {
69
- actions.closeMenu( 'hover' );
70
+ const { type, overlayOpenedBy } = getContext();
71
+ if (
72
+ type === 'submenu' &&
73
+ // Only close on hover if the overlay is closed.
74
+ Object.values( overlayOpenedBy || {} ).filter( Boolean )
75
+ .length === 0
76
+ ) {
77
+ actions.closeMenu( 'hover' );
78
+ }
70
79
  },
71
80
  openMenuOnClick() {
72
81
  const ctx = getContext();
@@ -29,14 +29,11 @@ import {
29
29
  } from '@wordpress/block-editor';
30
30
  import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url';
31
31
  import { useState, useEffect, useRef } from '@wordpress/element';
32
- import {
33
- placeCaretAtHorizontalEdge,
34
- __unstableStripHTML as stripHTML,
35
- } from '@wordpress/dom';
32
+ import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
36
33
  import { decodeEntities } from '@wordpress/html-entities';
37
34
  import { link as linkIcon, addSubmenu } from '@wordpress/icons';
38
35
  import { store as coreStore } from '@wordpress/core-data';
39
- import { useMergeRefs } from '@wordpress/compose';
36
+ import { useMergeRefs, usePrevious } from '@wordpress/compose';
40
37
 
41
38
  /**
42
39
  * Internal dependencies
@@ -93,7 +90,7 @@ const useIsDraggingWithin = ( elementRef ) => {
93
90
  ownerDocument.removeEventListener( 'dragend', handleDragEnd );
94
91
  ownerDocument.removeEventListener( 'dragenter', handleDragEnter );
95
92
  };
96
- }, [] );
93
+ }, [ elementRef ] );
97
94
 
98
95
  return isDraggingWithin;
99
96
  };
@@ -171,9 +168,14 @@ export default function NavigationLinkEdit( {
171
168
  const [ isInvalid, isDraft ] = useIsInvalidLink( kind, type, id );
172
169
  const { maxNestingLevel } = context;
173
170
 
174
- const { replaceBlock, __unstableMarkNextChangeAsNotPersistent } =
175
- useDispatch( blockEditorStore );
171
+ const {
172
+ replaceBlock,
173
+ __unstableMarkNextChangeAsNotPersistent,
174
+ selectPreviousBlock,
175
+ } = useDispatch( blockEditorStore );
176
176
  const [ isLinkOpen, setIsLinkOpen ] = useState( false );
177
+ // Store what element opened the popover, so we know where to return focus to (toolbar button vs navigation link text)
178
+ const [ openedBy, setOpenedBy ] = useState( null );
177
179
  // Use internal state instead of a ref to make sure that the component
178
180
  // re-renders when the popover's anchor updates.
179
181
  const [ popoverAnchor, setPopoverAnchor ] = useState( null );
@@ -181,6 +183,7 @@ export default function NavigationLinkEdit( {
181
183
  const isDraggingWithin = useIsDraggingWithin( listItemRef );
182
184
  const itemLabelPlaceholder = __( 'Add label…' );
183
185
  const ref = useRef();
186
+ const prevUrl = usePrevious( url );
184
187
 
185
188
  // Change the label using inspector causes rich text to change focus on firefox.
186
189
  // This is a workaround to keep the focus on the label field when label filed is focused we don't render the rich text.
@@ -226,7 +229,7 @@ export default function NavigationLinkEdit( {
226
229
  /**
227
230
  * Transform to submenu block.
228
231
  */
229
- function transformToSubmenu() {
232
+ const transformToSubmenu = () => {
230
233
  const newSubmenu = createBlock(
231
234
  'core/navigation-submenu',
232
235
  attributes,
@@ -235,7 +238,7 @@ export default function NavigationLinkEdit( {
235
238
  : [ createBlock( 'core/navigation-link' ) ]
236
239
  );
237
240
  replaceBlock( clientId, newSubmenu );
238
- }
241
+ };
239
242
 
240
243
  useEffect( () => {
241
244
  // Show the LinkControl on mount if the URL is empty
@@ -269,20 +272,18 @@ export default function NavigationLinkEdit( {
269
272
 
270
273
  // If the LinkControl popover is open and the URL has changed, close the LinkControl and focus the label text.
271
274
  useEffect( () => {
272
- if ( isLinkOpen && url ) {
273
- // Does this look like a URL and have something TLD-ish?
274
- if (
275
- isURL( prependHTTP( label ) ) &&
276
- /^.+\.[a-z]+/.test( label )
277
- ) {
278
- // Focus and select the label text.
279
- selectLabelText();
280
- } else {
281
- // Focus it (but do not select).
282
- placeCaretAtHorizontalEdge( ref.current, true );
283
- }
275
+ // We only want to do this when the URL has gone from nothing to a new URL AND the label looks like a URL
276
+ if (
277
+ ! prevUrl &&
278
+ url &&
279
+ isLinkOpen &&
280
+ isURL( prependHTTP( label ) ) &&
281
+ /^.+\.[a-z]+/.test( label )
282
+ ) {
283
+ // Focus and select the label text.
284
+ selectLabelText();
284
285
  }
285
- }, [ url, isLinkOpen, label ] );
286
+ }, [ prevUrl, url, isLinkOpen, label ] );
286
287
 
287
288
  /**
288
289
  * Focus the Link label text and select it.
@@ -334,7 +335,10 @@ export default function NavigationLinkEdit( {
334
335
  // as it shares the CMD+K shortcut.
335
336
  // See https://github.com/WordPress/gutenberg/pull/59845.
336
337
  event.preventDefault();
338
+ // If this link is a child of a parent submenu item, the parent submenu item event will also open, closing this popover
339
+ event.stopPropagation();
337
340
  setIsLinkOpen( true );
341
+ setOpenedBy( ref.current );
338
342
  }
339
343
  }
340
344
 
@@ -373,6 +377,7 @@ export default function NavigationLinkEdit( {
373
377
  if ( ! url || isInvalid || isDraft ) {
374
378
  blockProps.onClick = () => {
375
379
  setIsLinkOpen( true );
380
+ setOpenedBy( ref.current );
376
381
  };
377
382
  }
378
383
 
@@ -399,7 +404,10 @@ export default function NavigationLinkEdit( {
399
404
  icon={ linkIcon }
400
405
  title={ __( 'Link' ) }
401
406
  shortcut={ displayShortcut.primary( 'k' ) }
402
- onClick={ () => setIsLinkOpen( true ) }
407
+ onClick={ ( event ) => {
408
+ setIsLinkOpen( true );
409
+ setOpenedBy( event.currentTarget );
410
+ } }
403
411
  />
404
412
  { ! isAtMaxNesting && (
405
413
  <ToolbarButton
@@ -416,17 +424,19 @@ export default function NavigationLinkEdit( {
416
424
  <PanelBody title={ __( 'Settings' ) }>
417
425
  <TextControl
418
426
  __nextHasNoMarginBottom
427
+ __next40pxDefaultSize
419
428
  value={ label ? stripHTML( label ) : '' }
420
429
  onChange={ ( labelValue ) => {
421
430
  setAttributes( { label: labelValue } );
422
431
  } }
423
- label={ __( 'Label' ) }
432
+ label={ __( 'Text' ) }
424
433
  autoComplete="off"
425
434
  onFocus={ () => setIsLabelFieldFocused( true ) }
426
435
  onBlur={ () => setIsLabelFieldFocused( false ) }
427
436
  />
428
437
  <TextControl
429
438
  __nextHasNoMarginBottom
439
+ __next40pxDefaultSize
430
440
  value={ url ? safeDecodeURI( url ) : '' }
431
441
  onChange={ ( urlValue ) => {
432
442
  updateAttributes(
@@ -435,7 +445,7 @@ export default function NavigationLinkEdit( {
435
445
  attributes
436
446
  );
437
447
  } }
438
- label={ __( 'URL' ) }
448
+ label={ __( 'Link' ) }
439
449
  autoComplete="off"
440
450
  />
441
451
  <TextareaControl
@@ -451,6 +461,7 @@ export default function NavigationLinkEdit( {
451
461
  />
452
462
  <TextControl
453
463
  __nextHasNoMarginBottom
464
+ __next40pxDefaultSize
454
465
  value={ title || '' }
455
466
  onChange={ ( titleValue ) => {
456
467
  setAttributes( { title: titleValue } );
@@ -463,6 +474,7 @@ export default function NavigationLinkEdit( {
463
474
  />
464
475
  <TextControl
465
476
  __nextHasNoMarginBottom
477
+ __next40pxDefaultSize
466
478
  value={ rel || '' }
467
479
  onChange={ ( relValue ) => {
468
480
  setAttributes( { rel: relValue } );
@@ -565,10 +577,24 @@ export default function NavigationLinkEdit( {
565
577
  // If there is no link then remove the auto-inserted block.
566
578
  // This avoids empty blocks which can provided a poor UX.
567
579
  if ( ! url ) {
568
- // Need to handle refocusing the Nav block or the inserter?
580
+ // Select the previous block to keep focus nearby
581
+ selectPreviousBlock( clientId, true );
582
+ // Remove the link.
569
583
  onReplace( [] );
584
+ return;
570
585
  }
586
+
571
587
  setIsLinkOpen( false );
588
+ if ( openedBy ) {
589
+ openedBy.focus();
590
+ setOpenedBy( null );
591
+ } else if ( ref.current ) {
592
+ // select the ref when adding a new link
593
+ ref.current.focus();
594
+ } else {
595
+ // Fallback
596
+ selectPreviousBlock( clientId, true );
597
+ }
572
598
  } }
573
599
  anchor={ popoverAnchor }
574
600
  onRemove={ removeLink }
@@ -28,7 +28,6 @@ import {
28
28
  } from '@wordpress/block-editor';
29
29
  import { isURL, prependHTTP } from '@wordpress/url';
30
30
  import { useState, useEffect, useRef } from '@wordpress/element';
31
- import { placeCaretAtHorizontalEdge } from '@wordpress/dom';
32
31
  import { link as linkIcon, removeSubmenu } from '@wordpress/icons';
33
32
  import { useResourcePermissions } from '@wordpress/core-data';
34
33
  import { speak } from '@wordpress/a11y';
@@ -139,9 +138,14 @@ export default function NavigationSubmenuEdit( {
139
138
 
140
139
  const { showSubmenuIcon, maxNestingLevel, openSubmenusOnClick } = context;
141
140
 
142
- const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } =
143
- useDispatch( blockEditorStore );
141
+ const {
142
+ __unstableMarkNextChangeAsNotPersistent,
143
+ replaceBlock,
144
+ selectBlock,
145
+ } = useDispatch( blockEditorStore );
144
146
  const [ isLinkOpen, setIsLinkOpen ] = useState( false );
147
+ // Store what element opened the popover, so we know where to return focus to (toolbar button vs navigation link text)
148
+ const [ openedBy, setOpenedBy ] = useState( null );
145
149
  // Use internal state instead of a ref to make sure that the component
146
150
  // re-renders when the popover's anchor updates.
147
151
  const [ popoverAnchor, setPopoverAnchor ] = useState( null );
@@ -241,9 +245,6 @@ export default function NavigationSubmenuEdit( {
241
245
  ) {
242
246
  // Focus and select the label text.
243
247
  selectLabelText();
244
- } else {
245
- // Focus it (but do not select).
246
- placeCaretAtHorizontalEdge( ref.current, true );
247
248
  }
248
249
  }
249
250
  }, [ url ] );
@@ -283,7 +284,10 @@ export default function NavigationSubmenuEdit( {
283
284
  // as it shares the CMD+K shortcut.
284
285
  // See https://github.com/WordPress/gutenberg/pull/59845.
285
286
  event.preventDefault();
287
+ // If we don't stop propogation, this event bubbles up to the parent submenu item
288
+ event.stopPropagation();
286
289
  setIsLinkOpen( true );
290
+ setOpenedBy( ref.current );
287
291
  }
288
292
  }
289
293
 
@@ -370,7 +374,10 @@ export default function NavigationSubmenuEdit( {
370
374
  icon={ linkIcon }
371
375
  title={ __( 'Link' ) }
372
376
  shortcut={ displayShortcut.primary( 'k' ) }
373
- onClick={ () => setIsLinkOpen( true ) }
377
+ onClick={ ( event ) => {
378
+ setIsLinkOpen( true );
379
+ setOpenedBy( event.currentTarget );
380
+ } }
374
381
  />
375
382
  ) }
376
383
 
@@ -389,20 +396,22 @@ export default function NavigationSubmenuEdit( {
389
396
  <PanelBody title={ __( 'Settings' ) }>
390
397
  <TextControl
391
398
  __nextHasNoMarginBottom
399
+ __next40pxDefaultSize
392
400
  value={ label || '' }
393
401
  onChange={ ( labelValue ) => {
394
402
  setAttributes( { label: labelValue } );
395
403
  } }
396
- label={ __( 'Label' ) }
404
+ label={ __( 'Text' ) }
397
405
  autoComplete="off"
398
406
  />
399
407
  <TextControl
400
408
  __nextHasNoMarginBottom
409
+ __next40pxDefaultSize
401
410
  value={ url || '' }
402
411
  onChange={ ( urlValue ) => {
403
412
  setAttributes( { url: urlValue } );
404
413
  } }
405
- label={ __( 'URL' ) }
414
+ label={ __( 'Link' ) }
406
415
  autoComplete="off"
407
416
  />
408
417
  <TextareaControl
@@ -420,6 +429,7 @@ export default function NavigationSubmenuEdit( {
420
429
  />
421
430
  <TextControl
422
431
  __nextHasNoMarginBottom
432
+ __next40pxDefaultSize
423
433
  value={ title || '' }
424
434
  onChange={ ( titleValue ) => {
425
435
  setAttributes( { title: titleValue } );
@@ -432,6 +442,7 @@ export default function NavigationSubmenuEdit( {
432
442
  />
433
443
  <TextControl
434
444
  __nextHasNoMarginBottom
445
+ __next40pxDefaultSize
435
446
  value={ rel || '' }
436
447
  onChange={ ( relValue ) => {
437
448
  setAttributes( { rel: relValue } );
@@ -471,6 +482,7 @@ export default function NavigationSubmenuEdit( {
471
482
  onClick={ () => {
472
483
  if ( ! openSubmenusOnClick && ! url ) {
473
484
  setIsLinkOpen( true );
485
+ setOpenedBy( ref.current );
474
486
  }
475
487
  } }
476
488
  />
@@ -479,7 +491,15 @@ export default function NavigationSubmenuEdit( {
479
491
  <LinkUI
480
492
  clientId={ clientId }
481
493
  link={ attributes }
482
- onClose={ () => setIsLinkOpen( false ) }
494
+ onClose={ () => {
495
+ setIsLinkOpen( false );
496
+ if ( openedBy ) {
497
+ openedBy.focus();
498
+ setOpenedBy( null );
499
+ } else {
500
+ selectBlock( clientId );
501
+ }
502
+ } }
483
503
  anchor={ popoverAnchor }
484
504
  hasCreateSuggestion={ userCanCreate }
485
505
  onRemove={ () => {
@@ -108,6 +108,10 @@ const PatternEdit = ( { attributes, clientId } ) => {
108
108
  metadata: {
109
109
  ...clonedBlocks[ 0 ].attributes.metadata,
110
110
  categories: selectedPattern.categories,
111
+ patternName: selectedPattern.name,
112
+ name:
113
+ clonedBlocks[ 0 ].attributes.metadata.name ||
114
+ selectedPattern.title,
111
115
  },
112
116
  };
113
117
  }