@wordpress/block-library 9.37.2-next.ba3aee3a2.0 → 9.38.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 (238) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/build/audio/index.cjs +16 -8
  3. package/build/audio/index.cjs.map +2 -2
  4. package/build/block-keyboard-shortcuts/index.cjs +2 -4
  5. package/build/block-keyboard-shortcuts/index.cjs.map +2 -2
  6. package/build/button/index.cjs +17 -8
  7. package/build/button/index.cjs.map +2 -2
  8. package/build/code/index.cjs +3 -1
  9. package/build/code/index.cjs.map +2 -2
  10. package/build/comments-title/edit.cjs +10 -6
  11. package/build/comments-title/edit.cjs.map +2 -2
  12. package/build/cover/index.cjs +20 -10
  13. package/build/cover/index.cjs.map +2 -2
  14. package/build/details/index.cjs +3 -1
  15. package/build/details/index.cjs.map +2 -2
  16. package/build/file/index.cjs +19 -9
  17. package/build/file/index.cjs.map +2 -2
  18. package/build/heading/block.json +1 -3
  19. package/build/heading/deprecated.cjs +101 -5
  20. package/build/heading/deprecated.cjs.map +3 -3
  21. package/build/heading/edit.cjs +20 -41
  22. package/build/heading/edit.cjs.map +3 -3
  23. package/build/heading/index.cjs +3 -1
  24. package/build/heading/index.cjs.map +2 -2
  25. package/build/heading/save.cjs +2 -16
  26. package/build/heading/save.cjs.map +3 -3
  27. package/build/heading/transforms.cjs +16 -3
  28. package/build/heading/transforms.cjs.map +2 -2
  29. package/build/image/image.cjs +3 -3
  30. package/build/image/image.cjs.map +2 -2
  31. package/build/image/index.cjs +33 -17
  32. package/build/image/index.cjs.map +2 -2
  33. package/build/list-item/index.cjs +3 -1
  34. package/build/list-item/index.cjs.map +2 -2
  35. package/build/media-text/index.cjs +30 -15
  36. package/build/media-text/index.cjs.map +2 -2
  37. package/build/more/block.json +1 -0
  38. package/build/more/index.cjs +3 -1
  39. package/build/more/index.cjs.map +2 -2
  40. package/build/navigation/edit/index.cjs +7 -6
  41. package/build/navigation/edit/index.cjs.map +2 -2
  42. package/build/navigation/edit/responsive-wrapper.cjs +7 -4
  43. package/build/navigation/edit/responsive-wrapper.cjs.map +2 -2
  44. package/build/navigation/edit/use-create-overlay.cjs +2 -0
  45. package/build/navigation/edit/use-create-overlay.cjs.map +2 -2
  46. package/build/navigation-link/edit.cjs +8 -58
  47. package/build/navigation-link/edit.cjs.map +3 -3
  48. package/build/navigation-link/index.cjs +13 -8
  49. package/build/navigation-link/index.cjs.map +2 -2
  50. package/build/navigation-link/shared/index.cjs +11 -2
  51. package/build/navigation-link/shared/index.cjs.map +2 -2
  52. package/build/navigation-link/shared/invalid-draft-display.cjs +82 -0
  53. package/build/navigation-link/shared/invalid-draft-display.cjs.map +7 -0
  54. package/build/navigation-link/shared/use-enable-link-status-validation.cjs +50 -0
  55. package/build/navigation-link/shared/use-enable-link-status-validation.cjs.map +7 -0
  56. package/build/navigation-link/shared/use-is-invalid-link.cjs +64 -0
  57. package/build/navigation-link/shared/use-is-invalid-link.cjs.map +7 -0
  58. package/build/navigation-overlay-close/block.json +1 -4
  59. package/build/navigation-overlay-close/index.cjs +3 -25
  60. package/build/navigation-overlay-close/index.cjs.map +3 -3
  61. package/build/navigation-submenu/edit.cjs +36 -18
  62. package/build/navigation-submenu/edit.cjs.map +2 -2
  63. package/build/navigation-submenu/index.cjs +15 -8
  64. package/build/navigation-submenu/index.cjs.map +2 -2
  65. package/build/nextpage/block.json +1 -0
  66. package/build/paragraph/index.cjs +3 -1
  67. package/build/paragraph/index.cjs.map +2 -2
  68. package/build/paragraph/transforms.cjs +14 -3
  69. package/build/paragraph/transforms.cjs.map +3 -3
  70. package/build/preformatted/index.cjs +3 -1
  71. package/build/preformatted/index.cjs.map +2 -2
  72. package/build/pullquote/index.cjs +7 -3
  73. package/build/pullquote/index.cjs.map +2 -2
  74. package/build/search/index.cjs +10 -5
  75. package/build/search/index.cjs.map +2 -2
  76. package/build/social-link/index.cjs +13 -7
  77. package/build/social-link/index.cjs.map +2 -2
  78. package/build/table-of-contents/hooks.cjs +1 -1
  79. package/build/table-of-contents/hooks.cjs.map +1 -1
  80. package/build/template-part/edit/utils/get-template-part-icon.cjs.map +1 -1
  81. package/build/utils/is-within-overlay.cjs +52 -0
  82. package/build/utils/is-within-overlay.cjs.map +7 -0
  83. package/build/verse/index.cjs +3 -1
  84. package/build/verse/index.cjs.map +2 -2
  85. package/build/video/index.cjs +20 -10
  86. package/build/video/index.cjs.map +2 -2
  87. package/build-module/audio/index.mjs +16 -8
  88. package/build-module/audio/index.mjs.map +2 -2
  89. package/build-module/block-keyboard-shortcuts/index.mjs +2 -4
  90. package/build-module/block-keyboard-shortcuts/index.mjs.map +2 -2
  91. package/build-module/button/index.mjs +17 -8
  92. package/build-module/button/index.mjs.map +2 -2
  93. package/build-module/code/index.mjs +3 -1
  94. package/build-module/code/index.mjs.map +2 -2
  95. package/build-module/comments-title/edit.mjs +10 -6
  96. package/build-module/comments-title/edit.mjs.map +2 -2
  97. package/build-module/cover/index.mjs +20 -10
  98. package/build-module/cover/index.mjs.map +2 -2
  99. package/build-module/details/index.mjs +3 -1
  100. package/build-module/details/index.mjs.map +2 -2
  101. package/build-module/file/index.mjs +19 -9
  102. package/build-module/file/index.mjs.map +2 -2
  103. package/build-module/heading/block.json +1 -3
  104. package/build-module/heading/deprecated.mjs +101 -5
  105. package/build-module/heading/deprecated.mjs.map +2 -2
  106. package/build-module/heading/edit.mjs +22 -46
  107. package/build-module/heading/edit.mjs.map +2 -2
  108. package/build-module/heading/index.mjs +3 -1
  109. package/build-module/heading/index.mjs.map +2 -2
  110. package/build-module/heading/save.mjs +2 -6
  111. package/build-module/heading/save.mjs.map +2 -2
  112. package/build-module/heading/transforms.mjs +16 -3
  113. package/build-module/heading/transforms.mjs.map +2 -2
  114. package/build-module/image/image.mjs +3 -3
  115. package/build-module/image/image.mjs.map +2 -2
  116. package/build-module/image/index.mjs +33 -17
  117. package/build-module/image/index.mjs.map +2 -2
  118. package/build-module/list-item/index.mjs +3 -1
  119. package/build-module/list-item/index.mjs.map +2 -2
  120. package/build-module/media-text/index.mjs +30 -15
  121. package/build-module/media-text/index.mjs.map +2 -2
  122. package/build-module/more/block.json +1 -0
  123. package/build-module/more/index.mjs +3 -1
  124. package/build-module/more/index.mjs.map +2 -2
  125. package/build-module/navigation/edit/index.mjs +7 -6
  126. package/build-module/navigation/edit/index.mjs.map +2 -2
  127. package/build-module/navigation/edit/responsive-wrapper.mjs +7 -4
  128. package/build-module/navigation/edit/responsive-wrapper.mjs.map +2 -2
  129. package/build-module/navigation/edit/use-create-overlay.mjs +2 -0
  130. package/build-module/navigation/edit/use-create-overlay.mjs.map +2 -2
  131. package/build-module/navigation-link/edit.mjs +12 -60
  132. package/build-module/navigation-link/edit.mjs.map +2 -2
  133. package/build-module/navigation-link/index.mjs +13 -8
  134. package/build-module/navigation-link/index.mjs.map +2 -2
  135. package/build-module/navigation-link/shared/index.mjs +7 -1
  136. package/build-module/navigation-link/shared/index.mjs.map +2 -2
  137. package/build-module/navigation-link/shared/invalid-draft-display.mjs +47 -0
  138. package/build-module/navigation-link/shared/invalid-draft-display.mjs.map +7 -0
  139. package/build-module/navigation-link/shared/use-enable-link-status-validation.mjs +25 -0
  140. package/build-module/navigation-link/shared/use-enable-link-status-validation.mjs.map +7 -0
  141. package/build-module/navigation-link/shared/use-is-invalid-link.mjs +39 -0
  142. package/build-module/navigation-link/shared/use-is-invalid-link.mjs.map +7 -0
  143. package/build-module/navigation-overlay-close/block.json +1 -4
  144. package/build-module/navigation-overlay-close/index.mjs +3 -25
  145. package/build-module/navigation-overlay-close/index.mjs.map +2 -2
  146. package/build-module/navigation-submenu/edit.mjs +40 -19
  147. package/build-module/navigation-submenu/edit.mjs.map +2 -2
  148. package/build-module/navigation-submenu/index.mjs +15 -8
  149. package/build-module/navigation-submenu/index.mjs.map +2 -2
  150. package/build-module/nextpage/block.json +1 -0
  151. package/build-module/paragraph/index.mjs +3 -1
  152. package/build-module/paragraph/index.mjs.map +2 -2
  153. package/build-module/paragraph/transforms.mjs +2 -1
  154. package/build-module/paragraph/transforms.mjs.map +2 -2
  155. package/build-module/preformatted/index.mjs +3 -1
  156. package/build-module/preformatted/index.mjs.map +2 -2
  157. package/build-module/pullquote/index.mjs +7 -3
  158. package/build-module/pullquote/index.mjs.map +2 -2
  159. package/build-module/search/index.mjs +10 -5
  160. package/build-module/search/index.mjs.map +2 -2
  161. package/build-module/social-link/index.mjs +13 -7
  162. package/build-module/social-link/index.mjs.map +2 -2
  163. package/build-module/table-of-contents/hooks.mjs +1 -1
  164. package/build-module/table-of-contents/hooks.mjs.map +1 -1
  165. package/build-module/template-part/edit/utils/get-template-part-icon.mjs.map +1 -1
  166. package/build-module/utils/is-within-overlay.mjs +27 -0
  167. package/build-module/utils/is-within-overlay.mjs.map +7 -0
  168. package/build-module/verse/index.mjs +3 -1
  169. package/build-module/verse/index.mjs.map +2 -2
  170. package/build-module/video/index.mjs +20 -10
  171. package/build-module/video/index.mjs.map +2 -2
  172. package/build-style/navigation/style-rtl.css +1 -16
  173. package/build-style/navigation/style.css +1 -16
  174. package/build-style/style-rtl.css +1 -16
  175. package/build-style/style.css +1 -16
  176. package/package.json +37 -38
  177. package/src/audio/index.js +13 -7
  178. package/src/block-keyboard-shortcuts/index.js +4 -7
  179. package/src/breadcrumbs/index.php +6 -3
  180. package/src/button/index.js +15 -8
  181. package/src/code/index.js +2 -1
  182. package/src/comments-title/edit.js +10 -7
  183. package/src/comments-title/index.php +7 -8
  184. package/src/cover/index.js +17 -8
  185. package/src/details/index.js +2 -1
  186. package/src/file/index.js +15 -8
  187. package/src/heading/block.json +1 -3
  188. package/src/heading/deprecated.js +118 -5
  189. package/src/heading/edit.js +6 -32
  190. package/src/heading/edit.native.js +17 -2
  191. package/src/heading/index.js +2 -1
  192. package/src/heading/save.js +2 -11
  193. package/src/heading/transforms.js +16 -3
  194. package/src/image/image.js +38 -40
  195. package/src/image/index.js +29 -16
  196. package/src/list-item/index.js +2 -1
  197. package/src/media-text/index.js +27 -14
  198. package/src/more/block.json +1 -0
  199. package/src/more/index.js +2 -1
  200. package/src/navigation/edit/index.js +9 -4
  201. package/src/navigation/edit/responsive-wrapper.js +15 -8
  202. package/src/navigation/edit/test/use-create-overlay.js +12 -0
  203. package/src/navigation/edit/use-create-overlay.js +2 -0
  204. package/src/navigation/index.php +74 -24
  205. package/src/navigation/style.scss +2 -18
  206. package/src/navigation-link/edit.js +11 -97
  207. package/src/navigation-link/index.js +13 -8
  208. package/src/navigation-link/index.php +17 -29
  209. package/src/navigation-link/shared/helpers.php +46 -0
  210. package/src/navigation-link/shared/index.js +3 -0
  211. package/src/navigation-link/shared/invalid-draft-display.js +63 -0
  212. package/src/navigation-link/shared/test/use-enable-link-status-validation.test.js +127 -0
  213. package/src/navigation-link/shared/use-enable-link-status-validation.js +40 -0
  214. package/src/navigation-link/shared/use-is-invalid-link.js +72 -0
  215. package/src/navigation-overlay-close/block.json +1 -4
  216. package/src/navigation-overlay-close/index.js +2 -37
  217. package/src/navigation-overlay-close/index.php +37 -1
  218. package/src/navigation-submenu/edit.js +49 -24
  219. package/src/navigation-submenu/index.js +13 -8
  220. package/src/navigation-submenu/index.php +25 -18
  221. package/src/nextpage/block.json +1 -0
  222. package/src/paragraph/index.js +2 -1
  223. package/src/paragraph/transforms.js +3 -1
  224. package/src/preformatted/index.js +2 -1
  225. package/src/pullquote/index.js +5 -3
  226. package/src/query-title/index.php +2 -2
  227. package/src/search/index.js +7 -5
  228. package/src/social-link/index.js +12 -7
  229. package/src/table-of-contents/hooks.js +1 -1
  230. package/src/template-part/edit/utils/get-template-part-icon.js +1 -1
  231. package/src/utils/is-within-overlay.js +54 -0
  232. package/src/verse/index.js +2 -1
  233. package/src/video/index.js +17 -9
  234. package/build/navigation-overlay-close/save.cjs +0 -67
  235. package/build/navigation-overlay-close/save.cjs.map +0 -7
  236. package/build-module/navigation-overlay-close/save.mjs +0 -46
  237. package/build-module/navigation-overlay-close/save.mjs.map +0 -7
  238. package/src/navigation-overlay-close/save.js +0 -44
