@wordpress/block-library 9.38.0 → 9.38.1-next.v.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 (217) hide show
  1. package/build/block/block.json +2 -1
  2. package/build/breadcrumbs/edit.cjs +15 -5
  3. package/build/breadcrumbs/edit.cjs.map +2 -2
  4. package/build/breadcrumbs/index.cjs +1 -0
  5. package/build/breadcrumbs/index.cjs.map +2 -2
  6. package/build/comment-date/block.json +1 -0
  7. package/build/comment-edit-link/block.json +1 -3
  8. package/build/comment-edit-link/deprecated.cjs +91 -0
  9. package/build/comment-edit-link/deprecated.cjs.map +7 -0
  10. package/build/comment-edit-link/edit.cjs +6 -18
  11. package/build/comment-edit-link/edit.cjs.map +3 -3
  12. package/build/comment-edit-link/index.cjs +2 -0
  13. package/build/comment-edit-link/index.cjs.map +3 -3
  14. package/build/comment-reply-link/block.json +1 -5
  15. package/build/comment-reply-link/deprecated.cjs +84 -0
  16. package/build/comment-reply-link/deprecated.cjs.map +7 -0
  17. package/build/comment-reply-link/edit.cjs +10 -23
  18. package/build/comment-reply-link/edit.cjs.map +3 -3
  19. package/build/comment-reply-link/index.cjs +2 -0
  20. package/build/comment-reply-link/index.cjs.map +3 -3
  21. package/build/cover/edit/inspector-controls.cjs +1 -1
  22. package/build/cover/edit/inspector-controls.cjs.map +2 -2
  23. package/build/embed/util.cjs +9 -0
  24. package/build/embed/util.cjs.map +2 -2
  25. package/build/freeform/block.json +2 -1
  26. package/build/html/block.json +2 -1
  27. package/build/image/image.cjs +43 -9
  28. package/build/image/image.cjs.map +2 -2
  29. package/build/list-item/index.cjs +9 -1
  30. package/build/list-item/index.cjs.map +2 -2
  31. package/build/missing/block.json +2 -1
  32. package/build/more/block.json +2 -1
  33. package/build/navigation/block.json +5 -4
  34. package/build/navigation/deprecated.cjs +133 -5
  35. package/build/navigation/deprecated.cjs.map +2 -2
  36. package/build/navigation/edit/deleted-overlay-warning.cjs +70 -0
  37. package/build/navigation/edit/deleted-overlay-warning.cjs.map +7 -0
  38. package/build/navigation/edit/index.cjs +122 -65
  39. package/build/navigation/edit/index.cjs.map +3 -3
  40. package/build/navigation/edit/overlay-panel.cjs +10 -1
  41. package/build/navigation/edit/overlay-panel.cjs.map +3 -3
  42. package/build/navigation/edit/overlay-preview.cjs +120 -0
  43. package/build/navigation/edit/overlay-preview.cjs.map +7 -0
  44. package/build/navigation/edit/overlay-template-part-selector.cjs +59 -24
  45. package/build/navigation/edit/overlay-template-part-selector.cjs.map +3 -3
  46. package/build/navigation/edit/responsive-wrapper.cjs +12 -1
  47. package/build/navigation/edit/responsive-wrapper.cjs.map +3 -3
  48. package/build/navigation/edit/use-create-overlay.cjs +19 -2
  49. package/build/navigation/edit/use-create-overlay.cjs.map +3 -3
  50. package/build/navigation/utils/get-submenu-visibility.cjs +37 -0
  51. package/build/navigation/utils/get-submenu-visibility.cjs.map +7 -0
  52. package/build/navigation-link/edit.cjs +2 -40
  53. package/build/navigation-link/edit.cjs.map +2 -2
  54. package/build/navigation-link/shared/index.cjs +6 -0
  55. package/build/navigation-link/shared/index.cjs.map +2 -2
  56. package/build/navigation-link/shared/select-label-text.cjs +40 -0
  57. package/build/navigation-link/shared/select-label-text.cjs.map +7 -0
  58. package/build/navigation-link/shared/use-is-dragging-within.cjs +59 -0
  59. package/build/navigation-link/shared/use-is-dragging-within.cjs.map +7 -0
  60. package/build/navigation-submenu/block.json +1 -1
  61. package/build/navigation-submenu/edit.cjs +8 -47
  62. package/build/navigation-submenu/edit.cjs.map +2 -2
  63. package/build/nextpage/block.json +2 -1
  64. package/build/paragraph/block.json +1 -0
  65. package/build/shortcode/block.json +2 -1
  66. package/build/template-part/edit/index.cjs +1 -1
  67. package/build/template-part/edit/index.cjs.map +2 -2
  68. package/build/verse/block.json +1 -3
  69. package/build/verse/deprecated.cjs +74 -5
  70. package/build/verse/deprecated.cjs.map +3 -3
  71. package/build/verse/edit.cjs +33 -48
  72. package/build/verse/edit.cjs.map +3 -3
  73. package/build/verse/save.cjs +2 -16
  74. package/build/verse/save.cjs.map +3 -3
  75. package/build-module/block/block.json +2 -1
  76. package/build-module/breadcrumbs/edit.mjs +15 -5
  77. package/build-module/breadcrumbs/edit.mjs.map +2 -2
  78. package/build-module/breadcrumbs/index.mjs +1 -0
  79. package/build-module/breadcrumbs/index.mjs.map +2 -2
  80. package/build-module/comment-date/block.json +1 -0
  81. package/build-module/comment-edit-link/block.json +1 -3
  82. package/build-module/comment-edit-link/deprecated.mjs +60 -0
  83. package/build-module/comment-edit-link/deprecated.mjs.map +7 -0
  84. package/build-module/comment-edit-link/edit.mjs +7 -24
  85. package/build-module/comment-edit-link/edit.mjs.map +2 -2
  86. package/build-module/comment-edit-link/index.mjs +2 -0
  87. package/build-module/comment-edit-link/index.mjs.map +2 -2
  88. package/build-module/comment-reply-link/block.json +1 -5
  89. package/build-module/comment-reply-link/deprecated.mjs +53 -0
  90. package/build-module/comment-reply-link/deprecated.mjs.map +7 -0
  91. package/build-module/comment-reply-link/edit.mjs +12 -29
  92. package/build-module/comment-reply-link/edit.mjs.map +2 -2
  93. package/build-module/comment-reply-link/index.mjs +2 -0
  94. package/build-module/comment-reply-link/index.mjs.map +2 -2
  95. package/build-module/cover/edit/inspector-controls.mjs +1 -1
  96. package/build-module/cover/edit/inspector-controls.mjs.map +2 -2
  97. package/build-module/embed/util.mjs +8 -0
  98. package/build-module/embed/util.mjs.map +2 -2
  99. package/build-module/freeform/block.json +2 -1
  100. package/build-module/html/block.json +2 -1
  101. package/build-module/image/image.mjs +43 -9
  102. package/build-module/image/image.mjs.map +2 -2
  103. package/build-module/list-item/index.mjs +9 -1
  104. package/build-module/list-item/index.mjs.map +2 -2
  105. package/build-module/missing/block.json +2 -1
  106. package/build-module/more/block.json +2 -1
  107. package/build-module/navigation/block.json +5 -4
  108. package/build-module/navigation/deprecated.mjs +133 -5
  109. package/build-module/navigation/deprecated.mjs.map +2 -2
  110. package/build-module/navigation/edit/deleted-overlay-warning.mjs +49 -0
  111. package/build-module/navigation/edit/deleted-overlay-warning.mjs.map +7 -0
  112. package/build-module/navigation/edit/index.mjs +124 -65
  113. package/build-module/navigation/edit/index.mjs.map +2 -2
  114. package/build-module/navigation/edit/overlay-panel.mjs +10 -1
  115. package/build-module/navigation/edit/overlay-panel.mjs.map +2 -2
  116. package/build-module/navigation/edit/overlay-preview.mjs +99 -0
  117. package/build-module/navigation/edit/overlay-preview.mjs.map +7 -0
  118. package/build-module/navigation/edit/overlay-template-part-selector.mjs +61 -26
  119. package/build-module/navigation/edit/overlay-template-part-selector.mjs.map +2 -2
  120. package/build-module/navigation/edit/responsive-wrapper.mjs +12 -1
  121. package/build-module/navigation/edit/responsive-wrapper.mjs.map +2 -2
  122. package/build-module/navigation/edit/use-create-overlay.mjs +21 -4
  123. package/build-module/navigation/edit/use-create-overlay.mjs.map +2 -2
  124. package/build-module/navigation/utils/get-submenu-visibility.mjs +12 -0
  125. package/build-module/navigation/utils/get-submenu-visibility.mjs.map +7 -0
  126. package/build-module/navigation-link/edit.mjs +4 -40
  127. package/build-module/navigation-link/edit.mjs.map +2 -2
  128. package/build-module/navigation-link/shared/index.mjs +4 -0
  129. package/build-module/navigation-link/shared/index.mjs.map +2 -2
  130. package/build-module/navigation-link/shared/select-label-text.mjs +15 -0
  131. package/build-module/navigation-link/shared/select-label-text.mjs.map +7 -0
  132. package/build-module/navigation-link/shared/use-is-dragging-within.mjs +34 -0
  133. package/build-module/navigation-link/shared/use-is-dragging-within.mjs.map +7 -0
  134. package/build-module/navigation-submenu/block.json +1 -1
  135. package/build-module/navigation-submenu/edit.mjs +10 -47
  136. package/build-module/navigation-submenu/edit.mjs.map +2 -2
  137. package/build-module/nextpage/block.json +2 -1
  138. package/build-module/paragraph/block.json +1 -0
  139. package/build-module/shortcode/block.json +2 -1
  140. package/build-module/template-part/edit/index.mjs +1 -1
  141. package/build-module/template-part/edit/index.mjs.map +2 -2
  142. package/build-module/verse/block.json +1 -3
  143. package/build-module/verse/deprecated.mjs +74 -5
  144. package/build-module/verse/deprecated.mjs.map +2 -2
  145. package/build-module/verse/edit.mjs +35 -55
  146. package/build-module/verse/edit.mjs.map +2 -2
  147. package/build-module/verse/save.mjs +2 -6
  148. package/build-module/verse/save.mjs.map +2 -2
  149. package/build-style/editor-rtl.css +48 -0
  150. package/build-style/editor.css +48 -0
  151. package/build-style/media-text/style-rtl.css +2 -0
  152. package/build-style/media-text/style.css +2 -0
  153. package/build-style/navigation/editor-rtl.css +48 -0
  154. package/build-style/navigation/editor.css +48 -0
  155. package/build-style/navigation/style-rtl.css +64 -18
  156. package/build-style/navigation/style.css +64 -18
  157. package/build-style/style-rtl.css +67 -18
  158. package/build-style/style.css +67 -18
  159. package/build-style/verse/style-rtl.css +1 -0
  160. package/build-style/verse/style.css +1 -0
  161. package/package.json +37 -37
  162. package/src/block/block.json +2 -1
  163. package/src/breadcrumbs/edit.js +10 -2
  164. package/src/breadcrumbs/index.js +1 -0
  165. package/src/categories/index.php +5 -1
  166. package/src/comment-date/block.json +1 -0
  167. package/src/comment-edit-link/block.json +1 -3
  168. package/src/comment-edit-link/deprecated.js +63 -0
  169. package/src/comment-edit-link/edit.js +7 -31
  170. package/src/comment-edit-link/index.js +2 -0
  171. package/src/comment-reply-link/block.json +1 -5
  172. package/src/comment-reply-link/deprecated.js +56 -0
  173. package/src/comment-reply-link/edit.js +6 -35
  174. package/src/comment-reply-link/index.js +2 -0
  175. package/src/cover/edit/inspector-controls.js +1 -3
  176. package/src/embed/test/index.js +49 -0
  177. package/src/embed/util.js +21 -0
  178. package/src/freeform/block.json +2 -1
  179. package/src/html/block.json +2 -1
  180. package/src/image/image.js +63 -11
  181. package/src/list-item/index.js +12 -0
  182. package/src/media-text/style.scss +2 -0
  183. package/src/missing/block.json +2 -1
  184. package/src/more/block.json +2 -1
  185. package/src/navigation/block.json +5 -4
  186. package/src/navigation/deprecated.js +144 -5
  187. package/src/navigation/edit/deleted-overlay-warning.js +56 -0
  188. package/src/navigation/edit/index.js +157 -70
  189. package/src/navigation/edit/overlay-panel.js +10 -0
  190. package/src/navigation/edit/overlay-preview.js +133 -0
  191. package/src/navigation/edit/overlay-template-part-selector.js +76 -26
  192. package/src/navigation/edit/responsive-wrapper.js +14 -1
  193. package/src/navigation/edit/test/overlay-template-part-selector.js +24 -16
  194. package/src/navigation/edit/test/responsive-wrapper.js +179 -0
  195. package/src/navigation/edit/test/use-create-overlay.js +129 -2
  196. package/src/navigation/edit/use-create-overlay.js +26 -4
  197. package/src/navigation/editor.scss +51 -0
  198. package/src/navigation/index.php +59 -11
  199. package/src/navigation/style.scss +140 -76
  200. package/src/navigation/utils/get-submenu-visibility.js +27 -0
  201. package/src/navigation/utils/test/get-submenu-visibility.js +47 -0
  202. package/src/navigation-link/edit.js +3 -67
  203. package/src/navigation-link/shared/index.js +2 -0
  204. package/src/navigation-link/shared/select-label-text.js +16 -0
  205. package/src/navigation-link/shared/use-is-dragging-within.js +55 -0
  206. package/src/navigation-submenu/block.json +1 -1
  207. package/src/navigation-submenu/edit.js +10 -73
  208. package/src/navigation-submenu/index.php +36 -5
  209. package/src/nextpage/block.json +2 -1
  210. package/src/paragraph/block.json +1 -0
  211. package/src/shortcode/block.json +2 -1
  212. package/src/template-part/edit/index.js +1 -1
  213. package/src/verse/block.json +1 -3
  214. package/src/verse/deprecated.js +83 -4
  215. package/src/verse/edit.js +37 -56
  216. package/src/verse/save.js +2 -11
  217. package/src/verse/style.scss +1 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Returns the submenu visibility value with backward compatibility
