@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
@@ -0,0 +1,63 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import clsx from 'clsx';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { __ } from '@wordpress/i18n';
10
+ import { decodeEntities } from '@wordpress/html-entities';
11
+
12
+ /**
13
+ * Displays a label with an "(Invalid)" or "(Draft)" indicator for navigation links.
14
+ *
15
+ * @param {Object} props Component props.
16
+ * @param {string} props.label The label text to display.
17
+ * @param {boolean} props.isInvalid Whether the link is invalid (deleted or trashed).
18
+ * @param {boolean} props.isDraft Whether the link is a draft.
19
+ * @param {string} props.className Optional additional CSS class for the label element.
20
+ *
21
+ * @return {Element} The invalid/draft display component.
22
+ */
23
+ export function InvalidDraftDisplay( {
24
+ label,
25
+ isInvalid,
26
+ isDraft,
27
+ className = 'wp-block-navigation-link__label',
28
+ } ) {
29
+ // Only render if the link is invalid or a draft.
30
+ if ( ! isInvalid && ! isDraft ) {
31
+ return null;
32
+ }
33
+
34
+ const statusText = isInvalid
35
+ ? /* translators: Indicating that the navigation link is Invalid. */
36
+ __( 'Invalid' )
37
+ : /* translators: Indicating that the navigation link is a Draft. */
38
+ __( 'Draft' );
39
+
40
+ return (
41
+ <div
42
+ className={ clsx(
43
+ 'wp-block-navigation-link__placeholder-text',
44
+ className,
45
+ {
46
+ 'is-invalid': isInvalid,
47
+ 'is-draft': isDraft,
48
+ }
49
+ ) }
50
+ >
51
+ <span>
52
+ {
53
+ // Some attributes are stored in an escaped form. It's a legacy issue.
54
+ // Ideally they would be stored in a raw, unescaped form.
55
+ // Unescape is used here to "recover" the escaped characters
56
+ // so they display without encoding.
57
+ // See `updateAttributes` for more details.
58
+ `${ decodeEntities( label ) } (${ statusText })`.trim()
59
+ }
60
+ </span>
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { renderHook } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { useEnableLinkStatusValidation } from '../use-enable-link-status-validation';
10
+
11
+ // Mock useSelect directly at the implementation level to avoid loading complex dependencies
12
+ jest.mock( '@wordpress/data/src/components/use-select', () => {
13
+ const mock = jest.fn();
14
+ return mock;
15
+ } );
16
+
17
+ const { useSelect } = require( '@wordpress/data' );
18
+
19
+ describe( 'useEnableLinkStatusValidation', () => {
20
+ const mockClientId = 'test-client-id';
21
+ const mockRootNavigationId = 'root-navigation-id';
22
+ const mockSelectedBlockId = 'selected-block-id';
23
+
24
+ beforeEach( () => {
25
+ jest.clearAllMocks();
26
+ } );
27
+
28
+ it( 'should return true when root navigation block is selected', () => {
29
+ useSelect.mockImplementation( ( callback ) => {
30
+ return callback( () => ( {
31
+ getSelectedBlockClientId: () => mockRootNavigationId,
32
+ hasSelectedInnerBlock: () => false,
33
+ getBlockParentsByBlockName: () => [ mockRootNavigationId ],
34
+ } ) );
35
+ } );
36
+
37
+ const { result } = renderHook( () =>
38
+ useEnableLinkStatusValidation( mockClientId )
39
+ );
40
+
41
+ expect( result.current ).toBe( true );
42
+ } );
43
+
44
+ it( 'should return true when an inner block of root navigation is selected', () => {
45
+ useSelect.mockImplementation( ( callback ) => {
46
+ return callback( () => ( {
47
+ getSelectedBlockClientId: () => mockSelectedBlockId,
48
+ hasSelectedInnerBlock: ( clientId, deep ) =>
49
+ clientId === mockRootNavigationId && deep === true,
50
+ getBlockParentsByBlockName: () => [ mockRootNavigationId ],
51
+ } ) );
52
+ } );
53
+
54
+ const { result } = renderHook( () =>
55
+ useEnableLinkStatusValidation( mockClientId )
56
+ );
57
+
58
+ expect( result.current ).toBe( true );
59
+ } );
60
+
61
+ it( 'should return false when neither root navigation nor its inner blocks are selected', () => {
62
+ useSelect.mockImplementation( ( callback ) => {
63
+ return callback( () => ( {
64
+ getSelectedBlockClientId: () => 'other-block-id',
65
+ hasSelectedInnerBlock: () => false,
66
+ getBlockParentsByBlockName: () => [ mockRootNavigationId ],
67
+ } ) );
68
+ } );
69
+
70
+ const { result } = renderHook( () =>
71
+ useEnableLinkStatusValidation( mockClientId )
72
+ );
73
+
74
+ expect( result.current ).toBe( false );
75
+ } );
76
+
77
+ it( 'should handle case when root navigation id is not found', () => {
78
+ useSelect.mockImplementation( ( callback ) => {
79
+ return callback( () => ( {
80
+ getSelectedBlockClientId: () => mockSelectedBlockId,
81
+ hasSelectedInnerBlock: () => false,
82
+ getBlockParentsByBlockName: () => [],
83
+ } ) );
84
+ } );
85
+
86
+ const { result } = renderHook( () =>
87
+ useEnableLinkStatusValidation( mockClientId )
88
+ );
89
+
90
+ // When rootNavigationId is undefined (empty array),
91
+ // both conditions will be false
92
+ expect( result.current ).toBe( false );
93
+ } );
94
+
95
+ it( 'should update when clientId changes', () => {
96
+ const newClientId = 'new-client-id';
97
+ const newRootNavigationId = 'new-root-navigation-id';
98
+
99
+ useSelect.mockImplementation( ( callback ) => {
100
+ return callback( () => ( {
101
+ getSelectedBlockClientId: () => newRootNavigationId,
102
+ hasSelectedInnerBlock: () => false,
103
+ getBlockParentsByBlockName: ( clientId ) => {
104
+ return clientId === newClientId
105
+ ? [ newRootNavigationId ]
106
+ : [ mockRootNavigationId ];
107
+ },
108
+ } ) );
109
+ } );
110
+
111
+ const { result, rerender } = renderHook(
112
+ ( { clientId } ) => useEnableLinkStatusValidation( clientId ),
113
+ {
114
+ initialProps: { clientId: mockClientId },
115
+ }
116
+ );
117
+
118
+ // Initially false (different IDs)
119
+ expect( result.current ).toBe( false );
120
+
121
+ // Rerender with new clientId
122
+ rerender( { clientId: newClientId } );
123
+
124
+ // Should now be true (matching IDs)
125
+ expect( result.current ).toBe( true );
126
+ } );
127
+ } );
@@ -0,0 +1,40 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { store as blockEditorStore } from '@wordpress/block-editor';
6
+
7
+ /**
8
+ * Hook to determine if link status validation should be enabled.
9
+ *
10
+ * Link status validation is enabled when the root Navigation block is selected
11
+ * or any of its inner blocks are selected. This ensures validation only runs
12
+ * when the user is actively working within the navigation structure.
13
+ *
14
+ * @param {string} clientId The client ID of the current block.
15
+ * @return {boolean} Whether link status validation should be enabled.
16
+ */
17
+ export function useEnableLinkStatusValidation( clientId ) {
18
+ return useSelect(
19
+ ( select ) => {
20
+ const {
21
+ getSelectedBlockClientId,
22
+ hasSelectedInnerBlock,
23
+ getBlockParentsByBlockName,
24
+ } = select( blockEditorStore );
25
+
26
+ const selectedBlockId = getSelectedBlockClientId();
27
+ const rootNavigationId = getBlockParentsByBlockName(
28
+ clientId,
29
+ 'core/navigation'
30
+ )[ 0 ];
31
+
32
+ // Enable when the root Navigation block is selected or any of its inner blocks.
33
+ return (
34
+ selectedBlockId === rootNavigationId ||
35
+ hasSelectedInnerBlock( rootNavigationId, true )
36
+ );
37
+ },
38
+ [ clientId ]
39
+ );
40
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { store as coreStore } from '@wordpress/core-data';
6
+ import { useBlockEditingMode } from '@wordpress/block-editor';
7
+
8
+ /**
9
+ * A React hook to determine if a navigation link or submenu's parent link is invalid.
10
+ *
11
+ * @param {string} kind The kind of link (post-type, custom, taxonomy, etc).
12
+ * @param {string} type The type of post (post, page, etc).
13
+ * @param {number} id The post or term id.
14
+ * @param {boolean} enabled Whether to enable the validation check.
15
+ *
16
+ * @return {Array} Array containing [isInvalid, isDraft] booleans.
17
+ */
18
+ export const useIsInvalidLink = ( kind, type, id, enabled ) => {
19
+ const isPostType =
20
+ kind === 'post-type' || type === 'post' || type === 'page';
21
+ const hasId = Number.isInteger( id );
22
+ const blockEditingMode = useBlockEditingMode();
23
+
24
+ const { postStatus, isDeleted } = useSelect(
25
+ ( select ) => {
26
+ if ( ! isPostType ) {
27
+ return { postStatus: null, isDeleted: false };
28
+ }
29
+
30
+ // Fetching the posts status is an "expensive" operation. Especially for sites with large navigations.
31
+ // When the block is rendered in a template or other disabled contexts we can skip this check in order
32
+ // to avoid all these additional requests that don't really add any value in that mode.
33
+ if ( blockEditingMode === 'disabled' || ! enabled ) {
34
+ return { postStatus: null, isDeleted: false };
35
+ }
36
+
37
+ const { getEntityRecord, hasFinishedResolution } =
38
+ select( coreStore );
39
+ const entityRecord = getEntityRecord( 'postType', type, id );
40
+ const hasResolved = hasFinishedResolution( 'getEntityRecord', [
41
+ 'postType',
42
+ type,
43
+ id,
44
+ ] );
45
+
46
+ // If resolution has finished and entityRecord is undefined, the entity was deleted.
47
+ const deleted = hasResolved && entityRecord === undefined;
48
+
49
+ return {
50
+ postStatus: entityRecord?.status,
51
+ isDeleted: deleted,
52
+ };
53
+ },
54
+ [ isPostType, blockEditingMode, enabled, type, id ]
55
+ );
56
+
57
+ // Check Navigation Link validity if:
58
+ // 1. Link is 'post-type'.
59
+ // 2. It has an id.
60
+ // 3. It's neither null, nor undefined, as valid items might be either of those while loading.
61
+ // If those conditions are met, check if
62
+ // 1. The post status is trash (trashed).
63
+ // 2. The entity doesn't exist (deleted).
64
+ // If either of those is true, invalidate.
65
+ const isInvalid =
66
+ isPostType &&
67
+ hasId &&
68
+ ( isDeleted || ( postStatus && 'trash' === postStatus ) );
69
+ const isDraft = 'draft' === postStatus;
70
+
71
+ return [ isInvalid, isDraft ];
72
+ };
@@ -15,10 +15,7 @@
15
15
  "default": "icon"
16
16
  },
17
17
  "text": {
18
- "type": "rich-text",
19
- "source": "rich-text",
20
- "selector": "span",
21
- "default": "Close"
18
+ "type": "string"
22
19
  }
23
20
  },
24
21
  "supports": {
@@ -2,16 +2,14 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { addFilter } from '@wordpress/hooks';
5
- import { select } from '@wordpress/data';
6
- import { store as coreStore } from '@wordpress/core-data';
7
5
 
8
6
  /**
9
7
  * Internal dependencies
10
8
  */
11
9
  import initBlock from '../utils/init-block';
10
+ import { isWithinNavigationOverlay } from '../utils/is-within-overlay';
12
11
  import edit from './edit';
13
12
  import metadata from './block.json';
14
- import save from './save';
15
13
  import icon from './icon';
16
14
 
17
15
  const { name } = metadata;
@@ -21,41 +19,8 @@ export { metadata, name };
21
19
  export const settings = {
22
20
  icon,
23
21
  edit,
24
- save,
25
22
  };
26
23
 
27
- function isWithinOverlay() {
28
- // @wordpress/block-library should not depend on @wordpress/editor.
29
- // Blocks can be loaded into a *non-post* block editor, so to avoid
30
- // declaring @wordpress/editor as a dependency, we must access its
31
- // store by string.
32
- // eslint-disable-next-line @wordpress/data-no-store-string-literals
33
- const editorStore = select( 'core/editor' );
34
-
35
- // Return false if the editor store is not available.
36
- if ( ! editorStore ) {
37
- return false;
38
- }
39
-
40
- const { getCurrentPostType, getCurrentPostId } = editorStore;
41
- const { getEditedEntityRecord } = select( coreStore );
42
-
43
- const postType = getCurrentPostType();
44
- const postId = getCurrentPostId();
45
-
46
- if ( postType === 'wp_template_part' && postId ) {
47
- const templatePartEntity = getEditedEntityRecord(
48
- 'postType',
49
- 'wp_template_part',
50
- postId
51
- );
52
-
53
- return templatePartEntity?.area === 'overlay';
54
- }
55
-
56
- return false;
57
- }
58
-
59
24
  export const init = () => {
60
25
  addFilter(
61
26
  'blockEditor.__unstableCanInsertBlockType',
@@ -69,7 +34,7 @@ export const init = () => {
69
34
  return canInsert;
70
35
  }
71
36
 
72
- return isWithinOverlay();
37
+ return isWithinNavigationOverlay();
73
38
  }
74
39
  );
75
40
 
@@ -5,6 +5,39 @@
5
5
  * @package WordPress
6
6
  */
7
7
 
8
+ /**
9
+ * Renders the `core/navigation-overlay-close` block on server.
10
+ *
11
+ * @param array $attributes The block attributes.
12
+ *
13
+ * @return string Returns the block content.
14
+ */
15
+ function render_block_core_navigation_overlay_close( $attributes ) {
16
+ $text = empty( $attributes['text'] ) ? __( 'Close' ) : $attributes['text'];
17
+ $display_mode = empty( $attributes['displayMode'] ) ? 'icon' : $attributes['displayMode'];
18
+ $show_icon = 'both' === $display_mode || 'icon' === $display_mode;
19
+ $show_text = 'both' === $display_mode || 'text' === $display_mode;
20
+ $button_text = '';
21
+
22
+ if ( $show_icon ) {
23
+ $button_text .= '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1.1-1-6.1 6.2-6.1-6.2-1.1 1 6.1 6.3-6.5 6.7 1.1 1 6.5-6.6 6.5 6.6 1.1-1z" /></svg>';
24
+ }
25
+
26
+ if ( $show_text ) {
27
+ $button_text .= '<span class="wp-block-navigation-overlay-close__text">' . wp_kses_post( $text ) . '</span>';
28
+ }
29
+
30
+ $wrapper_attributes = get_block_wrapper_attributes();
31
+ $html_content = sprintf(
32
+ '<button %1$s type="button" %2$s >%3$s</button>',
33
+ $wrapper_attributes,
34
+ ! $show_text ? 'aria-label="' . __( 'Close' ) . '"' : '',
35
+ $button_text
36
+ );
37
+
38
+ return $html_content;
39
+ }
40
+
8
41
  /**
9
42
  * Registers the navigation overlay close block.
10
43
  *
@@ -12,7 +45,10 @@
12
45
  */
13
46
  function register_block_core_navigation_overlay_close() {
14
47
  register_block_type_from_metadata(
15
- __DIR__ . '/navigation-overlay-close'
48
+ __DIR__ . '/navigation-overlay-close',
49
+ array(
50
+ 'render_callback' => 'render_block_core_navigation_overlay_close',
51
+ )
16
52
  );
17
53
  }
18
54
  add_action( 'init', 'register_block_core_navigation_overlay_close' );
@@ -37,6 +37,9 @@ import {
37
37
  LinkUI,
38
38
  updateAttributes,
39
39
  useEntityBinding,
40
+ useIsInvalidLink,
41
+ InvalidDraftDisplay,
42
+ useEnableLinkStatusValidation,
40
43
  } from '../navigation-link/shared';
41
44
  import {
42
45
  getColors,
@@ -128,7 +131,7 @@ export default function NavigationSubmenuEdit( {
128
131
  context,
129
132
  clientId,
130
133
  } ) {
131
- const { label, url, description } = attributes;
134
+ const { label, url, description, kind, type, id } = attributes;
132
135
 
133
136
  const {
134
137
  showSubmenuIcon,
@@ -214,8 +217,18 @@ export default function NavigationSubmenuEdit( {
214
217
  [ clientId ]
215
218
  );
216
219
 
220
+ const validateLinkStatus = useEnableLinkStatusValidation( clientId );
221
+
217
222
  const prevHasChildren = usePrevious( hasChildren );
218
223
 
224
+ // Check if the submenu's parent link is invalid or draft
225
+ const [ isInvalid, isDraft ] = useIsInvalidLink(
226
+ kind,
227
+ type,
228
+ id,
229
+ validateLinkStatus
230
+ );
231
+
219
232
  // Show the LinkControl on mount if the URL is empty
220
233
  // ( When adding a new menu item)
221
234
  // This can't be done in the useState call because it conflicts
@@ -392,29 +405,41 @@ export default function NavigationSubmenuEdit( {
392
405
  </InspectorControls>
393
406
  <div { ...blockProps }>
394
407
  <ParentElement className="wp-block-navigation-item__content">
395
- <RichText
396
- ref={ ref }
397
- identifier="label"
398
- className="wp-block-navigation-item__label"
399
- value={ label }
400
- onChange={ ( labelValue ) =>
401
- setAttributes( { label: labelValue } )
402
- }
403
- onMerge={ mergeBlocks }
404
- onReplace={ onReplace }
405
- aria-label={ __( 'Navigation link text' ) }
406
- placeholder={ itemLabelPlaceholder }
407
- withoutInteractiveFormatting
408
- onClick={ () => {
409
- if ( ! openSubmenusOnClick && ! url ) {
410
- setIsLinkOpen( true );
411
- }
412
- } }
413
- />
414
- { description && (
415
- <span className="wp-block-navigation-item__description">
416
- { description }
417
- </span>
408
+ { ! isInvalid && ! isDraft && (
409
+ <>
410
+ <RichText
411
+ ref={ ref }
412
+ identifier="label"
413
+ className="wp-block-navigation-item__label"
414
+ value={ label }
415
+ onChange={ ( labelValue ) =>
416
+ setAttributes( { label: labelValue } )
417
+ }
418
+ onMerge={ mergeBlocks }
419
+ onReplace={ onReplace }
420
+ aria-label={ __( 'Navigation link text' ) }
421
+ placeholder={ itemLabelPlaceholder }
422
+ withoutInteractiveFormatting
423
+ onClick={ () => {
424
+ if ( ! openSubmenusOnClick && ! url ) {
425
+ setIsLinkOpen( true );
426
+ }
427
+ } }
428
+ />
429
+ { description && (
430
+ <span className="wp-block-navigation-item__description">
431
+ { description }
432
+ </span>
433
+ ) }
434
+ </>
435
+ ) }
436
+ { ( isInvalid || isDraft ) && (
437
+ <InvalidDraftDisplay
438
+ label={ label }
439
+ isInvalid={ isInvalid }
440
+ isDraft={ isDraft }
441
+ className="wp-block-navigation-item__label"
442
+ />
418
443
  ) }
419
444
  { ! openSubmenusOnClick && isLinkOpen && (
420
445
  <LinkUI
@@ -57,21 +57,26 @@ if ( window.__experimentalContentOnlyInspectorFields ) {
57
57
  {
58
58
  id: 'label',
59
59
  label: __( 'Label' ),
60
- type: 'richtext',
60
+ type: 'text',
61
+ Edit: 'rich-text', //TODO: replace with custom component
61
62
  },
62
63
  {
63
64
  id: 'link',
64
65
  label: __( 'Link' ),
65
- type: 'link',
66
- mapping: {
67
- href: 'url',
68
- rel: 'rel',
69
- // TODO - opens in new tab? id?
70
- },
66
+ type: 'url',
67
+ Edit: 'link', // TODO: replace with custom component
68
+ getValue: ( { item } ) => ( {
69
+ url: item.url,
70
+ rel: item.rel,
71
+ } ),
72
+ setValue: ( { value } ) => ( {
73
+ url: value.url,
74
+ rel: value.rel,
75
+ } ),
71
76
  },
72
77
  ];
73
78
  settings[ formKey ] = {
74
- fields: [ 'label' ],
79
+ fields: [ 'label', 'link' ],
75
80
  };
76
81
  }
77
82
 
@@ -5,6 +5,13 @@
5
5
  * @package WordPress
6
6
  */
7
7
 
8
+ // Path differs between source and build: '../navigation-link/shared/helpers.php' in source, './navigation-link/shared/helpers.php' in build.
9
+ if ( file_exists( __DIR__ . '/../navigation-link/shared/helpers.php' ) ) {
10
+ require_once __DIR__ . '/../navigation-link/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 font sizes
10
17
  * which will be applied to the navigation markup in the front-end.
@@ -65,13 +72,11 @@ function block_core_navigation_submenu_render_submenu_icon() {
65
72
  * @return string Returns the post content with the legacy widget added.
66
73
  */
67
74
  function render_block_core_navigation_submenu( $attributes, $content, $block ) {
68
- $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] );
69
- $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind'];
70
- $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] );
71
-
72
- // Don't render the block's subtree if it is a draft.
73
- if ( $is_post_type && $navigation_link_has_id && 'publish' !== get_post_status( $attributes['id'] ) ) {
74
- return '';
75
+ // Check if this navigation item should render based on post status.
76
+ if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
77
+ if ( ! gutenberg_block_core_shared_navigation_item_should_render( $attributes, $block ) ) {
78
+ return '';
79
+ }
75
80
  }
76
81
 
77
82
  // Don't render the block's subtree if it has no label.
@@ -82,9 +87,15 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
82
87
  $font_sizes = block_core_navigation_submenu_build_css_font_sizes( $block->context );
83
88
  $style_attribute = $font_sizes['inline_styles'];
84
89
 
85
- $has_submenu = count( $block->inner_blocks ) > 0;
86
- $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] );
87
- $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind );
90
+ // Render inner blocks first to check if any menu items will actually display.
91
+ $inner_blocks_html = '';
92
+ foreach ( $block->inner_blocks as $inner_block ) {
93
+ $inner_blocks_html .= $inner_block->render();
94
+ }
95
+ $has_submenu = ! empty( trim( $inner_blocks_html ) );
96
+
97
+ $kind = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] );
98
+ $is_active = ! empty( $attributes['id'] ) && get_queried_object_id() === (int) $attributes['id'] && ! empty( get_queried_object()->$kind );
88
99
 
89
100
  if ( is_post_type_archive() && ! empty( $attributes['url'] ) ) {
90
101
  $queried_archive_link = get_post_type_archive_link( get_queried_object()->name );
@@ -190,7 +201,7 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
190
201
  $html .= '</a>';
191
202
  // End anchor tag content.
192
203
 
193
- if ( $show_submenu_indicators ) {
204
+ if ( $show_submenu_indicators && $has_submenu ) {
194
205
  // The submenu icon is rendered in a button here
195
206
  // so that there's a clickable element to open the submenu.
196
207
  $html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation__submenu-icon wp-block-navigation-submenu__toggle" aria-expanded="false">' . block_core_navigation_submenu_render_submenu_icon() . '</button>';
@@ -215,8 +226,9 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
215
226
 
216
227
  $html .= '</button>';
217
228
 
218
- $html .= '<span class="wp-block-navigation__submenu-icon">' . block_core_navigation_submenu_render_submenu_icon() . '</span>';
219
-
229
+ if ( $has_submenu ) {
230
+ $html .= '<span class="wp-block-navigation__submenu-icon">' . block_core_navigation_submenu_render_submenu_icon() . '</span>';
231
+ }
220
232
  }
221
233
 
222
234
  if ( $has_submenu ) {
@@ -248,11 +260,6 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
248
260
  $style_attribute = $colors_supports['style'];
249
261
  }
250
262
 
251
- $inner_blocks_html = '';
252
- foreach ( $block->inner_blocks as $inner_block ) {
253
- $inner_blocks_html .= $inner_block->render();
254
- }
255
-
256
263
  if ( strpos( $inner_blocks_html, 'current-menu-item' ) ) {
257
264
  $tag_processor = new WP_HTML_Tag_Processor( $html );
258
265
  while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item' ) ) ) {
@@ -13,6 +13,7 @@
13
13
  "customClassName": false,
14
14
  "className": false,
15
15
  "html": false,
16
+ "visibility": false,
16
17
  "interactivity": {
17
18
  "clientNavigation": true
18
19
  }
@@ -65,7 +65,8 @@ if ( window.__experimentalContentOnlyInspectorFields ) {
65
65
  {
66
66
  id: 'content',
67
67
  label: __( 'Content' ),
68
- type: 'richtext',
68
+ type: 'text',
69
+ Edit: 'rich-text', // TODO: replace with custom component
69
70
  },
70
71
  ];
71
72
  settings[ formKey ] = {