@@ -34,9 +34,12 @@ export default function ResponsiveWrapper( {
34
34
  return children;
35
35
  }
36
36
 
37
+ // Only apply overlay colors if there's no custom overlay template part.
38
+ const hasCustomOverlay = !! overlay;
39
+
37
40
  const responsiveContainerClasses = clsx(
38
41
  'wp-block-navigation__responsive-container',
39
- {
42
+ ! hasCustomOverlay && {
40
43
  'has-text-color':
41
44
  !! overlayTextColor.color || !! overlayTextColor?.class,
42
45
  [ getColorClassName( 'color', overlayTextColor?.slug ) ]:
@@ -48,18 +51,22 @@ export default function ResponsiveWrapper( {
48
51
  'background-color',
49
52
  overlayBackgroundColor?.slug
50
53
  ) ]: !! overlayBackgroundColor?.slug,
54
+ },
55
+ {
51
56
  'is-menu-open': isOpen,
52
57
  'hidden-by-default': isHiddenByDefault,
53
58
  }
54
59
  );
55
60
 
56
- const styles = {
57
- color: ! overlayTextColor?.slug && overlayTextColor?.color,
58
- backgroundColor:
59
- ! overlayBackgroundColor?.slug &&
60
- overlayBackgroundColor?.color &&
61
- overlayBackgroundColor.color,
62
- };
61
+ const styles = ! hasCustomOverlay
62
+ ? {
63
+ color: ! overlayTextColor?.slug && overlayTextColor?.color,
64
+ backgroundColor:
65
+ ! overlayBackgroundColor?.slug &&
66
+ overlayBackgroundColor?.color &&
67
+ overlayBackgroundColor.color,
68
+ }
69
+ : {};
63
70
 
64
71
  const openButtonClasses = clsx(
65
72
  'wp-block-navigation__responsive-container-open',
@@ -23,6 +23,16 @@ jest.mock( '@wordpress/core-data', () => ( {
23
23
  store: {},
24
24
  } ) );
25
25
 
26
+ // Mock @wordpress/blocks
27
+ jest.mock( '@wordpress/blocks', () => ( {
28
+ serialize: jest.fn( ( blocks ) => JSON.stringify( blocks ) ),
29
+ createBlock: jest.fn( ( name ) => ( {
30
+ name,
31
+ attributes: {},
32
+ innerBlocks: [],
33
+ } ) ),
34
+ } ) );
35
+
26
36
  describe( 'useCreateOverlayTemplatePart', () => {
27
37
  const mockSaveEntityRecord = jest.fn();
28
38
 
@@ -62,6 +72,7 @@ describe( 'useCreateOverlayTemplatePart', () => {
62
72
  expect.objectContaining( {
63
73
  slug: 'overlay',
64
74
  title: 'Overlay',
75
+ content: expect.any( String ),
65
76
  area: 'navigation-overlay',
66
77
  } ),
67
78
  { throwOnError: true }
@@ -107,6 +118,7 @@ describe( 'useCreateOverlayTemplatePart', () => {
107
118
  expect.objectContaining( {
108
119
  title: 'Overlay 2',
109
120
  slug: 'overlay-2',
121
+ content: expect.any( String ),
110
122
  area: 'navigation-overlay',
111
123
  } ),
112
124
  { throwOnError: true }
@@ -5,6 +5,7 @@ import { useCallback } from '@wordpress/element';
5
5
  import { useDispatch } from '@wordpress/data';
6
6
  import { store as coreStore } from '@wordpress/core-data';
7
7
  import { __ } from '@wordpress/i18n';
8
+ import { serialize, createBlock } from '@wordpress/blocks';
8
9
 
9
10
  /**
10
11
  * Internal dependencies
@@ -41,6 +42,7 @@ export default function useCreateOverlayTemplatePart( overlayTemplateParts ) {
41
42
  {
42
43
  slug: cleanSlug,
43
44
  title: uniqueTitle,
45
+ content: serialize( [ createBlock( 'core/paragraph' ) ] ),
44
46
  area: NAVIGATION_OVERLAY_TEMPLATE_PART_AREA,
45
47
  },
46
48
  { throwOnError: true }
@@ -325,6 +325,9 @@ class WP_Navigation_Block_Renderer {
325
325
  $block['attrs'] = array();
326
326
  }
327
327
  $block['attrs']['overlayMenu'] = 'never';
328
+ // Mark this as a nested navigation within an overlay template part
329
+ // so we can handle its rendering differently.
330
+ $block['attrs']['_isWithinOverlayTemplatePart'] = true;
328
331
  }
329
332
 
330
333
  // Recursively process inner blocks.
@@ -507,15 +510,7 @@ class WP_Navigation_Block_Renderer {
507
510
  // Only published posts are valid. If this is changed then a corresponding change
508
511
  // must also be implemented in `use-navigation-menu.js`.
509
512
  if ( 'publish' === $navigation_post->post_status ) {
510
- $navigation_name = $navigation_post->post_title;
511
-
512
- // This is used to count the number of times a navigation name has been seen,
513
- // so that we can ensure every navigation has a unique id.
514
- if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) {
515
- ++static::$seen_menu_names[ $navigation_name ];
516
- } else {
517
- static::$seen_menu_names[ $navigation_name ] = 1;
518
- }
513
+ return $navigation_post->post_title;
519
514
  }
520
515
  }
521
516
 
@@ -599,6 +594,48 @@ class WP_Navigation_Block_Renderer {
599
594
  return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'];
600
595
  }
601
596
 
597
+ /**
598
+ * Get responsive container classes for the navigation block.
599
+ *
600
+ * @since 7.0.0
601
+ *
602
+ * @param bool $is_hidden_by_default Whether the responsive menu is hidden by default.
603
+ * @param bool $has_custom_overlay Whether a custom overlay is used.
604
+ * @param array $colors The colors array.
605
+ * @return array Returns the responsive container classes.
606
+ */
607
+ private static function get_responsive_container_classes( $is_hidden_by_default, $has_custom_overlay, $colors ) {
608
+ $responsive_container_classes = array( 'wp-block-navigation__responsive-container' );
609
+
610
+ if ( $is_hidden_by_default ) {
611
+ $responsive_container_classes[] = 'hidden-by-default';
612
+ }
613
+
614
+ if ( $has_custom_overlay ) {
615
+ // Only add the disable-default-overlay class if experiment is enabled AND overlay blocks actually rendered.
616
+ $responsive_container_classes[] = 'disable-default-overlay';
617
+ } else {
618
+ // Don't apply overlay color classes if using a custom overlay template part.
619
+ // The custom overlay is responsible for its own styling.
620
+ $responsive_container_classes[] = implode( ' ', $colors['overlay_css_classes'] );
621
+ }
622
+
623
+ return $responsive_container_classes;
624
+ }
625
+
626
+ /**
627
+ * Get overlay inline styles for the navigation block.
628
+ *
629
+ * @since 7.0.0
630
+ *
631
+ * @param array $colors The colors array.
632
+ * @return string Returns the overlay inline styles.
633
+ */
634
+ private static function get_overlay_inline_styles( $has_custom_overlay, $colors ) {
635
+ $overlay_inline_styles = $has_custom_overlay ? '' : esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) );
636
+ return ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '';
637
+ }
638
+
602
639
  /**
603
640
  * Get the responsive container markup
604
641
  *
@@ -649,14 +686,9 @@ class WP_Navigation_Block_Renderer {
649
686
  $has_custom_overlay = ! empty( $overlay_blocks_html );
650
687
  }
651
688
 
652
- // Only add the disable-default-overlay class if experiment is enabled AND overlay blocks actually rendered.
653
- $responsive_container_classes = array(
654
- 'wp-block-navigation__responsive-container',
655
- $is_hidden_by_default ? 'hidden-by-default' : '',
656
- $has_custom_overlay ? 'disable-default-overlay' : '',
657
- implode( ' ', $colors['overlay_css_classes'] ),
658
- );
659
- $open_button_classes = array(
689
+ $responsive_container_classes = static::get_responsive_container_classes( $is_hidden_by_default, $has_custom_overlay, $colors );
690
+
691
+ $open_button_classes = array(
660
692
  'wp-block-navigation__responsive-container-open',
661
693
  $is_hidden_by_default ? 'always-shown' : '',
662
694
  );
@@ -705,7 +737,9 @@ class WP_Navigation_Block_Renderer {
705
737
  ';
706
738
  }
707
739
 
708
- $overlay_inline_styles = esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) );
740
+ // Don't apply overlay inline styles if using a custom overlay template part.
741
+ // The custom overlay is responsible for its own styling.
742
+ $overlay_inline_styles = static::get_overlay_inline_styles( $has_custom_overlay, $colors );
709
743
 
710
744
  if ( $has_custom_overlay ) {
711
745
  $custom_overlay_markup = sprintf(
@@ -744,7 +778,7 @@ class WP_Navigation_Block_Renderer {
744
778
  $toggle_aria_label_close,
745
779
  esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ),
746
780
  esc_attr( trim( implode( ' ', $open_button_classes ) ) ),
747
- ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '',
781
+ $overlay_inline_styles,
748
782
  $toggle_button_content,
749
783
  $toggle_close_button_content,
750
784
  $open_button_directives,
@@ -765,8 +799,7 @@ class WP_Navigation_Block_Renderer {
765
799
  * @param WP_Block_List $inner_blocks A list of inner blocks.
766
800
  * @return string Returns the navigation block markup.
767
801
  */
768
- private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) {
769
- $nav_menu_name = static::get_unique_navigation_name( $attributes );
802
+ private static function get_nav_attributes( $attributes, $inner_blocks ) {
770
803
  $is_interactive = static::is_interactive( $attributes, $inner_blocks );
771
804
  $is_responsive_menu = static::is_responsive( $attributes );
772
805
  $style = static::get_styles( $attributes );
@@ -775,6 +808,15 @@ class WP_Navigation_Block_Renderer {
775
808
  'class' => $class,
776
809
  'style' => $style,
777
810
  );
811
+ // Only add aria-label for top-level navigation blocks.
812
+ // Skip navigation blocks marked as being within overlay template parts.
813
+ $is_within_overlay = $attributes['_isWithinOverlayTemplatePart'] ?? false;
814
+ if ( $is_within_overlay ) {
815
+ $nav_menu_name = static::get_navigation_name( $attributes );
816
+ } else {
817
+ $nav_menu_name = static::get_unique_navigation_name( $attributes );
818
+ }
819
+
778
820
  if ( ! empty( $nav_menu_name ) ) {
779
821
  $extra_attributes['aria-label'] = $nav_menu_name;
780
822
  }
@@ -844,7 +886,7 @@ class WP_Navigation_Block_Renderer {
844
886
  * @param WP_Block_List $inner_blocks The list of inner blocks.
845
887
  * @return string Returns the navigation wrapper markup.
846
888
  */
847
- private static function get_wrapper_markup( $attributes, $inner_blocks ) {
889
+ private static function get_inner_block_markup( $attributes, $inner_blocks ) {
848
890
  $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks );
849
891
  if ( static::is_responsive( $attributes ) ) {
850
892
  return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html );
@@ -863,6 +905,14 @@ class WP_Navigation_Block_Renderer {
863
905
  private static function get_unique_navigation_name( $attributes ) {
864
906
  $nav_menu_name = static::get_navigation_name( $attributes );
865
907
 
908
+ // This is used to count the number of times a navigation name has been seen,
909
+ // so that we can ensure every navigation has a unique id.
910
+ if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) ) {
911
+ ++static::$seen_menu_names[ $nav_menu_name ];
912
+ } else {
913
+ static::$seen_menu_names[ $nav_menu_name ] = 1;
914
+ }
915
+
866
916
  // If the menu name has been used previously then append an ID
867
917
  // to the name to ensure uniqueness across a given post.
868
918
  if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) {
@@ -914,8 +964,8 @@ class WP_Navigation_Block_Renderer {
914
964
 
915
965
  return sprintf(
916
966
  '<nav %1$s>%2$s</nav>',
917
- static::get_nav_wrapper_attributes( $attributes, $inner_blocks ),
918
- static::get_wrapper_markup( $attributes, $inner_blocks )
967
+ static::get_nav_attributes( $attributes, $inner_blocks ),
968
+ static::get_inner_block_markup( $attributes, $inner_blocks )
919
969
  );
920
970
  }
921
971
  }
@@ -238,25 +238,9 @@ $navigation-icon-size: 24px;
238
238
 
239
239
  // Custom menu items.
240
240
  // Show submenus on hover unless they open on click.
241
- &:not(.open-on-click):hover > .wp-block-navigation__submenu-container {
242
- visibility: visible;
243
- overflow: visible;
244
- opacity: 1;
245
- width: auto;
246
- height: auto;
247
- min-width: 200px;
248
- }
249
-
241
+ &:not(.open-on-click):hover > .wp-block-navigation__submenu-container,
250
242
  // Keep submenus open when focus is within.
251
- &:not(.open-on-click):not(.open-on-hover-click):focus-within > .wp-block-navigation__submenu-container {
252
- visibility: visible;
253
- overflow: visible;
254
- opacity: 1;
255
- width: auto;
256
- height: auto;
257
- min-width: 200px;
258
- }
259
-
243
+ &:not(.open-on-click):not(.open-on-hover-click):focus-within > .wp-block-navigation__submenu-container,
260
244
  // Show submenus on click.
261
245
  .wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container {
262
246
  visibility: visible;
@@ -23,13 +23,10 @@ import {
23
23
  store as blockEditorStore,
24
24
  getColorClassName,
25
25
  useInnerBlocksProps,
26
- useBlockEditingMode,
27
26
  } from '@wordpress/block-editor';
28
27
  import { isURL, prependHTTP } from '@wordpress/url';
29
28
  import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
30
- import { decodeEntities } from '@wordpress/html-entities';
31
29
  import { link as linkIcon, addSubmenu } from '@wordpress/icons';
32
- import { store as coreStore } from '@wordpress/core-data';
33
30
  import { useMergeRefs, useInstanceId } from '@wordpress/compose';
34
31
 
35
32
  /**
@@ -42,6 +39,9 @@ import {
42
39
  useEntityBinding,
43
40
  MissingEntityHelpText,
44
41
  useHandleLinkChange,
42
+ useIsInvalidLink,
43
+ InvalidDraftDisplay,
44
+ useEnableLinkStatusValidation,
45
45
  } from './shared';
46
46
 
47
47
  const DEFAULT_BLOCK = { name: 'core/navigation-link' };
@@ -101,62 +101,6 @@ const useIsDraggingWithin = ( elementRef ) => {
101
101
  return isDraggingWithin;
102
102
  };
103
103
 
104
- const useIsInvalidLink = ( kind, type, id, enabled ) => {
105
- const isPostType =
106
- kind === 'post-type' || type === 'post' || type === 'page';
107
- const hasId = Number.isInteger( id );
108
- const blockEditingMode = useBlockEditingMode();
109
-
110
- const { postStatus, isDeleted } = useSelect(
111
- ( select ) => {
112
- if ( ! isPostType ) {
113
- return { postStatus: null, isDeleted: false };
114
- }
115
-
116
- // Fetching the posts status is an "expensive" operation. Especially for sites with large navigations.
117
- // When the block is rendered in a template or other disabled contexts we can skip this check in order
118
- // to avoid all these additional requests that don't really add any value in that mode.
119
- if ( blockEditingMode === 'disabled' || ! enabled ) {
120
- return { postStatus: null, isDeleted: false };
121
- }
122
-
123
- const { getEntityRecord, hasFinishedResolution } =
124
- select( coreStore );
125
- const entityRecord = getEntityRecord( 'postType', type, id );
126
- const hasResolved = hasFinishedResolution( 'getEntityRecord', [
127
- 'postType',
128
- type,
129
- id,
130
- ] );
131
-
132
- // If resolution has finished and entityRecord is undefined, the entity was deleted.
133
- const deleted = hasResolved && entityRecord === undefined;
134
-
135
- return {
136
- postStatus: entityRecord?.status,
137
- isDeleted: deleted,
138
- };
139
- },
140
- [ isPostType, blockEditingMode, enabled, type, id ]
141
- );
142
-
143
- // Check Navigation Link validity if:
144
- // 1. Link is 'post-type'.
145
- // 2. It has an id.
146
- // 3. It's neither null, nor undefined, as valid items might be either of those while loading.
147
- // If those conditions are met, check if
148
- // 1. The post status is trash (trashed).
149
- // 2. The entity doesn't exist (deleted).
150
- // If either of those is true, invalidate.
151
- const isInvalid =
152
- isPostType &&
153
- hasId &&
154
- ( isDeleted || ( postStatus && 'trash' === postStatus ) );
155
- const isDraft = 'draft' === postStatus;
156
-
157
- return [ isInvalid, isDraft ];
158
- };
159
-
160
104
  function getMissingText( type ) {
161
105
  let missingText = '';
162
106
 
@@ -224,7 +168,6 @@ export default function NavigationLinkEdit( {
224
168
  isTopLevelLink,
225
169
  isParentOfSelectedBlock,
226
170
  hasChildren,
227
- validateLinkStatus,
228
171
  parentBlockClientId,
229
172
  isSubmenu,
230
173
  } = useSelect(
@@ -235,12 +178,10 @@ export default function NavigationLinkEdit( {
235
178
  getBlockRootClientId,
236
179
  hasSelectedInnerBlock,
237
180
  getBlockParentsByBlockName,
238
- getSelectedBlockClientId,
239
181
  } = select( blockEditorStore );
240
182
  const rootClientId = getBlockRootClientId( clientId );
241
183
  const parentBlockName = getBlockName( rootClientId );
242
184
  const isTopLevel = parentBlockName === 'core/navigation';
243
- const selectedBlockClientId = getSelectedBlockClientId();
244
185
  const rootNavigationClientId = isTopLevel
245
186
  ? rootClientId
246
187
  : getBlockParentsByBlockName(
@@ -254,11 +195,6 @@ export default function NavigationLinkEdit( {
254
195
  ? rootClientId
255
196
  : rootNavigationClientId;
256
197
 
257
- // Enable when the root Navigation block is selected or any of its inner blocks.
258
- const enableLinkStatusValidation =
259
- selectedBlockClientId === rootNavigationClientId ||
260
- hasSelectedInnerBlock( rootNavigationClientId, true );
261
-
262
198
  return {
263
199
  isAtMaxNesting:
264
200
  getBlockParentsByBlockName( clientId, NESTING_BLOCK_NAMES )
@@ -269,13 +205,14 @@ export default function NavigationLinkEdit( {
269
205
  true
270
206
  ),
271
207
  hasChildren: !! getBlockCount( clientId ),
272
- validateLinkStatus: enableLinkStatusValidation,
273
208
  parentBlockClientId: parentBlockId,
274
209
  isSubmenu: parentBlockName === 'core/navigation-submenu',
275
210
  };
276
211
  },
277
212
  [ clientId, maxNestingLevel ]
278
213
  );
214
+
215
+ const validateLinkStatus = useEnableLinkStatusValidation( clientId );
279
216
  const { getBlocks } = useSelect( blockEditorStore );
280
217
 
281
218
  // URL binding logic
@@ -493,10 +430,6 @@ export default function NavigationLinkEdit( {
493
430
  } );
494
431
 
495
432
  const missingText = getMissingText( type );
496
- /* translators: Whether the navigation link is Invalid or a Draft. */
497
- const placeholderText = `(${
498
- isInvalid ? __( 'Invalid' ) : __( 'Draft' )
499
- })`;
500
433
 
501
434
  return (
502
435
  <>
@@ -578,31 +511,12 @@ export default function NavigationLinkEdit( {
578
511
  </>
579
512
  ) }
580
513
  { ( isInvalid || isDraft ) && (
581
- <div
582
- className={ clsx(
583
- 'wp-block-navigation-link__placeholder-text',
584
- 'wp-block-navigation-link__label',
585
- {
586
- 'is-invalid': isInvalid,
587
- 'is-draft': isDraft,
588
- }
589
- ) }
590
- >
591
- <span>
592
- {
593
- // Some attributes are stored in an escaped form. It's a legacy issue.
594
- // Ideally they would be stored in a raw, unescaped form.
595
- // Unescape is used here to "recover" the escaped characters
596
- // so they display without encoding.
597
- // See `updateAttributes` for more details.
598
- `${ decodeEntities( label ) } ${
599
- isInvalid || isDraft
600
- ? placeholderText
601
- : ''
602
- }`.trim()
603
- }
604
- </span>
605
- </div>
514
+ <InvalidDraftDisplay
515
+ label={ label }
516
+ isInvalid={ isInvalid }
517
+ isDraft={ isDraft }
518
+ className="wp-block-navigation-link__label"
519
+ />
606
520
  ) }
607
521
  </>
608
522
  ) }
@@ -98,21 +98,26 @@ if ( window.__experimentalContentOnlyInspectorFields ) {
98
98
  {
99
99
  id: 'label',
100
100
  label: __( 'Label' ),
101
- type: 'richtext',
101
+ type: 'text',
102
+ Edit: 'rich-text',
102
103
  },
103
104
  {
104
105
  id: 'link',
105
106
  label: __( 'Link' ),
106
- type: 'link',
107
- mapping: {
108
- href: 'url',
109
- rel: 'rel',
110
- // TODO - opens in new tab? id?
111
- },
107
+ type: 'url',
108
+ Edit: 'link',
109
+ getValue: ( { item } ) => ( {
110
+ url: item.url,
111
+ rel: item.rel,
112
+ } ),
113
+ setValue: ( { value } ) => ( {
114
+ url: value.url,
115
+ rel: value.rel,
116
+ } ),
112
117
  },
113
118
  ];
114
119
  settings[ formKey ] = {
115
- fields: [ 'label' ],
120
+ fields: [ 'label', 'link' ],
116
121
  };
117
122
  }
118
123
 
@@ -5,6 +5,13 @@
5
5
  * @package WordPress
6
6
  */
7
7
 
8
+ // Path differs between source and build: './shared/helpers.php' in source, './navigation-link/shared/helpers.php' in build.
9
+ if ( file_exists( __DIR__ . '/shared/helpers.php' ) ) {
10
+ require_once __DIR__ . '/shared/helpers.php';
11
+ } else {
12
+ require_once __DIR__ . '/navigation-link/shared/helpers.php';
13
+ }
14
+
8
15
  /**
9
16
  * Build an array with CSS classes and inline styles defining the colors
10
17
  * which will be applied to the navigation markup in the front-end.
@@ -170,29 +177,9 @@ function block_core_navigation_link_maybe_urldecode( $url ) {
170
177
  * @return string Returns the post content with the legacy widget added.
171
178
  */
172
179
  function render_block_core_navigation_link( $attributes, $content, $block ) {
173
- $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] );
174
- $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind'];
175
- $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] );
176
-
177
- // Don't render the block's subtree if it is a draft or if the ID does not exist.
178
- if ( $is_post_type && $navigation_link_has_id ) {
179
- $post = get_post( $attributes['id'] );
180
- /**
181
- * Filter allowed post_status for navigation link block to render.
182
- *
183
- * @since 6.8.0
184
- *
185
- * @param array $post_status
186
- * @param array $attributes
187
- * @param WP_Block $block
188
- */
189
- $allowed_post_status = (array) apply_filters(
190
- 'render_block_core_navigation_link_allowed_post_status',
191
- array( 'publish' ),
192
- $attributes,
193
- $block
194
- );
195
- if ( ! $post || ! in_array( $post->post_status, $allowed_post_status, true ) ) {
180
+ // Check if this navigation item should render based on post status.
181
+ if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
182
+ if ( ! gutenberg_block_core_shared_navigation_item_should_render( $attributes, $block ) ) {
196
183
  return '';
197
184
  }
198
185
  }
@@ -208,8 +195,14 @@ function render_block_core_navigation_link( $attributes, $content, $block ) {
208
195
  );
209
196
  $style_attribute = $font_sizes['inline_styles'];
210
197
 
198
+ // Render inner blocks first to check if any menu items will actually display.
199
+ $inner_blocks_html = '';
200
+ foreach ( $block->inner_blocks as $inner_block ) {
201
+ $inner_blocks_html .= $inner_block->render();
202
+ }
203
+ $has_submenu = ! empty( trim( $inner_blocks_html ) );
204
+
211
205
  $css_classes = trim( implode( ' ', $classes ) );
212
- $has_submenu = count( $block->inner_blocks ) > 0;
213
206
  $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] );
214
207
  $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind );
215
208
 
@@ -282,11 +275,6 @@ function render_block_core_navigation_link( $attributes, $content, $block ) {
282
275
  }
283
276
 
284
277
  if ( $has_submenu ) {
285
- $inner_blocks_html = '';
286
- foreach ( $block->inner_blocks as $inner_block ) {
287
- $inner_blocks_html .= $inner_block->render();
288
- }
289
-
290
278
  $html .= sprintf(
291
279
  '<ul class="wp-block-navigation__submenu-container">%s</ul>',
292
280
  $inner_blocks_html
@@ -0,0 +1,46 @@
1
+ <?php
2
+ /**
3
+ * Shared helper functions for Navigation Link and Navigation Submenu blocks.
4
+ *
5
+ * @package WordPress
6
+ */
7
+
8
+ /**
9
+ * Checks if a navigation item should render based on post status.
10
+ *
11
+ * @since 7.0.0
12
+ *
13
+ * @param array $attributes The block attributes.
14
+ * @param WP_Block $block The parsed block.
15
+ * @return bool True if the item should render, false otherwise.
16
+ */
17
+ function block_core_shared_navigation_item_should_render( $attributes, $block ) {
18
+ $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] );
19
+ $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind'];
20
+ $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] );
21
+
22
+ // Don't render the block's subtree if it is a draft or if the ID does not exist.
23
+ if ( $is_post_type && $navigation_link_has_id ) {
24
+ $post = get_post( $attributes['id'] );
25
+ /**
26
+ * Filter allowed post_status for navigation link block to render.
27
+ *
28
+ * @since 6.8.0
29
+ *
30
+ * @param array $post_status Array of allowed post statuses.
31
+ * @param array $attributes Block attributes.
32
+ * @param WP_Block $block The parsed block.
33
+ */
34
+ $allowed_post_status = (array) apply_filters(
35
+ 'render_block_core_navigation_link_allowed_post_status',
36
+ array( 'publish' ),
37
+ $attributes,
38
+ $block
39
+ );
40
+ if ( ! $post || ! in_array( $post->post_status, $allowed_post_status, true ) ) {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ return true;
46
+ }
@@ -13,3 +13,6 @@ export {
13
13
  } from './use-entity-binding';
14
14
  export { LinkUI } from '../link-ui';
15
15
  export { useHandleLinkChange } from './use-handle-link-change';
16
+ export { useIsInvalidLink } from './use-is-invalid-link';
17
+ export { InvalidDraftDisplay } from './invalid-draft-display';
18
+ export { useEnableLinkStatusValidation } from './use-enable-link-status-validation';