3
+ * for the deprecated openSubmenusOnClick attribute.
4
+ *
5
+ * This function centralizes the migration logic from the boolean
6
+ * openSubmenusOnClick to the new submenuVisibility enum.
7
+ *
8
+ * NOTE: Keep this function in sync with block_core_navigation_get_submenu_visibility
9
+ * in packages/block-library/src/navigation/index.php
10
+ *
11
+ * @param {Object} attributes Block attributes containing submenuVisibility and/or openSubmenusOnClick.
12
+ * @return {string} The visibility mode: 'hover', 'click', or 'always'.
13
+ */
14
+ export function getSubmenuVisibility( attributes ) {
15
+ const { submenuVisibility, openSubmenusOnClick } = attributes;
16
+
17
+ // If new attribute is set, use it
18
+ if ( submenuVisibility ) {
19
+ return submenuVisibility;
20
+ }
21
+
22
+ // Fall back to old attribute for backward compatibility
23
+ // openSubmenusOnClick: true -> 'click'
24
+ // openSubmenusOnClick: false -> 'hover'
25
+ // openSubmenusOnClick: undefined -> 'hover' (default)
26
+ return openSubmenusOnClick ? 'click' : 'hover';
27
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { getSubmenuVisibility } from '../get-submenu-visibility';
5
+
6
+ describe( 'getSubmenuVisibility', () => {
7
+ it( 'should return submenuVisibility when it is set', () => {
8
+ expect(
9
+ getSubmenuVisibility( {
10
+ submenuVisibility: 'click',
11
+ openSubmenusOnClick: false,
12
+ } )
13
+ ).toBe( 'click' );
14
+ expect(
15
+ getSubmenuVisibility( {
16
+ submenuVisibility: 'hover',
17
+ openSubmenusOnClick: true,
18
+ } )
19
+ ).toBe( 'hover' );
20
+ expect(
21
+ getSubmenuVisibility( {
22
+ submenuVisibility: 'always',
23
+ openSubmenusOnClick: false,
24
+ } )
25
+ ).toBe( 'always' );
26
+ } );
27
+
28
+ it( 'should fall back to "click" when submenuVisibility is undefined and openSubmenusOnClick is true', () => {
29
+ expect( getSubmenuVisibility( { openSubmenusOnClick: true } ) ).toBe(
30
+ 'click'
31
+ );
32
+ } );
33
+
34
+ it( 'should fall back to "hover" when submenuVisibility is undefined and openSubmenusOnClick is false', () => {
35
+ expect( getSubmenuVisibility( { openSubmenusOnClick: false } ) ).toBe(
36
+ 'hover'
37
+ );
38
+ } );
39
+
40
+ it( 'should fall back to "hover" when both submenuVisibility and openSubmenusOnClick are undefined', () => {
41
+ expect( getSubmenuVisibility( {} ) ).toBe( 'hover' );
42
+ } );
43
+
44
+ it( 'should handle empty attributes object', () => {
45
+ expect( getSubmenuVisibility( {} ) ).toBe( 'hover' );
46
+ } );
47
+ } );
@@ -42,6 +42,8 @@ import {
42
42
  useIsInvalidLink,
43
43
  InvalidDraftDisplay,
44
44
  useEnableLinkStatusValidation,
45
+ useIsDraggingWithin,
46
+ selectLabelText,
45
47
  } from './shared';
46
48
 
47
49
  const DEFAULT_BLOCK = { name: 'core/navigation-link' };
@@ -50,57 +52,6 @@ const NESTING_BLOCK_NAMES = [
50
52
  'core/navigation-submenu',
51
53
  ];
52
54
 
53
- /**
54
- * A React hook to determine if it's dragging within the target element.
55
- *
56
- * @typedef {import('@wordpress/element').RefObject} RefObject
57
- *
58
- * @param {RefObject<HTMLElement>} elementRef The target elementRef object.
59
- *
60
- * @return {boolean} Is dragging within the target element.
61
- */
62
- const useIsDraggingWithin = ( elementRef ) => {
63
- const [ isDraggingWithin, setIsDraggingWithin ] = useState( false );
64
-
65
- useEffect( () => {
66
- const { ownerDocument } = elementRef.current;
67
-
68
- function handleDragStart( event ) {
69
- // Check the first time when the dragging starts.
70
- handleDragEnter( event );
71
- }
72
-
73
- // Set to false whenever the user cancel the drag event by either releasing the mouse or press Escape.
74
- function handleDragEnd() {
75
- setIsDraggingWithin( false );
76
- }
77
-
78
- function handleDragEnter( event ) {
79
- // Check if the current target is inside the item element.
80
- if ( elementRef.current.contains( event.target ) ) {
81
- setIsDraggingWithin( true );
82
- } else {
83
- setIsDraggingWithin( false );
84
- }
85
- }
86
-
87
- // Bind these events to the document to catch all drag events.
88
- // Ideally, we can also use `event.relatedTarget`, but sadly that
89
- // doesn't work in Safari.
90
- ownerDocument.addEventListener( 'dragstart', handleDragStart );
91
- ownerDocument.addEventListener( 'dragend', handleDragEnd );
92
- ownerDocument.addEventListener( 'dragenter', handleDragEnter );
93
-
94
- return () => {
95
- ownerDocument.removeEventListener( 'dragstart', handleDragStart );
96
- ownerDocument.removeEventListener( 'dragend', handleDragEnd );
97
- ownerDocument.removeEventListener( 'dragenter', handleDragEnter );
98
- };
99
- }, [ elementRef ] );
100
-
101
- return isDraggingWithin;
102
- };
103
-
104
55
  function getMissingText( type ) {
105
56
  let missingText = '';
106
57
 
@@ -293,7 +244,7 @@ export default function NavigationLinkEdit( {
293
244
  // If the label looks like a URL, focus and select the label text.
294
245
  if ( isURL( prependHTTP( label ) ) && /^.+\.[a-z]+/.test( label ) ) {
295
246
  // Focus and select the label text.
296
- selectLabelText();
247
+ selectLabelText( ref );
297
248
  } else {
298
249
  // If the link was just created, we want to select the block so the inspector controls
299
250
  // are accurate.
@@ -317,21 +268,6 @@ export default function NavigationLinkEdit( {
317
268
  }
318
269
  }, [ url, isLinkOpen, isNewLink, label ] );
319
270
 
320
- /**
321
- * Focus the Link label text and select it.
322
- */
323
- function selectLabelText() {
324
- ref.current.focus();
325
- const { ownerDocument } = ref.current;
326
- const { defaultView } = ownerDocument;
327
- const selection = defaultView.getSelection();
328
- const range = ownerDocument.createRange();
329
- // Get the range of the current ref contents so we can add this range to the selection.
330
- range.selectNodeContents( ref.current );
331
- selection.removeAllRanges();
332
- selection.addRange( range );
333
- }
334
-
335
271
  /**
336
272
  * Removes the current link if set.
337
273
  */
@@ -16,3 +16,5 @@ export { useHandleLinkChange } from './use-handle-link-change';
16
16
  export { useIsInvalidLink } from './use-is-invalid-link';
17
17
  export { InvalidDraftDisplay } from './invalid-draft-display';
18
18
  export { useEnableLinkStatusValidation } from './use-enable-link-status-validation';
19
+ export { useIsDraggingWithin } from './use-is-dragging-within';
20
+ export { selectLabelText } from './select-label-text';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Focus the Link label text and select it.
3
+ *
4
+ * @param {Object} ref React ref object pointing to the label element.
5
+ */
6
+ export function selectLabelText( ref ) {
7
+ ref.current.focus();
8
+ const { ownerDocument } = ref.current;
9
+ const { defaultView } = ownerDocument;
10
+ const selection = defaultView.getSelection();
11
+ const range = ownerDocument.createRange();
12
+ // Get the range of the current ref contents so we can add this range to the selection.
13
+ range.selectNodeContents( ref.current );
14
+ selection.removeAllRanges();
15
+ selection.addRange( range );
16
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState, useEffect } from '@wordpress/element';
5
+
6
+ /**
7
+ * A React hook to determine if it's dragging within the target element.
8
+ *
9
+ * @typedef {import('@wordpress/element').RefObject} RefObject
10
+ *
11
+ * @param {RefObject<HTMLElement>} elementRef The target elementRef object.
12
+ *
13
+ * @return {boolean} Is dragging within the target element.
14
+ */
15
+ export const useIsDraggingWithin = ( elementRef ) => {
16
+ const [ isDraggingWithin, setIsDraggingWithin ] = useState( false );
17
+
18
+ useEffect( () => {
19
+ const { ownerDocument } = elementRef.current;
20
+
21
+ function handleDragStart( event ) {
22
+ // Check the first time when the dragging starts.
23
+ handleDragEnter( event );
24
+ }
25
+
26
+ // Set to false whenever the user cancel the drag event by either releasing the mouse or press Escape.
27
+ function handleDragEnd() {
28
+ setIsDraggingWithin( false );
29
+ }
30
+
31
+ function handleDragEnter( event ) {
32
+ // Check if the current target is inside the item element.
33
+ if ( elementRef.current.contains( event.target ) ) {
34
+ setIsDraggingWithin( true );
35
+ } else {
36
+ setIsDraggingWithin( false );
37
+ }
38
+ }
39
+
40
+ // Bind these events to the document to catch all drag events.
41
+ // Ideally, we can also use `event.relatedTarget`, but sadly that
42
+ // doesn't work in Safari.
43
+ ownerDocument.addEventListener( 'dragstart', handleDragStart );
44
+ ownerDocument.addEventListener( 'dragend', handleDragEnd );
45
+ ownerDocument.addEventListener( 'dragenter', handleDragEnter );
46
+
47
+ return () => {
48
+ ownerDocument.removeEventListener( 'dragstart', handleDragStart );
49
+ ownerDocument.removeEventListener( 'dragend', handleDragEnd );
50
+ ownerDocument.removeEventListener( 'dragenter', handleDragEnter );
51
+ };
52
+ }, [ elementRef ] );
53
+
54
+ return isDraggingWithin;
55
+ };
@@ -55,7 +55,7 @@
55
55
  "customFontSize",
56
56
  "showSubmenuIcon",
57
57
  "maxNestingLevel",
58
- "openSubmenusOnClick",
58
+ "submenuVisibility",
59
59
  "style"
60
60
  ],
61
61
  "supports": {
@@ -40,12 +40,15 @@ import {
40
40
  useIsInvalidLink,
41
41
  InvalidDraftDisplay,
42
42
  useEnableLinkStatusValidation,
43
+ useIsDraggingWithin,
44
+ selectLabelText,
43
45
  } from '../navigation-link/shared';
44
46
  import {
45
47
  getColors,
46
48
  getNavigationChildBlockProps,
47
49
  } from '../navigation/edit/utils';
48
50
  import { DEFAULT_BLOCK } from '../navigation/constants';
51
+ import { getSubmenuVisibility } from '../navigation/utils/get-submenu-visibility';
49
52
 
50
53
  const ALLOWED_BLOCKS = [
51
54
  'core/navigation-link',
@@ -53,57 +56,6 @@ const ALLOWED_BLOCKS = [
53
56
  'core/page-list',
54
57
  ];
55
58
 
56
- /**
57
- * A React hook to determine if it's dragging within the target element.
58
- *
59
- * @typedef {import('@wordpress/element').RefObject} RefObject
60
- *
61
- * @param {RefObject<HTMLElement>} elementRef The target elementRef object.
62
- *
63
- * @return {boolean} Is dragging within the target element.
64
- */
65
- const useIsDraggingWithin = ( elementRef ) => {
66
- const [ isDraggingWithin, setIsDraggingWithin ] = useState( false );
67
-
68
- useEffect( () => {
69
- const { ownerDocument } = elementRef.current;
70
-
71
- function handleDragStart( event ) {
72
- // Check the first time when the dragging starts.
73
- handleDragEnter( event );
74
- }
75
-
76
- // Set to false whenever the user cancel the drag event by either releasing the mouse or press Escape.
77
- function handleDragEnd() {
78
- setIsDraggingWithin( false );
79
- }
80
-
81
- function handleDragEnter( event ) {
82
- // Check if the current target is inside the item element.
83
- if ( elementRef.current.contains( event.target ) ) {
84
- setIsDraggingWithin( true );
85
- } else {
86
- setIsDraggingWithin( false );
87
- }
88
- }
89
-
90
- // Bind these events to the document to catch all drag events.
91
- // Ideally, we can also use `event.relatedTarget`, but sadly that
92
- // doesn't work in Safari.
93
- ownerDocument.addEventListener( 'dragstart', handleDragStart );
94
- ownerDocument.addEventListener( 'dragend', handleDragEnd );
95
- ownerDocument.addEventListener( 'dragenter', handleDragEnter );
96
-
97
- return () => {
98
- ownerDocument.removeEventListener( 'dragstart', handleDragStart );
99
- ownerDocument.removeEventListener( 'dragend', handleDragEnd );
100
- ownerDocument.removeEventListener( 'dragenter', handleDragEnter );
101
- };
102
- }, [] );
103
-
104
- return isDraggingWithin;
105
- };
106
-
107
59
  /**
108
60
  * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind
109
61
  */
@@ -133,16 +85,15 @@ export default function NavigationSubmenuEdit( {
133
85
  } ) {
134
86
  const { label, url, description, kind, type, id } = attributes;
135
87
 
136
- const {
137
- showSubmenuIcon,
138
- maxNestingLevel,
139
- openSubmenusOnClick: contextOpenSubmenusOnClick,
140
- } = context;
88
+ const { showSubmenuIcon, maxNestingLevel } = context;
141
89
  const blockEditingMode = useBlockEditingMode();
142
90
 
91
+ // Determine effective submenu visibility with backward compatibility
92
+ const submenuVisibility = getSubmenuVisibility( context );
93
+
143
94
  // Force click-only behavior in contentOnly mode to prevent hover dropdowns
144
95
  const openSubmenusOnClick =
145
- blockEditingMode !== 'default' ? true : contextOpenSubmenusOnClick;
96
+ blockEditingMode !== 'default' ? true : submenuVisibility === 'click';
146
97
 
147
98
  // URL binding logic
148
99
  const { clearBinding, createBinding } = useEntityBinding( {
@@ -258,26 +209,11 @@ export default function NavigationSubmenuEdit( {
258
209
  /^.+\.[a-z]+/.test( label )
259
210
  ) {
260
211
  // Focus and select the label text.
261
- selectLabelText();
212
+ selectLabelText( ref );
262
213
  }
263
214
  }
264
215
  }, [ url ] );
265
216
 
266
- /**
267
- * Focus the Link label text and select it.
268
- */
269
- function selectLabelText() {
270
- ref.current.focus();
271
- const { ownerDocument } = ref.current;
272
- const { defaultView } = ownerDocument;
273
- const selection = defaultView.getSelection();
274
- const range = ownerDocument.createRange();
275
- // Get the range of the current ref contents so we can add this range to the selection.
276
- range.selectNodeContents( ref.current );
277
- selection.removeAllRanges();
278
- selection.addRange( range );
279
- }
280
-
281
217
  const {
282
218
  textColor,
283
219
  customTextColor,
@@ -310,6 +246,7 @@ export default function NavigationSubmenuEdit( {
310
246
  [ getColorClassName( 'background-color', backgroundColor ) ]:
311
247
  !! backgroundColor,
312
248
  'open-on-click': openSubmenusOnClick,
249
+ 'open-always': submenuVisibility === 'always',
313
250
  } ),
314
251
  style: {
315
252
  color: ! textColor && customTextColor,
@@ -5,6 +5,34 @@
5
5
  * @package WordPress
6
6
  */
7
7
 
8
+ /**
9
+ * Returns the submenu visibility value with backward compatibility
10
+ * for the deprecated openSubmenusOnClick attribute.
11
+ *
12
+ * This function centralizes the migration logic from the boolean
13
+ * openSubmenusOnClick to the new submenuVisibility enum.
14
+ *
15
+ * @since 6.9.0
16
+ *
17
+ * @param array $attributes Block attributes containing submenuVisibility and/or openSubmenusOnClick.
18
+ * @return string The visibility mode: 'hover', 'click', or 'always'.
19
+ */
20
+ function block_core_navigation_submenu_get_submenu_visibility( $attributes ) {
21
+ $submenu_visibility = isset( $attributes['submenuVisibility'] ) ? $attributes['submenuVisibility'] : null;
22
+ $open_submenus_on_click = isset( $attributes['openSubmenusOnClick'] ) ? $attributes['openSubmenusOnClick'] : null;
23
+
24
+ // If new attribute is set, use it.
25
+ if ( null !== $submenu_visibility ) {
26
+ return $submenu_visibility;
27
+ }
28
+
29
+ // Fall back to old attribute for backward compatibility.
30
+ // openSubmenusOnClick: true -> 'click'
31
+ // openSubmenusOnClick: false -> 'hover'
32
+ // openSubmenusOnClick: null -> 'hover' (default)
33
+ return ! empty( $open_submenus_on_click ) ? 'click' : 'hover';
34
+ }
35
+
8
36
  // Path differs between source and build: '../navigation-link/shared/helpers.php' in source, './navigation-link/shared/helpers.php' in build.
9
37
  if ( file_exists( __DIR__ . '/../navigation-link/shared/helpers.php' ) ) {
10
38
  require_once __DIR__ . '/../navigation-link/shared/helpers.php';
@@ -105,9 +133,10 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
105
133
  }
106
134
 
107
135
  $show_submenu_indicators = isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon'];
108
- $open_on_click = isset( $block->context['openSubmenusOnClick'] ) && $block->context['openSubmenusOnClick'];
109
- $open_on_hover_and_click = isset( $block->context['openSubmenusOnClick'] ) && ! $block->context['openSubmenusOnClick'] &&
110
- $show_submenu_indicators;
136
+ $computed_visibility = block_core_navigation_submenu_get_submenu_visibility( $block->context );
137
+ $open_on_click = 'click' === $computed_visibility;
138
+ $open_on_hover = 'hover' === $computed_visibility;
139
+ $open_on_hover_and_click = $open_on_hover && $show_submenu_indicators;
111
140
 
112
141
  $classes = array(
113
142
  'wp-block-navigation-item',
@@ -125,6 +154,9 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
125
154
  if ( $open_on_hover_and_click ) {
126
155
  $classes[] = 'open-on-hover-click';
127
156
  }
157
+ if ( 'always' === $computed_visibility ) {
158
+ $classes[] = 'open-always';
159
+ }
128
160
  if ( $is_active ) {
129
161
  $classes[] = 'current-menu-item';
130
162
  }
@@ -150,7 +182,7 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
150
182
 
151
183
  $html = '<li ' . $wrapper_attributes . '>';
152
184
 
153
- // If Submenus open on hover, we render an anchor tag with attributes.
185
+ // If Submenus open on hover or are always open, we render an anchor tag with attributes.
154
186
  // If submenu icons are set to show, we also render a submenu button, so the submenu can be opened on click.
155
187
  if ( ! $open_on_click ) {
156
188
  $item_url = $attributes['url'] ?? '';
@@ -207,7 +239,6 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
207
239
  $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>';
208
240
  }
209
241
  } else {
210
- // If menus open on click, we render the parent as a button.
211
242
  $html .= '<button aria-label="' . esc_attr( $aria_label ) . '" class="wp-block-navigation-item__content wp-block-navigation-submenu__toggle" aria-expanded="false">';
212
243
 
213
244
  // Wrap title with span to isolate it from submenu icon.
@@ -16,7 +16,8 @@
16
16
  "visibility": false,
17
17
  "interactivity": {
18
18
  "clientNavigation": true
19
- }
19
+ },
20
+ "customCSS": false
20
21
  },
21
22
  "editorStyle": "wp-block-nextpage-editor"
22
23
  }
@@ -57,6 +57,7 @@
57
57
  "fontSize": true,
58
58
  "lineHeight": true,
59
59
  "textAlign": true,
60
+ "textColumns": true,
60
61
  "__experimentalFontFamily": true,
61
62
  "__experimentalTextDecoration": true,
62
63
  "__experimentalFontStyle": true,
@@ -16,7 +16,8 @@
16
16
  "supports": {
17
17
  "className": false,
18
18
  "customClassName": false,
19
- "html": false
19
+ "html": false,
20
+ "customCSS": false
20
21
  },
21
22
  "editorStyle": "wp-block-shortcode-editor"
22
23
  }
@@ -324,7 +324,7 @@ export default function TemplatePartEdit( {
324
324
  } }
325
325
  </BlockSettingsMenuControls>
326
326
 
327
- <InspectorControls>
327
+ <InspectorControls group="settings">
328
328
  <TemplatesList
329
329
  area={ area }
330
330
  clientId={ clientId }
@@ -14,9 +14,6 @@
14
14
  "selector": "pre",
15
15
  "__unstablePreserveWhiteSpace": true,
16
16
  "role": "content"
17
- },
18
- "textAlign": {
19
- "type": "string"
20
17
  }
21
18
  },
22
19
  "supports": {
@@ -46,6 +43,7 @@
46
43
  "fontSize": true,
47
44
  "__experimentalFontFamily": true,
48
45
  "lineHeight": true,
46
+ "textAlign": true,
49
47
  "__experimentalFontStyle": true,
50
48
  "__experimentalFontWeight": true,
51
49
  "__experimentalLetterSpacing": true,
@@ -12,6 +12,7 @@ import { RichText, useBlockProps } from '@wordpress/block-editor';
12
12
  * Internal dependencies
13
13
  */
14
14
  import migrateFontFamily from '../utils/migrate-font-family';
15
+ import migrateTextAlign from '../utils/migrate-text-align';
15
16
 
16
17
  const v1 = {
17
18
  attributes: {
@@ -36,6 +37,7 @@ const v1 = {
36
37
  />
37
38
  );
38
39
  },
40
+ migrate: migrateTextAlign,
39
41
  };
40
42
 
41
43
  const v2 = {
@@ -79,9 +81,86 @@ const v2 = {
79
81
  </pre>
80
82
  );
81
83
  },
82
- migrate: migrateFontFamily,
83
- isEligible( { style } ) {
84
- return style?.typography?.fontFamily;
84
+ migrate( attributes ) {
85
+ return migrateTextAlign( migrateFontFamily( attributes ) );
86
+ },
87
+ isEligible( { style, textAlign } ) {
88
+ return style?.typography?.fontFamily || !! textAlign;
89
+ },
90
+ };
91
+
92
+ const v3 = {
93
+ attributes: {
94
+ content: {
95
+ type: 'rich-text',
96
+ source: 'rich-text',
97
+ selector: 'pre',
98
+ __unstablePreserveWhiteSpace: true,
99
+ role: 'content',
100
+ },
101
+ textAlign: {
102
+ type: 'string',
103
+ },
104
+ },
105
+ supports: {
106
+ anchor: true,
107
+ background: {
108
+ backgroundImage: true,
109
+ backgroundSize: true,
110
+ },
111
+ color: {
112
+ gradients: true,
113
+ link: true,
114
+ },
115
+ dimensions: {
116
+ minHeight: true,
117
+ },
118
+ typography: {
119
+ fontSize: true,
120
+ __experimentalFontFamily: true,
121
+ lineHeight: true,
122
+ __experimentalFontStyle: true,
123
+ __experimentalFontWeight: true,
124
+ __experimentalLetterSpacing: true,
125
+ __experimentalTextTransform: true,
126
+ __experimentalTextDecoration: true,
127
+ __experimentalWritingMode: true,
128
+ },
129
+ spacing: {
130
+ margin: true,
131
+ padding: true,
132
+ },
133
+ __experimentalBorder: {
134
+ radius: true,
135
+ width: true,
136
+ color: true,
137
+ style: true,
138
+ },
139
+ interactivity: {
140
+ clientNavigation: true,
141
+ },
142
+ },
143
+ save( { attributes } ) {
144
+ const { textAlign, content } = attributes;
145
+
146
+ const className = clsx( {
147
+ [ `has-text-align-${ textAlign }` ]: textAlign,
148
+ } );
149
+
150
+ return (
151
+ <pre { ...useBlockProps.save( { className } ) }>
152
+ <RichText.Content value={ content } />
153
+ </pre>
154
+ );
155
+ },
156
+ migrate: migrateTextAlign,
157
+ isEligible( attributes ) {
158
+ return (
159
+ !! attributes.textAlign ||
160
+ !! attributes.className?.match(
161
+ /\bhas-text-align-(left|center|right)\b/
162
+ )
163
+ );
85
164
  },
86
165
  };
87
166
 
@@ -93,4 +172,4 @@ const v2 = {
93
172
  *
94
173
  * See block-deprecation.md
95
174
  */
96
- export default [ v2, v1 ];
175
+ export default [ v3, v2, v1 ];