@wordpress/block-library 9.40.2-next.v.202602271551.0 → 9.40.2

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 (250) hide show
  1. package/build/accordion/view.cjs +34 -0
  2. package/build/accordion/view.cjs.map +2 -2
  3. package/build/comment-reply-link/edit.cjs +2 -1
  4. package/build/comment-reply-link/edit.cjs.map +2 -2
  5. package/build/comments/edit/placeholder.cjs +2 -1
  6. package/build/comments/edit/placeholder.cjs.map +2 -2
  7. package/build/cover/edit/index.cjs +39 -26
  8. package/build/cover/edit/index.cjs.map +2 -2
  9. package/build/cover/embed-video-utils.cjs +8 -6
  10. package/build/cover/embed-video-utils.cjs.map +2 -2
  11. package/build/embed/embed-preview.cjs +2 -1
  12. package/build/embed/embed-preview.cjs.map +2 -2
  13. package/build/html/block.json +2 -1
  14. package/build/html/modal.cjs +147 -230
  15. package/build/html/modal.cjs.map +3 -3
  16. package/build/icon/block.json +3 -12
  17. package/build/image/image.cjs +1 -1
  18. package/build/image/image.cjs.map +2 -2
  19. package/build/latest-comments/deprecated.cjs +33 -0
  20. package/build/latest-comments/deprecated.cjs.map +2 -2
  21. package/build/navigation/edit/index.cjs +125 -123
  22. package/build/navigation/edit/index.cjs.map +3 -3
  23. package/build/navigation/edit/leaf-more-menu.cjs +6 -68
  24. package/build/navigation/edit/leaf-more-menu.cjs.map +3 -3
  25. package/build/navigation/edit/menu-inspector-controls.cjs +91 -20
  26. package/build/navigation/edit/menu-inspector-controls.cjs.map +3 -3
  27. package/build/navigation/edit/navigation-menu-selector.cjs +4 -5
  28. package/build/navigation/edit/navigation-menu-selector.cjs.map +3 -3
  29. package/build/navigation/edit/placeholder/index.cjs +2 -2
  30. package/build/navigation/edit/placeholder/index.cjs.map +3 -3
  31. package/build/navigation/use-navigation-entities.cjs +67 -0
  32. package/build/navigation/use-navigation-entities.cjs.map +7 -0
  33. package/build/navigation/view.cjs +9 -2
  34. package/build/navigation/view.cjs.map +2 -2
  35. package/build/navigation-link/edit.cjs +2 -1
  36. package/build/navigation-link/edit.cjs.map +2 -2
  37. package/build/navigation-link/shared/use-handle-link-change.cjs +19 -3
  38. package/build/navigation-link/shared/use-handle-link-change.cjs.map +3 -3
  39. package/build/navigation-link/shared/use-link-preview.cjs +2 -2
  40. package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
  41. package/build/navigation-submenu/edit.cjs +8 -22
  42. package/build/navigation-submenu/edit.cjs.map +2 -2
  43. package/build/nextpage/block.json +0 -1
  44. package/build/paragraph/edit.cjs +1 -1
  45. package/build/paragraph/edit.cjs.map +1 -1
  46. package/build/post-navigation-link/block.json +3 -1
  47. package/build/post-navigation-link/edit.cjs +36 -2
  48. package/build/post-navigation-link/edit.cjs.map +3 -3
  49. package/build/post-navigation-link/index.cjs +0 -2
  50. package/build/post-navigation-link/index.cjs.map +3 -3
  51. package/build/post-title/block.json +3 -1
  52. package/build/post-title/deprecated.cjs +1 -82
  53. package/build/post-title/deprecated.cjs.map +3 -3
  54. package/build/post-title/edit.cjs +36 -10
  55. package/build/post-title/edit.cjs.map +3 -3
  56. package/build/query-title/block.json +3 -1
  57. package/build/query-title/deprecated.cjs +1 -70
  58. package/build/query-title/deprecated.cjs.map +3 -3
  59. package/build/query-title/edit.cjs +35 -17
  60. package/build/query-title/edit.cjs.map +3 -3
  61. package/build/shortcode/block.json +2 -1
  62. package/build/site-tagline/block.json +4 -6
  63. package/build/site-tagline/deprecated.cjs +1 -66
  64. package/build/site-tagline/deprecated.cjs.map +3 -3
  65. package/build/site-tagline/edit.cjs +28 -14
  66. package/build/site-tagline/edit.cjs.map +3 -3
  67. package/build/site-title/block.json +3 -1
  68. package/build/site-title/deprecated.cjs +1 -79
  69. package/build/site-title/deprecated.cjs.map +3 -3
  70. package/build/site-title/edit.cjs +30 -14
  71. package/build/site-title/edit.cjs.map +3 -3
  72. package/build/tabs-menu-item/block.json +26 -1
  73. package/build/tabs-menu-item/controls.cjs +100 -2
  74. package/build/tabs-menu-item/controls.cjs.map +3 -3
  75. package/build/tabs-menu-item/edit.cjs +65 -6
  76. package/build/tabs-menu-item/edit.cjs.map +2 -2
  77. package/build/tabs-menu-item/save.cjs +15 -1
  78. package/build/tabs-menu-item/save.cjs.map +2 -2
  79. package/build-module/accordion/view.mjs +35 -1
  80. package/build-module/accordion/view.mjs.map +2 -2
  81. package/build-module/comment-reply-link/edit.mjs +3 -2
  82. package/build-module/comment-reply-link/edit.mjs.map +2 -2
  83. package/build-module/comments/edit/placeholder.mjs +3 -2
  84. package/build-module/comments/edit/placeholder.mjs.map +2 -2
  85. package/build-module/cover/edit/index.mjs +47 -29
  86. package/build-module/cover/edit/index.mjs.map +2 -2
  87. package/build-module/cover/embed-video-utils.mjs +7 -5
  88. package/build-module/cover/embed-video-utils.mjs.map +2 -2
  89. package/build-module/embed/embed-preview.mjs +2 -1
  90. package/build-module/embed/embed-preview.mjs.map +2 -2
  91. package/build-module/html/block.json +2 -1
  92. package/build-module/html/modal.mjs +149 -232
  93. package/build-module/html/modal.mjs.map +2 -2
  94. package/build-module/icon/block.json +3 -12
  95. package/build-module/image/image.mjs +1 -1
  96. package/build-module/image/image.mjs.map +2 -2
  97. package/build-module/latest-comments/deprecated.mjs +33 -0
  98. package/build-module/latest-comments/deprecated.mjs.map +2 -2
  99. package/build-module/navigation/edit/index.mjs +125 -123
  100. package/build-module/navigation/edit/index.mjs.map +2 -2
  101. package/build-module/navigation/edit/leaf-more-menu.mjs +7 -73
  102. package/build-module/navigation/edit/leaf-more-menu.mjs.map +2 -2
  103. package/build-module/navigation/edit/menu-inspector-controls.mjs +101 -21
  104. package/build-module/navigation/edit/menu-inspector-controls.mjs.map +2 -2
  105. package/build-module/navigation/edit/navigation-menu-selector.mjs +5 -6
  106. package/build-module/navigation/edit/navigation-menu-selector.mjs.map +2 -2
  107. package/build-module/navigation/edit/placeholder/index.mjs +2 -2
  108. package/build-module/navigation/edit/placeholder/index.mjs.map +2 -2
  109. package/build-module/navigation/use-navigation-entities.mjs +46 -0
  110. package/build-module/navigation/use-navigation-entities.mjs.map +7 -0
  111. package/build-module/navigation/view.mjs +9 -2
  112. package/build-module/navigation/view.mjs.map +2 -2
  113. package/build-module/navigation-link/edit.mjs +2 -1
  114. package/build-module/navigation-link/edit.mjs.map +2 -2
  115. package/build-module/navigation-link/shared/use-handle-link-change.mjs +19 -3
  116. package/build-module/navigation-link/shared/use-handle-link-change.mjs.map +2 -2
  117. package/build-module/navigation-link/shared/use-link-preview.mjs +2 -2
  118. package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
  119. package/build-module/navigation-submenu/edit.mjs +9 -23
  120. package/build-module/navigation-submenu/edit.mjs.map +2 -2
  121. package/build-module/nextpage/block.json +0 -1
  122. package/build-module/paragraph/edit.mjs +2 -2
  123. package/build-module/paragraph/edit.mjs.map +1 -1
  124. package/build-module/post-navigation-link/block.json +3 -1
  125. package/build-module/post-navigation-link/edit.mjs +30 -3
  126. package/build-module/post-navigation-link/edit.mjs.map +2 -2
  127. package/build-module/post-navigation-link/index.mjs +0 -2
  128. package/build-module/post-navigation-link/index.mjs.map +2 -2
  129. package/build-module/post-title/block.json +3 -1
  130. package/build-module/post-title/deprecated.mjs +1 -82
  131. package/build-module/post-title/deprecated.mjs.map +2 -2
  132. package/build-module/post-title/edit.mjs +27 -10
  133. package/build-module/post-title/edit.mjs.map +2 -2
  134. package/build-module/query-title/block.json +3 -1
  135. package/build-module/query-title/deprecated.mjs +1 -70
  136. package/build-module/query-title/deprecated.mjs.map +2 -2
  137. package/build-module/query-title/edit.mjs +36 -17
  138. package/build-module/query-title/edit.mjs.map +2 -2
  139. package/build-module/shortcode/block.json +2 -1
  140. package/build-module/site-tagline/block.json +4 -6
  141. package/build-module/site-tagline/deprecated.mjs +1 -66
  142. package/build-module/site-tagline/deprecated.mjs.map +2 -2
  143. package/build-module/site-tagline/edit.mjs +29 -14
  144. package/build-module/site-tagline/edit.mjs.map +2 -2
  145. package/build-module/site-title/block.json +3 -1
  146. package/build-module/site-title/deprecated.mjs +1 -79
  147. package/build-module/site-title/deprecated.mjs.map +2 -2
  148. package/build-module/site-title/edit.mjs +31 -14
  149. package/build-module/site-title/edit.mjs.map +2 -2
  150. package/build-module/tabs-menu-item/block.json +26 -1
  151. package/build-module/tabs-menu-item/controls.mjs +104 -3
  152. package/build-module/tabs-menu-item/controls.mjs.map +2 -2
  153. package/build-module/tabs-menu-item/edit.mjs +66 -6
  154. package/build-module/tabs-menu-item/edit.mjs.map +2 -2
  155. package/build-module/tabs-menu-item/save.mjs +15 -1
  156. package/build-module/tabs-menu-item/save.mjs.map +2 -2
  157. package/build-style/editor-rtl.css +20 -6
  158. package/build-style/editor.css +20 -6
  159. package/build-style/html/editor-rtl.css +10 -6
  160. package/build-style/html/editor.css +10 -6
  161. package/build-style/icon/editor-rtl.css +5 -0
  162. package/build-style/icon/editor.css +5 -0
  163. package/build-style/navigation/style-rtl.css +15 -1
  164. package/build-style/navigation/style.css +15 -1
  165. package/build-style/navigation-overlay-close/style-rtl.css +3 -3
  166. package/build-style/navigation-overlay-close/style.css +3 -3
  167. package/build-style/style-rtl.css +30 -7
  168. package/build-style/style.css +30 -7
  169. package/build-style/tabs-menu-item/editor-rtl.css +5 -0
  170. package/build-style/tabs-menu-item/editor.css +5 -0
  171. package/build-style/tabs-menu-item/style-rtl.css +12 -3
  172. package/build-style/tabs-menu-item/style.css +12 -3
  173. package/package.json +38 -38
  174. package/src/accordion/view.js +44 -1
  175. package/src/accordion-item/index.php +18 -5
  176. package/src/comment-reply-link/edit.js +5 -2
  177. package/src/comments/edit/placeholder.js +5 -2
  178. package/src/cover/edit/index.js +72 -35
  179. package/src/cover/embed-video-utils.js +9 -7
  180. package/src/cover/index.php +12 -4
  181. package/src/details/index.php +47 -0
  182. package/src/embed/embed-preview.js +6 -5
  183. package/src/html/block.json +2 -1
  184. package/src/html/editor.scss +15 -5
  185. package/src/html/modal.js +32 -99
  186. package/src/icon/block.json +3 -12
  187. package/src/icon/editor.scss +5 -0
  188. package/src/icon/index.php +3 -1
  189. package/src/image/image.js +14 -15
  190. package/src/image/index.php +7 -5
  191. package/src/latest-comments/deprecated.js +33 -0
  192. package/src/navigation/edit/index.js +136 -134
  193. package/src/navigation/edit/leaf-more-menu.js +11 -86
  194. package/src/navigation/edit/menu-inspector-controls.js +142 -23
  195. package/src/navigation/edit/navigation-menu-selector.js +5 -6
  196. package/src/navigation/edit/placeholder/index.js +2 -3
  197. package/src/navigation/edit/test/navigation-menu-selector.js +20 -23
  198. package/src/navigation/index.php +62 -9
  199. package/src/navigation/style.scss +17 -1
  200. package/src/navigation/use-navigation-entities.js +72 -0
  201. package/src/navigation/view.js +14 -2
  202. package/src/navigation-link/edit.js +1 -0
  203. package/src/navigation-link/shared/test/use-handle-link-change.test.js +212 -0
  204. package/src/navigation-link/shared/test/use-link-preview.test.js +9 -0
  205. package/src/navigation-link/shared/use-handle-link-change.js +36 -9
  206. package/src/navigation-link/shared/use-link-preview.js +6 -9
  207. package/src/navigation-overlay-close/style.scss +3 -3
  208. package/src/navigation-submenu/edit.js +11 -28
  209. package/src/nextpage/block.json +0 -1
  210. package/src/page-list/index.php +1 -1
  211. package/src/paragraph/edit.js +2 -2
  212. package/src/post-featured-image/index.php +4 -2
  213. package/src/post-navigation-link/block.json +3 -1
  214. package/src/post-navigation-link/edit.js +35 -2
  215. package/src/post-navigation-link/index.js +0 -2
  216. package/src/post-title/block.json +3 -1
  217. package/src/post-title/deprecated.js +1 -86
  218. package/src/post-title/edit.js +18 -2
  219. package/src/query-title/block.json +3 -1
  220. package/src/query-title/deprecated.js +1 -74
  221. package/src/query-title/edit.js +27 -11
  222. package/src/query-title/index.php +1 -1
  223. package/src/shortcode/block.json +2 -1
  224. package/src/site-tagline/block.json +4 -6
  225. package/src/site-tagline/deprecated.js +1 -70
  226. package/src/site-tagline/edit.js +22 -9
  227. package/src/site-title/block.json +3 -1
  228. package/src/site-title/deprecated.js +1 -83
  229. package/src/site-title/edit.js +22 -9
  230. package/src/tabs-menu-item/block.json +26 -1
  231. package/src/tabs-menu-item/controls.js +108 -0
  232. package/src/tabs-menu-item/edit.js +79 -6
  233. package/src/tabs-menu-item/editor.scss +6 -0
  234. package/src/tabs-menu-item/save.js +26 -1
  235. package/src/tabs-menu-item/style.scss +14 -3
  236. package/build/navigation/edit/navigation-link-ui.cjs +0 -97
  237. package/build/navigation/edit/navigation-link-ui.cjs.map +0 -7
  238. package/build/navigation/edit/navigation-list-view-header.cjs +0 -86
  239. package/build/navigation/edit/navigation-list-view-header.cjs.map +0 -7
  240. package/build/post-navigation-link/deprecated.cjs +0 -100
  241. package/build/post-navigation-link/deprecated.cjs.map +0 -7
  242. package/build-module/navigation/edit/navigation-link-ui.mjs +0 -76
  243. package/build-module/navigation/edit/navigation-link-ui.mjs.map +0 -7
  244. package/build-module/navigation/edit/navigation-list-view-header.mjs +0 -58
  245. package/build-module/navigation/edit/navigation-list-view-header.mjs.map +0 -7
  246. package/build-module/post-navigation-link/deprecated.mjs +0 -69
  247. package/build-module/post-navigation-link/deprecated.mjs.map +0 -7
  248. package/src/navigation/edit/navigation-link-ui.js +0 -115
  249. package/src/navigation/edit/navigation-list-view-header.js +0 -62
  250. package/src/post-navigation-link/deprecated.js +0 -72
@@ -6,7 +6,12 @@ import {
6
6
  InspectorControls,
7
7
  store as blockEditorStore,
8
8
  } from '@wordpress/block-editor';
9
- import { PanelBody, Spinner } from '@wordpress/components';
9
+ import {
10
+ PanelBody,
11
+ Spinner,
12
+ __experimentalHStack as HStack,
13
+ __experimentalHeading as Heading,
14
+ } from '@wordpress/components';
10
15
  import { useSelect, useDispatch } from '@wordpress/data';
11
16
  import { __, sprintf } from '@wordpress/i18n';
12
17
  import { useContext } from '@wordpress/element';
@@ -19,18 +24,121 @@ import { unlock } from '../../lock-unlock';
19
24
  import DeletedNavigationWarning from './deleted-navigation-warning';
20
25
  import useNavigationMenu from '../use-navigation-menu';
21
26
  import LeafMoreMenu from './leaf-more-menu';
22
- import { NavigationLinkUI } from './navigation-link-ui';
23
- import NavigationListViewHeader from './navigation-list-view-header';
27
+ import {
28
+ LinkUI,
29
+ updateAttributes,
30
+ useEntityBinding,
31
+ } from '../../navigation-link/shared';
24
32
 
25
33
  const actionLabel =
26
34
  /* translators: %s: The name of a menu. */ __( "Switch to '%s'" );
35
+ const BLOCKS_WITH_LINK_UI_SUPPORT = [
36
+ 'core/navigation-link',
37
+ 'core/navigation-submenu',
38
+ ];
27
39
  const {
28
40
  PrivateListView,
41
+ useBlockDisplayTitle,
29
42
  PrivateBlockContext,
30
43
  useListViewPanelState,
31
- useBlockDisplayTitle,
32
44
  } = unlock( blockEditorPrivateApis );
33
45
 
46
+ function AdditionalBlockContent( { block, insertedBlock, setInsertedBlock } ) {
47
+ const { updateBlockAttributes, removeBlock } =
48
+ useDispatch( blockEditorStore );
49
+
50
+ const supportsLinkControls = BLOCKS_WITH_LINK_UI_SUPPORT?.includes(
51
+ insertedBlock?.name
52
+ );
53
+ const blockWasJustInserted = insertedBlock?.clientId === block.clientId;
54
+ const showLinkControls = supportsLinkControls && blockWasJustInserted;
55
+
56
+ // Get binding utilities for the inserted block
57
+ const { createBinding, clearBinding } = useEntityBinding( {
58
+ clientId: insertedBlock?.clientId,
59
+ attributes: insertedBlock?.attributes || {},
60
+ } );
61
+
62
+ if ( ! showLinkControls ) {
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Cleanup function for auto-inserted Navigation Link blocks.
68
+ *
69
+ * Removes the block if it has no URL and clears the inserted block state.
70
+ * This ensures consistent cleanup behavior across different contexts.
71
+ */
72
+ const cleanupInsertedBlock = () => {
73
+ // Prevent automatic block selection when removing blocks in list view context
74
+ // This avoids focus stealing that would close the list view and switch to canvas
75
+ const shouldAutoSelectBlock = false;
76
+
77
+ // Follows the exact same pattern as Navigation Link block's onClose handler
78
+ // If there is no URL then remove the auto-inserted block to avoid empty blocks
79
+ if ( ! insertedBlock?.attributes?.url && insertedBlock?.clientId ) {
80
+ // Remove the block entirely to avoid poor UX
81
+ // This matches the Navigation Link block's behavior
82
+ removeBlock( insertedBlock.clientId, shouldAutoSelectBlock );
83
+ }
84
+ setInsertedBlock( null );
85
+ };
86
+
87
+ const setInsertedBlockAttributes =
88
+ ( _insertedBlockClientId ) => ( _updatedAttributes ) => {
89
+ if ( ! _insertedBlockClientId ) {
90
+ return;
91
+ }
92
+ updateBlockAttributes( _insertedBlockClientId, _updatedAttributes );
93
+ };
94
+
95
+ // Wrapper function to clean up original block when a new block is selected
96
+ const handleSetInsertedBlock = ( newBlock ) => {
97
+ // Prevent automatic block selection when removing blocks in list view context
98
+ // This avoids focus stealing that would close the list view and switch to canvas
99
+ const shouldAutoSelectBlock = false;
100
+
101
+ // If we have an existing inserted block and a new block is being set,
102
+ // remove the original block to avoid duplicates
103
+ if ( insertedBlock?.clientId && newBlock ) {
104
+ removeBlock( insertedBlock.clientId, shouldAutoSelectBlock );
105
+ }
106
+ setInsertedBlock( newBlock );
107
+ };
108
+
109
+ return (
110
+ <LinkUI
111
+ clientId={ insertedBlock?.clientId }
112
+ link={ insertedBlock?.attributes }
113
+ onBlockInsert={ handleSetInsertedBlock }
114
+ onClose={ () => {
115
+ // Use cleanup function
116
+ cleanupInsertedBlock();
117
+ } }
118
+ onChange={ ( updatedValue ) => {
119
+ // updateAttributes determines the final state and returns metadata
120
+ const { isEntityLink, attributes: updatedAttributes } =
121
+ updateAttributes(
122
+ updatedValue,
123
+ setInsertedBlockAttributes( insertedBlock?.clientId ),
124
+ insertedBlock?.attributes
125
+ );
126
+
127
+ // Handle URL binding based on the final computed state
128
+ // Only create bindings for entity links (posts, pages, taxonomies)
129
+ // Never create bindings for custom links (manual URLs)
130
+ if ( isEntityLink ) {
131
+ createBinding( updatedAttributes );
132
+ } else {
133
+ clearBinding();
134
+ }
135
+
136
+ setInsertedBlock( null );
137
+ } }
138
+ />
139
+ );
140
+ }
141
+
34
142
  const MainContent = ( {
35
143
  clientId,
36
144
  currentMenuId,
@@ -86,7 +194,7 @@ const MainContent = ( {
86
194
  description={ description }
87
195
  showAppender
88
196
  blockSettingsMenu={ LeafMoreMenu }
89
- additionalBlockContent={ NavigationLinkUI }
197
+ additionalBlockContent={ AdditionalBlockContent }
90
198
  onSelect={ openListViewContentPanel }
91
199
  />
92
200
  </div>
@@ -124,23 +232,34 @@ const MenuInspectorControls = ( props ) => {
124
232
  return (
125
233
  <InspectorControls group="list">
126
234
  <PanelBody title={ null }>
127
- <NavigationListViewHeader
128
- clientId={ clientId }
129
- blockEditingMode={ blockEditingMode }
130
- currentMenuId={ currentMenuId }
131
- onSelectClassicMenu={ onSelectClassicMenu }
132
- onSelectNavigationMenu={ onSelectNavigationMenu }
133
- onCreateNew={ onCreateNew }
134
- createNavigationMenuIsSuccess={
135
- createNavigationMenuIsSuccess
136
- }
137
- createNavigationMenuIsError={
138
- createNavigationMenuIsError
139
- }
140
- isManageMenusButtonDisabled={
141
- isManageMenusButtonDisabled
142
- }
143
- />
235
+ <HStack className="wp-block-navigation-off-canvas-editor__header">
236
+ <Heading
237
+ className="wp-block-navigation-off-canvas-editor__title"
238
+ level={ 2 }
239
+ >
240
+ { blockTitle }
241
+ </Heading>
242
+ { blockEditingMode === 'default' && (
243
+ <NavigationMenuSelector
244
+ currentMenuId={ currentMenuId }
245
+ onSelectClassicMenu={ onSelectClassicMenu }
246
+ onSelectNavigationMenu={
247
+ onSelectNavigationMenu
248
+ }
249
+ onCreateNew={ onCreateNew }
250
+ createNavigationMenuIsSuccess={
251
+ createNavigationMenuIsSuccess
252
+ }
253
+ createNavigationMenuIsError={
254
+ createNavigationMenuIsError
255
+ }
256
+ actionLabel={ actionLabel }
257
+ isManageMenusButtonDisabled={
258
+ isManageMenusButtonDisabled
259
+ }
260
+ />
261
+ ) }
262
+ </HStack>
144
263
  <MainContent
145
264
  { ...props }
146
265
  expandRevision={ expandRevision }
@@ -154,7 +273,7 @@ const MenuInspectorControls = ( props ) => {
154
273
  return (
155
274
  <InspectorControls group="list">
156
275
  <PanelBody
157
- title={ blockTitle }
276
+ title={ __( 'Navigation' ) }
158
277
  opened={ isOpened }
159
278
  onToggle={ handleToggle }
160
279
  >
@@ -11,12 +11,13 @@ import { moreVertical } from '@wordpress/icons';
11
11
  import { __, sprintf } from '@wordpress/i18n';
12
12
  import { decodeEntities } from '@wordpress/html-entities';
13
13
  import { useEffect, useMemo, useState } from '@wordpress/element';
14
- import { useEntityRecords, useEntityProp } from '@wordpress/core-data';
14
+ import { useEntityProp } from '@wordpress/core-data';
15
15
 
16
16
  /**
17
17
  * Internal dependencies
18
18
  */
19
19
  import useNavigationMenu from '../use-navigation-menu';
20
+ import useNavigationEntities from '../use-navigation-entities';
20
21
 
21
22
  function buildMenuLabel( title, id, status ) {
22
23
  if ( ! title ) {
@@ -52,10 +53,7 @@ function NavigationMenuSelector( {
52
53
 
53
54
  actionLabel = actionLabel || createActionLabel;
54
55
 
55
- const { records: classicMenus } = useEntityRecords( 'root', 'menu', {
56
- per_page: -1,
57
- context: 'view',
58
- } );
56
+ const { menus: classicMenus } = useNavigationEntities();
59
57
 
60
58
  const {
61
59
  navigationMenus,
@@ -69,7 +67,8 @@ function NavigationMenuSelector( {
69
67
  const [ currentTitle ] = useEntityProp(
70
68
  'postType',
71
69
  'wp_navigation',
72
- 'title'
70
+ 'title',
71
+ currentMenuId
73
72
  );
74
73
 
75
74
  const menuChoices = useMemo( () => {
@@ -6,11 +6,11 @@ import { __ } from '@wordpress/i18n';
6
6
  import { navigation, Icon } from '@wordpress/icons';
7
7
  import { speak } from '@wordpress/a11y';
8
8
  import { useEffect } from '@wordpress/element';
9
- import { useEntityRecords } from '@wordpress/core-data';
10
9
 
11
10
  /**
12
11
  * Internal dependencies
13
12
  */
13
+ import useNavigationEntities from '../../use-navigation-entities';
14
14
  import PlaceholderPreview from './placeholder-preview';
15
15
  import NavigationMenuSelector from '../navigation-menu-selector';
16
16
 
@@ -24,8 +24,7 @@ export default function NavigationPlaceholder( {
24
24
  onSelectClassicMenu,
25
25
  onCreateEmpty,
26
26
  } ) {
27
- const { isResolving: isResolvingMenus, hasResolved: hasResolvedMenus } =
28
- useEntityRecords( 'root', 'menu', { per_page: -1, context: 'view' } );
27
+ const { isResolvingMenus, hasResolvedMenus } = useNavigationEntities();
29
28
 
30
29
  useEffect( () => {
31
30
  if ( ! isSelected ) {
@@ -4,16 +4,12 @@
4
4
  import { render, screen } from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
- /**
8
- * WordPress dependencies
9
- */
10
- import { useEntityRecords } from '@wordpress/core-data';
11
-
12
7
  /**
13
8
  * Internal dependencies
14
9
  */
15
10
  import NavigationMenuSelector from '../navigation-menu-selector';
16
11
  import useNavigationMenu from '../../use-navigation-menu';
12
+ import useNavigationEntities from '../../use-navigation-entities';
17
13
 
18
14
  jest.mock( '../../use-navigation-menu', () => {
19
15
  // This allows us to tweak the returned value on each test.
@@ -21,13 +17,14 @@ jest.mock( '../../use-navigation-menu', () => {
21
17
  return mock;
22
18
  } );
23
19
 
24
- jest.mock( '@wordpress/core-data', () => ( {
25
- ...jest.requireActual( '@wordpress/core-data' ),
26
- useEntityRecords: jest.fn(),
27
- } ) );
20
+ jest.mock( '../../use-navigation-entities', () => {
21
+ // This allows us to tweak the returned value on each test.
22
+ const mock = jest.fn();
23
+ return mock;
24
+ } );
28
25
 
29
- useEntityRecords.mockReturnValue( {
30
- records: [],
26
+ useNavigationEntities.mockReturnValue( {
27
+ menus: [],
31
28
  } );
32
29
 
33
30
  const navigationMenu1 = {
@@ -488,8 +485,8 @@ describe( 'NavigationMenuSelector', () => {
488
485
  it( 'should not show classic menus if there are no classic menus', async () => {
489
486
  const user = userEvent.setup();
490
487
 
491
- useEntityRecords.mockReturnValue( {
492
- records: [],
488
+ useNavigationEntities.mockReturnValue( {
489
+ menus: [],
493
490
  } );
494
491
 
495
492
  render( <NavigationMenuSelector /> );
@@ -510,8 +507,8 @@ describe( 'NavigationMenuSelector', () => {
510
507
  canUserCreateNavigationMenus: false,
511
508
  } );
512
509
 
513
- useEntityRecords.mockReturnValue( {
514
- records: classicMenusFixture,
510
+ useNavigationEntities.mockReturnValue( {
511
+ menus: classicMenusFixture,
515
512
  } );
516
513
 
517
514
  render( <NavigationMenuSelector /> );
@@ -532,8 +529,8 @@ describe( 'NavigationMenuSelector', () => {
532
529
  canUserCreateNavigationMenus: true,
533
530
  } );
534
531
 
535
- useEntityRecords.mockReturnValue( {
536
- records: classicMenusFixture,
532
+ useNavigationEntities.mockReturnValue( {
533
+ menus: classicMenusFixture,
537
534
  } );
538
535
 
539
536
  render( <NavigationMenuSelector /> );
@@ -569,8 +566,8 @@ describe( 'NavigationMenuSelector', () => {
569
566
  canUserCreateNavigationMenus: true,
570
567
  } );
571
568
 
572
- useEntityRecords.mockReturnValue( {
573
- records: classicMenusFixture,
569
+ useNavigationEntities.mockReturnValue( {
570
+ menus: classicMenusFixture,
574
571
  } );
575
572
 
576
573
  const { rerender } = render(
@@ -601,8 +598,8 @@ describe( 'NavigationMenuSelector', () => {
601
598
  canUserCreateNavigationMenus: true,
602
599
  } );
603
600
 
604
- useEntityRecords.mockReturnValue( {
605
- records: classicMenusFixture,
601
+ useNavigationEntities.mockReturnValue( {
602
+ menus: classicMenusFixture,
606
603
  } );
607
604
 
608
605
  rerender(
@@ -634,8 +631,8 @@ describe( 'NavigationMenuSelector', () => {
634
631
  canUserCreateNavigationMenus: true,
635
632
  } );
636
633
 
637
- useEntityRecords.mockReturnValue( {
638
- records: classicMenusFixture,
634
+ useNavigationEntities.mockReturnValue( {
635
+ menus: classicMenusFixture,
639
636
  } );
640
637
 
641
638
  // Simulate the menu being created and component being re-rendered.
@@ -425,7 +425,11 @@ class WP_Navigation_Block_Renderer {
425
425
  $full_template_part_id = $theme . '//' . $slug;
426
426
  $block_template = get_block_file_template( $full_template_part_id, 'wp_template_part' );
427
427
  if ( isset( $block_template->content ) ) {
428
- $parsed_blocks = parse_blocks( $block_template->content );
428
+ // Expand shortcodes before parsing blocks, matching the order in
429
+ // `render_block_core_template_part()`.
430
+ $content = shortcode_unautop( $block_template->content );
431
+ $content = do_shortcode( $content );
432
+ $parsed_blocks = parse_blocks( $content );
429
433
  $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
430
434
  // Disable overlay menu for any navigation blocks within the overlay to prevent nested overlays.
431
435
  $blocks = static::disable_overlay_menu_for_nested_navigation_blocks( $blocks );
@@ -449,6 +453,12 @@ class WP_Navigation_Block_Renderer {
449
453
  // Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
450
454
  $markup = serialize_blocks( $blocks );
451
455
  $markup = apply_block_hooks_to_content_from_post_object( $markup, $template_part_post );
456
+
457
+ // Expand shortcodes before parsing blocks, matching the order in
458
+ // `render_block_core_template_part()`.
459
+ $markup = shortcode_unautop( $markup );
460
+ $markup = do_shortcode( $markup );
461
+
452
462
  $blocks = parse_blocks( $markup );
453
463
 
454
464
  // Disable overlay menu for any navigation blocks within the overlay to prevent nested overlays.
@@ -697,19 +707,21 @@ class WP_Navigation_Block_Renderer {
697
707
  if ( ! empty( $attributes['overlay'] ) ) {
698
708
  // Get blocks from the overlay template part.
699
709
  $overlay_blocks = static::get_overlay_blocks_from_template_part( $attributes['overlay'], $attributes );
700
- // Check if overlay contains a navigation-overlay-close block.
701
- $has_custom_overlay_close_block = block_core_navigation_block_tree_has_block_type(
702
- $overlay_blocks,
703
- 'core/navigation-overlay-close',
704
- array( 'core/navigation' ) // Skip navigation blocks, as they cannot contain an overlay close block
705
- );
706
710
  // Render template part blocks directly without navigation container wrapper.
707
711
  $overlay_blocks_html = static::get_template_part_blocks_html( $overlay_blocks );
712
+ // Check if overlay contains a navigation-overlay-close block (detect in rendered HTML so it works with patterns).
713
+ $has_custom_overlay_close_block = block_core_navigation_overlay_html_has_close_block( $overlay_blocks_html );
708
714
  // Add Interactivity API directives to the overlay close block if present.
709
715
  if ( $has_custom_overlay_close_block && $is_interactive ) {
710
716
  $tags = new WP_HTML_Tag_Processor( $overlay_blocks_html );
711
717
  $overlay_blocks_html = block_core_navigation_add_directives_to_overlay_close( $tags );
712
718
  }
719
+ // Images in the overlay are hidden until the menu is opened. Pre-set
720
+ // fetchpriority="low" so that when wp_filter_content_tags() processes the
721
+ // parent template part, it sees the attribute already present and calls
722
+ // wp_get_loading_optimization_attributes() with fetchpriority="low", which both prevents
723
+ // fetchpriority="high" from being added and stops the LCP counter from being incremented.
724
+ $overlay_blocks_html = block_core_navigation_set_overlay_image_fetch_priority( $overlay_blocks_html );
713
725
  }
714
726
 
715
727
  $has_custom_overlay = ! empty( $overlay_blocks_html );
@@ -1088,6 +1100,28 @@ if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
1088
1100
  }
1089
1101
  }
1090
1102
 
1103
+ /**
1104
+ * Checks if the overlay HTML contains a navigation-overlay-close block.
1105
+ *
1106
+ * Uses WP_HTML_Tag_Processor to detect the close button in rendered output,
1107
+ * so it works when the overlay uses patterns (pattern content is rendered at
1108
+ * output time, not in the block tree).
1109
+ *
1110
+ * @since 7.0.0
1111
+ *
1112
+ * @param string $html The rendered overlay HTML.
1113
+ * @return bool True if a close button element is found.
1114
+ */
1115
+ function block_core_navigation_overlay_html_has_close_block( $html ) {
1116
+ $tags = new WP_HTML_Tag_Processor( $html );
1117
+ return $tags->next_tag(
1118
+ array(
1119
+ 'tag_name' => 'BUTTON',
1120
+ 'class_name' => 'wp-block-navigation-overlay-close',
1121
+ )
1122
+ );
1123
+ }
1124
+
1091
1125
  /**
1092
1126
  * Add Interactivity API directives to the navigation-overlay-close block
1093
1127
  * markup using the Tag Processor.
@@ -1111,6 +1145,25 @@ function block_core_navigation_add_directives_to_overlay_close( $tags ) {
1111
1145
  return $tags->get_updated_html();
1112
1146
  }
1113
1147
 
1148
+ /**
1149
+ * Sets fetchpriority="low" on all IMG tags within the navigation overlay.
1150
+ *
1151
+ * Images in the overlay are hidden until the menu is opened, so they should
1152
+ * not compete with any actual LCP element image on the page.
1153
+ *
1154
+ * @since 7.0.0
1155
+ *
1156
+ * @param string $overlay_blocks_html The rendered HTML of the overlay blocks.
1157
+ * @return string Modified HTML with fetchpriority="low" on all IMG tags.
1158
+ */
1159
+ function block_core_navigation_set_overlay_image_fetch_priority( string $overlay_blocks_html ): string {
1160
+ $tags = new WP_HTML_Tag_Processor( $overlay_blocks_html );
1161
+ while ( $tags->next_tag( 'IMG' ) ) {
1162
+ $tags->set_attribute( 'fetchpriority', 'low' );
1163
+ }
1164
+ return $tags->get_updated_html();
1165
+ }
1166
+
1114
1167
  /**
1115
1168
  * Add Interactivity API directives to the navigation-submenu and page-list
1116
1169
  * blocks markup using the Tag Processor.
@@ -1146,8 +1199,8 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut
1146
1199
  $open_on_hover = 'hover' === $computed_visibility;
1147
1200
 
1148
1201
  if ( $open_on_hover ) {
1149
- $tags->set_attribute( 'data-wp-on--mouseenter', 'actions.openMenuOnHover' );
1150
- $tags->set_attribute( 'data-wp-on--mouseleave', 'actions.closeMenuOnHover' );
1202
+ $tags->set_attribute( 'data-wp-on--pointerenter', 'actions.openMenuOnHover' );
1203
+ $tags->set_attribute( 'data-wp-on--pointerleave', 'actions.closeMenuOnHover' );
1151
1204
  }
1152
1205
 
1153
1206
  // Add directives to the toggle submenu button.
@@ -247,7 +247,13 @@ $navigation-icon-size: 24px;
247
247
  }
248
248
 
249
249
  // Show submenus on hover unless they open on click.
250
- &:not(.open-on-click):hover > .wp-block-navigation__submenu-container,
250
+ // Restricted to true-hover devices to prevent sticky touch-hover on mobile.
251
+ @media (hover: hover) {
252
+ &:not(.open-on-click):hover > .wp-block-navigation__submenu-container {
253
+ @include show-submenu;
254
+ min-width: 200px;
255
+ }
256
+ }
251
257
  // Keep submenus open when focus is within.
252
258
  &:not(.open-on-click):not(.open-on-hover-click):focus-within > .wp-block-navigation__submenu-container,
253
259
  // Show submenus on click.
@@ -694,8 +700,18 @@ button.wp-block-navigation-item__content {
694
700
  display: none;
695
701
  }
696
702
 
703
+ // Override justification dropdown menu positioning rules inside custom overlays.
704
+ // Mirrors the protection at lines 674-678 for default overlays, which does not
705
+ // apply here. Prevents .items-justified-right descendant rules from cascading
706
+ // right: 0 into the overlay and anchoring submenus off the left edge of the viewport.
707
+ // See: https://github.com/WordPress/gutenberg/issues/76276
697
708
  .wp-block-navigation__overlay-container {
698
709
  display: block;
710
+
711
+ .wp-block-navigation__submenu-container {
712
+ right: auto;
713
+ left: 0;
714
+ }
699
715
  }
700
716
  }
701
717
  }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useEntityRecords } from '@wordpress/core-data';
5
+
6
+ /**
7
+ * @typedef {Object} NavigationEntitiesData
8
+ * @property {Array|undefined} pages - a collection of WP Post entity objects of post type "Page".
9
+ * @property {boolean} isResolvingPages - indicates whether the request to fetch pages is currently resolving.
10
+ * @property {boolean} hasResolvedPages - indicates whether the request to fetch pages has finished resolving.
11
+ * @property {Array|undefined} menus - a collection of Menu entity objects.
12
+ * @property {boolean} isResolvingMenus - indicates whether the request to fetch menus is currently resolving.
13
+ * @property {boolean} hasResolvedMenus - indicates whether the request to fetch menus has finished resolving.
14
+ * @property {Array|undefined} menusItems - a collection of Menu Item entity objects for the current menuId.
15
+ * @property {boolean} hasResolvedMenuItems - indicates whether the request to fetch menuItems has finished resolving.
16
+ * @property {boolean} hasPages - indicates whether there is currently any data for pages.
17
+ * @property {boolean} hasMenus - indicates whether there is currently any data for menus.
18
+ */
19
+
20
+ /**
21
+ * Manages fetching and resolution state for all entities required
22
+ * for the Navigation block.
23
+ *
24
+ * @param {number} menuId the menu for which to retrieve menuItem data.
25
+ * @return { NavigationEntitiesData } the entity data.
26
+ */
27
+ export default function useNavigationEntities( menuId ) {
28
+ const {
29
+ records: menus,
30
+ isResolving: isResolvingMenus,
31
+ hasResolved: hasResolvedMenus,
32
+ } = useEntityRecords( 'root', 'menu', { per_page: -1, context: 'view' } );
33
+
34
+ const {
35
+ records: pages,
36
+ isResolving: isResolvingPages,
37
+ hasResolved: hasResolvedPages,
38
+ } = useEntityRecords( 'postType', 'page', {
39
+ parent: 0,
40
+ order: 'asc',
41
+ orderby: 'id',
42
+ per_page: -1,
43
+ context: 'view',
44
+ } );
45
+
46
+ const { records: menuItems, hasResolved: hasResolvedMenuItems } =
47
+ useEntityRecords(
48
+ 'root',
49
+ 'menuItem',
50
+ {
51
+ menus: menuId,
52
+ per_page: -1,
53
+ context: 'view',
54
+ },
55
+ { enabled: !! menuId }
56
+ );
57
+
58
+ return {
59
+ pages,
60
+ isResolvingPages,
61
+ hasResolvedPages,
62
+ hasPages: !! ( hasResolvedPages && pages?.length ),
63
+
64
+ menus,
65
+ isResolvingMenus,
66
+ hasResolvedMenus,
67
+ hasMenus: !! ( hasResolvedMenus && menus?.length ),
68
+
69
+ menuItems,
70
+ hasResolvedMenuItems,
71
+ };
72
+ }
@@ -82,7 +82,12 @@ const { state, actions } = store(
82
82
  },
83
83
  },
84
84
  actions: {
85
- openMenuOnHover() {
85
+ openMenuOnHover( event ) {
86
+ // Pointer events from touch should not open the submenu on hover;
87
+ // touch devices toggle via the click action instead.
88
+ if ( event?.pointerType === 'touch' ) {
89
+ return;
90
+ }
86
91
  const { type, overlayOpenedBy } = getContext();
87
92
  if (
88
93
  type === 'submenu' &&
@@ -93,7 +98,10 @@ const { state, actions } = store(
93
98
  actions.openMenu( 'hover' );
94
99
  }
95
100
  },
96
- closeMenuOnHover() {
101
+ closeMenuOnHover( event ) {
102
+ if ( event?.pointerType === 'touch' ) {
103
+ return;
104
+ }
97
105
  const { type, overlayOpenedBy } = getContext();
98
106
  if (
99
107
  type === 'submenu' &&
@@ -128,6 +136,10 @@ const { state, actions } = store(
128
136
  if ( menuOpenedBy.click || menuOpenedBy.focus ) {
129
137
  actions.closeMenu( 'click' );
130
138
  actions.closeMenu( 'focus' );
139
+ // Also clear hover in case it was set by a synthetic pointerenter
140
+ // on touch (e.g. the browser-fired mouseenter-equivalent before
141
+ // the click event), ensuring the submenu fully closes.
142
+ actions.closeMenu( 'hover' );
131
143
  } else {
132
144
  ctx.previousFocus = ref;
133
145
  actions.openMenu( 'click' );
@@ -177,6 +177,7 @@ export default function NavigationLinkEdit( {
177
177
  clientId,
178
178
  attributes,
179
179
  setAttributes,
180
+ allowTextUpdate: true,
180
181
  } );
181
182
 
182
183
  const [ isInvalid, isDraft ] = useIsInvalidLink(