@wordpress/block-library 9.31.1-next.f56bd8138.0 → 9.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/accordion/edit.js +62 -12
  3. package/build/accordion/edit.js.map +1 -1
  4. package/build/accordion/index.js +15 -8
  5. package/build/accordion/index.js.map +1 -1
  6. package/build/accordion/view.js +15 -15
  7. package/build/accordion/view.js.map +1 -1
  8. package/build/accordion-heading/edit.js +68 -0
  9. package/build/accordion-heading/edit.js.map +1 -0
  10. package/build/accordion-heading/icon.js.map +1 -0
  11. package/build/{accordion-header → accordion-heading}/index.js +9 -17
  12. package/build/accordion-heading/index.js.map +1 -0
  13. package/build/{accordion-content → accordion-heading}/init.js.map +1 -1
  14. package/build/{accordion-header → accordion-heading}/save.js +7 -20
  15. package/build/accordion-heading/save.js.map +1 -0
  16. package/build/{accordion-content → accordion-item}/edit.js +8 -2
  17. package/build/accordion-item/edit.js.map +1 -0
  18. package/build/accordion-item/icon.js.map +1 -0
  19. package/build/{accordion-content → accordion-item}/index.js +6 -5
  20. package/build/accordion-item/index.js.map +1 -0
  21. package/build/accordion-item/init.js.map +1 -0
  22. package/build/{accordion-content → accordion-item}/save.js +5 -7
  23. package/build/accordion-item/save.js.map +1 -0
  24. package/build/accordion-panel/edit.js +2 -1
  25. package/build/accordion-panel/edit.js.map +1 -1
  26. package/build/accordion-panel/index.js +5 -3
  27. package/build/accordion-panel/index.js.map +1 -1
  28. package/build/accordion-panel/save.js +3 -1
  29. package/build/accordion-panel/save.js.map +1 -1
  30. package/build/block/index.js +1 -0
  31. package/build/block/index.js.map +1 -1
  32. package/build/group/variations.js +3 -4
  33. package/build/group/variations.js.map +1 -1
  34. package/build/index.js +24 -4
  35. package/build/index.js.map +1 -1
  36. package/build/navigation/edit/leaf-more-menu.js +6 -1
  37. package/build/navigation/edit/leaf-more-menu.js.map +1 -1
  38. package/build/navigation-link/edit.js +3 -140
  39. package/build/navigation-link/edit.js.map +1 -1
  40. package/build/navigation-link/shared/controls.js +171 -0
  41. package/build/navigation-link/shared/controls.js.map +1 -0
  42. package/build/navigation-link/shared/index.js +13 -0
  43. package/build/navigation-link/shared/index.js.map +1 -0
  44. package/build/navigation-submenu/edit.js +7 -114
  45. package/build/navigation-submenu/edit.js.map +1 -1
  46. package/build/pattern/index.js +1 -0
  47. package/build/pattern/index.js.map +1 -1
  48. package/build/post-date/edit.js +16 -3
  49. package/build/post-date/edit.js.map +1 -1
  50. package/build/post-time-to-read/edit.js +27 -15
  51. package/build/post-time-to-read/edit.js.map +1 -1
  52. package/build/post-time-to-read/index.js +7 -1
  53. package/build/post-time-to-read/index.js.map +1 -1
  54. package/build/post-time-to-read/variations.js +41 -0
  55. package/build/post-time-to-read/variations.js.map +1 -0
  56. package/build/query-title/edit.js +1 -1
  57. package/build/query-title/edit.js.map +1 -1
  58. package/build/table-of-contents/index.js +1 -0
  59. package/build/table-of-contents/index.js.map +1 -1
  60. package/build/template-part/index.js +1 -0
  61. package/build/template-part/index.js.map +1 -1
  62. package/build/utils/get-transformed-metadata.js +7 -0
  63. package/build/utils/get-transformed-metadata.js.map +1 -1
  64. package/build-module/accordion/edit.js +66 -16
  65. package/build-module/accordion/edit.js.map +1 -1
  66. package/build-module/accordion/index.js +15 -8
  67. package/build-module/accordion/index.js.map +1 -1
  68. package/build-module/accordion/view.js +15 -15
  69. package/build-module/accordion/view.js.map +1 -1
  70. package/build-module/accordion-heading/edit.js +61 -0
  71. package/build-module/accordion-heading/edit.js.map +1 -0
  72. package/build-module/accordion-heading/icon.js.map +1 -0
  73. package/build-module/{accordion-header → accordion-heading}/index.js +9 -17
  74. package/build-module/accordion-heading/index.js.map +1 -0
  75. package/build-module/{accordion-content → accordion-heading}/init.js.map +1 -1
  76. package/build-module/{accordion-header → accordion-heading}/save.js +7 -18
  77. package/build-module/accordion-heading/save.js.map +1 -0
  78. package/build-module/{accordion-content → accordion-item}/edit.js +8 -2
  79. package/build-module/accordion-item/edit.js.map +1 -0
  80. package/build-module/accordion-item/icon.js.map +1 -0
  81. package/build-module/{accordion-content → accordion-item}/index.js +6 -5
  82. package/build-module/accordion-item/index.js.map +1 -0
  83. package/build-module/accordion-item/init.js.map +1 -0
  84. package/build-module/{accordion-content → accordion-item}/save.js +5 -7
  85. package/build-module/accordion-item/save.js.map +1 -0
  86. package/build-module/accordion-panel/edit.js +2 -1
  87. package/build-module/accordion-panel/edit.js.map +1 -1
  88. package/build-module/accordion-panel/index.js +5 -3
  89. package/build-module/accordion-panel/index.js.map +1 -1
  90. package/build-module/accordion-panel/save.js +3 -1
  91. package/build-module/accordion-panel/save.js.map +1 -1
  92. package/build-module/block/index.js +1 -0
  93. package/build-module/block/index.js.map +1 -1
  94. package/build-module/group/variations.js +3 -4
  95. package/build-module/group/variations.js.map +1 -1
  96. package/build-module/index.js +25 -5
  97. package/build-module/index.js.map +1 -1
  98. package/build-module/navigation/edit/leaf-more-menu.js +6 -1
  99. package/build-module/navigation/edit/leaf-more-menu.js.map +1 -1
  100. package/build-module/navigation-link/edit.js +4 -141
  101. package/build-module/navigation-link/edit.js.map +1 -1
  102. package/build-module/navigation-link/shared/controls.js +165 -0
  103. package/build-module/navigation-link/shared/controls.js.map +1 -0
  104. package/build-module/navigation-link/shared/index.js +9 -0
  105. package/build-module/navigation-link/shared/index.js.map +1 -0
  106. package/build-module/navigation-submenu/edit.js +7 -114
  107. package/build-module/navigation-submenu/edit.js.map +1 -1
  108. package/build-module/pattern/index.js +1 -0
  109. package/build-module/pattern/index.js.map +1 -1
  110. package/build-module/post-date/edit.js +17 -4
  111. package/build-module/post-date/edit.js.map +1 -1
  112. package/build-module/post-time-to-read/edit.js +27 -15
  113. package/build-module/post-time-to-read/edit.js.map +1 -1
  114. package/build-module/post-time-to-read/index.js +7 -1
  115. package/build-module/post-time-to-read/index.js.map +1 -1
  116. package/build-module/post-time-to-read/variations.js +33 -0
  117. package/build-module/post-time-to-read/variations.js.map +1 -0
  118. package/build-module/query-title/edit.js +1 -1
  119. package/build-module/query-title/edit.js.map +1 -1
  120. package/build-module/table-of-contents/index.js +1 -0
  121. package/build-module/table-of-contents/index.js.map +1 -1
  122. package/build-module/template-part/index.js +1 -0
  123. package/build-module/template-part/index.js.map +1 -1
  124. package/build-module/utils/get-transformed-metadata.js +7 -0
  125. package/build-module/utils/get-transformed-metadata.js.map +1 -1
  126. package/build-style/{accordion → accordion-heading}/style-rtl.css +9 -54
  127. package/build-style/{accordion → accordion-heading}/style.css +9 -54
  128. package/build-style/accordion-item/style-rtl.css +155 -0
  129. package/build-style/accordion-item/style.css +155 -0
  130. package/build-style/accordion-panel/style-rtl.css +140 -0
  131. package/build-style/accordion-panel/style.css +140 -0
  132. package/build-style/style-rtl.css +23 -42
  133. package/build-style/style.css +23 -42
  134. package/package.json +35 -35
  135. package/src/accordion/block.json +11 -4
  136. package/src/accordion/edit.js +70 -13
  137. package/src/accordion/index.js +4 -4
  138. package/src/accordion/index.php +1 -1
  139. package/src/accordion/view.js +15 -15
  140. package/src/{accordion-header → accordion-heading}/block.json +10 -17
  141. package/src/accordion-heading/edit.js +70 -0
  142. package/src/{accordion-header → accordion-heading}/save.js +8 -18
  143. package/src/accordion-heading/style.scss +43 -0
  144. package/src/{accordion-content → accordion-item}/block.json +6 -5
  145. package/src/{accordion-content → accordion-item}/edit.js +12 -2
  146. package/src/{accordion-content → accordion-item}/index.php +11 -11
  147. package/src/{accordion-content → accordion-item}/save.js +4 -10
  148. package/src/accordion-item/style.scss +21 -0
  149. package/src/accordion-panel/block.json +5 -3
  150. package/src/accordion-panel/edit.js +1 -0
  151. package/src/accordion-panel/save.js +3 -1
  152. package/src/accordion-panel/style.scss +8 -0
  153. package/src/block/block.json +1 -0
  154. package/src/comments/index.php +2 -2
  155. package/src/group/variations.js +3 -14
  156. package/src/index.js +23 -4
  157. package/src/navigation/edit/leaf-more-menu.js +9 -1
  158. package/src/navigation/index.php +2 -2
  159. package/src/navigation-link/edit.js +3 -142
  160. package/src/navigation-link/shared/README.md +47 -0
  161. package/src/navigation-link/shared/controls.js +167 -0
  162. package/src/navigation-link/shared/index.js +8 -0
  163. package/src/navigation-link/shared/test/controls.js +210 -0
  164. package/src/navigation-submenu/edit.js +8 -129
  165. package/src/pattern/block.json +1 -0
  166. package/src/post-date/edit.js +16 -16
  167. package/src/post-time-to-read/block.json +5 -1
  168. package/src/post-time-to-read/edit.js +87 -59
  169. package/src/post-time-to-read/index.js +2 -0
  170. package/src/post-time-to-read/index.php +48 -23
  171. package/src/post-time-to-read/variations.js +39 -0
  172. package/src/query-title/edit.js +2 -1
  173. package/src/query-title/index.php +3 -1
  174. package/src/social-link/index.php +2 -2
  175. package/src/style.scss +3 -1
  176. package/src/table-of-contents/block.json +1 -0
  177. package/src/table-of-contents/index.php +44 -0
  178. package/src/template-part/block.json +1 -0
  179. package/src/utils/get-transformed-metadata.js +8 -0
  180. package/build/accordion-content/edit.js.map +0 -1
  181. package/build/accordion-content/icon.js.map +0 -1
  182. package/build/accordion-content/index.js.map +0 -1
  183. package/build/accordion-content/save.js.map +0 -1
  184. package/build/accordion-header/edit.js +0 -94
  185. package/build/accordion-header/edit.js.map +0 -1
  186. package/build/accordion-header/icon.js.map +0 -1
  187. package/build/accordion-header/index.js.map +0 -1
  188. package/build/accordion-header/init.js.map +0 -1
  189. package/build/accordion-header/save.js.map +0 -1
  190. package/build-module/accordion-content/edit.js.map +0 -1
  191. package/build-module/accordion-content/icon.js.map +0 -1
  192. package/build-module/accordion-content/index.js.map +0 -1
  193. package/build-module/accordion-content/save.js.map +0 -1
  194. package/build-module/accordion-header/edit.js +0 -85
  195. package/build-module/accordion-header/edit.js.map +0 -1
  196. package/build-module/accordion-header/icon.js.map +0 -1
  197. package/build-module/accordion-header/index.js.map +0 -1
  198. package/build-module/accordion-header/init.js.map +0 -1
  199. package/build-module/accordion-header/save.js.map +0 -1
  200. package/src/accordion/style.scss +0 -90
  201. package/src/accordion-header/edit.js +0 -94
  202. /package/build/{accordion-header → accordion-heading}/icon.js +0 -0
  203. /package/build/{accordion-content → accordion-heading}/init.js +0 -0
  204. /package/build/{accordion-content → accordion-item}/icon.js +0 -0
  205. /package/build/{accordion-header → accordion-item}/init.js +0 -0
  206. /package/build-module/{accordion-header → accordion-heading}/icon.js +0 -0
  207. /package/build-module/{accordion-content → accordion-heading}/init.js +0 -0
  208. /package/build-module/{accordion-content → accordion-item}/icon.js +0 -0
  209. /package/build-module/{accordion-header → accordion-item}/init.js +0 -0
  210. /package/src/{accordion-header → accordion-heading}/icon.js +0 -0
  211. /package/src/{accordion-content → accordion-heading}/index.js +0 -0
  212. /package/src/{accordion-content → accordion-heading}/init.js +0 -0
  213. /package/src/{accordion-content → accordion-item}/icon.js +0 -0
  214. /package/src/{accordion-header → accordion-item}/index.js +0 -0
  215. /package/src/{accordion-header → accordion-item}/init.js +0 -0
@@ -9,6 +9,7 @@ export default function Edit( { attributes } ) {
9
9
 
10
10
  const blockProps = useBlockProps( {
11
11
  'aria-hidden': ! isSelected && ! openByDefault,
12
+ role: 'region',
12
13
  } );
13
14
 
14
15
  const innerBlocksProps = useInnerBlocksProps( blockProps, {
@@ -4,7 +4,9 @@
4
4
  import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
5
5
 
6
6
  export default function save() {
7
- const blockProps = useBlockProps.save();
7
+ const blockProps = useBlockProps.save( {
8
+ role: 'region',
9
+ } );
8
10
  const innerBlocksProps = useInnerBlocksProps.save( blockProps );
9
11
  return <div { ...innerBlocksProps } />;
10
12
  }
@@ -0,0 +1,8 @@
1
+ .wp-block-accordion-panel {
2
+ // Prevent blockGap from Accordion Content block from adding extra margin between accordions.
3
+ &[inert],
4
+ &[aria-hidden="true"] {
5
+ display: none;
6
+ margin-block-start: 0;
7
+ }
8
+ }
@@ -24,6 +24,7 @@
24
24
  "html": false,
25
25
  "inserter": false,
26
26
  "renaming": false,
27
+ "blockVisibility": false,
27
28
  "interactivity": {
28
29
  "clientNavigation": true
29
30
  }
@@ -28,11 +28,11 @@
28
28
  function render_block_core_comments( $attributes, $content, $block ) {
29
29
  global $post;
30
30
 
31
- $post_id = $block->context['postId'];
32
- if ( ! isset( $post_id ) ) {
31
+ if ( ! isset( $block->context['postId'] ) ) {
33
32
  return '';
34
33
  }
35
34
 
35
+ $post_id = $block->context['postId'];
36
36
  // Return early if there are no comments and comments are closed.
37
37
  if ( ! comments_open( $post_id ) && (int) get_comments_number( $post_id ) === 0 ) {
38
38
  return '';
@@ -53,11 +53,6 @@ const variations = [
53
53
  attributes: { layout: { type: 'constrained' } },
54
54
  isDefault: true,
55
55
  scope: [ 'block', 'inserter', 'transform' ],
56
- isActive: ( blockAttributes ) =>
57
- ! blockAttributes.layout ||
58
- ! blockAttributes.layout?.type ||
59
- blockAttributes.layout?.type === 'default' ||
60
- blockAttributes.layout?.type === 'constrained',
61
56
  icon: group,
62
57
  },
63
58
  {
@@ -66,10 +61,7 @@ const variations = [
66
61
  description: __( 'Arrange blocks horizontally.' ),
67
62
  attributes: { layout: { type: 'flex', flexWrap: 'nowrap' } },
68
63
  scope: [ 'block', 'inserter', 'transform' ],
69
- isActive: ( blockAttributes ) =>
70
- blockAttributes.layout?.type === 'flex' &&
71
- ( ! blockAttributes.layout?.orientation ||
72
- blockAttributes.layout?.orientation === 'horizontal' ),
64
+ isActive: [ 'layout.type' ],
73
65
  icon: row,
74
66
  example,
75
67
  },
@@ -79,9 +71,7 @@ const variations = [
79
71
  description: __( 'Arrange blocks vertically.' ),
80
72
  attributes: { layout: { type: 'flex', orientation: 'vertical' } },
81
73
  scope: [ 'block', 'inserter', 'transform' ],
82
- isActive: ( blockAttributes ) =>
83
- blockAttributes.layout?.type === 'flex' &&
84
- blockAttributes.layout?.orientation === 'vertical',
74
+ isActive: [ 'layout.type', 'layout.orientation' ],
85
75
  icon: stack,
86
76
  example,
87
77
  },
@@ -91,8 +81,7 @@ const variations = [
91
81
  description: __( 'Arrange blocks in a grid.' ),
92
82
  attributes: { layout: { type: 'grid' } },
93
83
  scope: [ 'block', 'inserter', 'transform' ],
94
- isActive: ( blockAttributes ) =>
95
- blockAttributes.layout?.type === 'grid',
84
+ isActive: [ 'layout.type' ],
96
85
  icon: grid,
97
86
  example,
98
87
  },
package/src/index.js CHANGED
@@ -6,7 +6,10 @@ import {
6
6
  setFreeformContentHandlerName,
7
7
  setUnregisteredTypeHandlerName,
8
8
  setGroupingBlockName,
9
+ registerBlockType,
9
10
  } from '@wordpress/blocks';
11
+ import { createElement } from '@wordpress/element';
12
+ import ServerSideRender from '@wordpress/server-side-render';
10
13
 
11
14
  /**
12
15
  * Internal dependencies
@@ -21,8 +24,8 @@ import {
21
24
  //
22
25
  // See https://github.com/WordPress/gutenberg/pull/40655 for more context.
23
26
  import * as accordion from './accordion';
24
- import * as accordionContent from './accordion-content';
25
- import * as accordionHeader from './accordion-header';
27
+ import * as accordionItem from './accordion-item';
28
+ import * as accordionHeading from './accordion-heading';
26
29
  import * as accordionPanel from './accordion-panel';
27
30
  import * as archives from './archives';
28
31
  import * as avatar from './avatar';
@@ -243,8 +246,8 @@ const getAllBlocks = () => {
243
246
 
244
247
  if ( window?.__experimentalEnableBlockExperiments ) {
245
248
  blocks.push( accordion );
246
- blocks.push( accordionContent );
247
- blocks.push( accordionHeader );
249
+ blocks.push( accordionItem );
250
+ blocks.push( accordionHeading );
248
251
  blocks.push( accordionPanel );
249
252
  blocks.push( termsQuery );
250
253
  blocks.push( termTemplate );
@@ -309,6 +312,22 @@ export const registerCoreBlocks = (
309
312
  ) => {
310
313
  blocks.forEach( ( { init } ) => init() );
311
314
 
315
+ // Auto-register PHP-only blocks with ServerSideRender
316
+ if ( window.__unstableAutoRegisterBlocks ) {
317
+ window.__unstableAutoRegisterBlocks.forEach( ( blockName ) => {
318
+ registerBlockType( blockName, {
319
+ title: blockName,
320
+ edit: ( { attributes } ) => {
321
+ return createElement( ServerSideRender, {
322
+ block: blockName,
323
+ attributes,
324
+ } );
325
+ },
326
+ save: () => null,
327
+ } );
328
+ } );
329
+ }
330
+
312
331
  setDefaultBlockName( paragraph.name );
313
332
  if (
314
333
  window.wp &&
@@ -13,6 +13,11 @@ import { useDispatch, useSelect } from '@wordpress/data';
13
13
  import { __, sprintf } from '@wordpress/i18n';
14
14
  import { BlockTitle, store as blockEditorStore } from '@wordpress/block-editor';
15
15
 
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+ import { DEFAULT_BLOCK } from '../constants';
20
+
16
21
  const POPOVER_PROPS = {
17
22
  className: 'block-editor-block-settings-menu__popover',
18
23
  placement: 'bottom-start',
@@ -43,7 +48,10 @@ function AddSubmenuItem( {
43
48
  disabled={ isDisabled }
44
49
  onClick={ () => {
45
50
  const updateSelectionOnInsert = false;
46
- const newLink = createBlock( 'core/navigation-link' );
51
+ const newLink = createBlock(
52
+ DEFAULT_BLOCK.name,
53
+ DEFAULT_BLOCK.attributes
54
+ );
47
55
 
48
56
  if ( block.name === 'core/navigation-submenu' ) {
49
57
  insertBlock(
@@ -478,10 +478,10 @@ class WP_Navigation_Block_Renderer {
478
478
  );
479
479
 
480
480
  $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
481
- $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="7.5" width="16" height="1.5" /><rect x="4" y="15" width="16" height="1.5" /></svg>';
481
+ $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7.5h16v1.5H4z"></path><path d="M4 15h16v1.5H4z"></path></svg>';
482
482
  if ( isset( $attributes['icon'] ) ) {
483
483
  if ( 'menu' === $attributes['icon'] ) {
484
- $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 5v1.5h14V5H5zm0 7.8h14v-1.5H5v1.5zM5 19h14v-1.5H5V19z" /></svg>';
484
+ $toggle_button_icon = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 5v1.5h14V5H5z"></path><path d="M5 12.8h14v-1.5H5v1.5z"></path><path d="M5 19h14v-1.5H5V19z"></path></svg>';
485
485
  }
486
486
  }
487
487
  $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
@@ -8,15 +8,7 @@ import clsx from 'clsx';
8
8
  */
9
9
  import { createBlock } from '@wordpress/blocks';
10
10
  import { useSelect, useDispatch } from '@wordpress/data';
11
- import {
12
- __experimentalToolsPanel as ToolsPanel,
13
- __experimentalToolsPanelItem as ToolsPanelItem,
14
- CheckboxControl,
15
- TextControl,
16
- TextareaControl,
17
- ToolbarButton,
18
- ToolbarGroup,
19
- } from '@wordpress/components';
11
+ import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
20
12
  import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes';
21
13
  import { __ } from '@wordpress/i18n';
22
14
  import {
@@ -29,9 +21,8 @@ import {
29
21
  useInnerBlocksProps,
30
22
  useBlockEditingMode,
31
23
  } from '@wordpress/block-editor';
32
- import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url';
24
+ import { isURL, prependHTTP } from '@wordpress/url';
33
25
  import { useState, useEffect, useRef } from '@wordpress/element';
34
- import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
35
26
  import { decodeEntities } from '@wordpress/html-entities';
36
27
  import { link as linkIcon, addSubmenu } from '@wordpress/icons';
37
28
  import { store as coreStore } from '@wordpress/core-data';
@@ -43,7 +34,7 @@ import { useMergeRefs, usePrevious } from '@wordpress/compose';
43
34
  import { LinkUI } from './link-ui';
44
35
  import { updateAttributes } from './update-attributes';
45
36
  import { getColors } from '../navigation/edit/utils';
46
- import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
37
+ import { Controls } from './shared';
47
38
 
48
39
  const DEFAULT_BLOCK = { name: 'core/navigation-link' };
49
40
  const NESTING_BLOCK_NAMES = [
@@ -175,136 +166,6 @@ function getMissingText( type ) {
175
166
  * packages/block-library/src/navigation-submenu/edit.js
176
167
  * Consider reusing this components for both blocks.
177
168
  */
178
- function Controls( { attributes, setAttributes, setIsEditingControl } ) {
179
- const { label, url, description, rel, opensInNewTab } = attributes;
180
- const lastURLRef = useRef( url );
181
- const dropdownMenuProps = useToolsPanelDropdownMenuProps();
182
- return (
183
- <ToolsPanel
184
- label={ __( 'Settings' ) }
185
- resetAll={ () => {
186
- setAttributes( {
187
- label: '',
188
- url: '',
189
- description: '',
190
- rel: '',
191
- opensInNewTab: false,
192
- } );
193
- } }
194
- dropdownMenuProps={ dropdownMenuProps }
195
- >
196
- <ToolsPanelItem
197
- hasValue={ () => !! label }
198
- label={ __( 'Text' ) }
199
- onDeselect={ () => setAttributes( { label: '' } ) }
200
- isShownByDefault
201
- >
202
- <TextControl
203
- __nextHasNoMarginBottom
204
- __next40pxDefaultSize
205
- label={ __( 'Text' ) }
206
- value={ label ? stripHTML( label ) : '' }
207
- onChange={ ( labelValue ) => {
208
- setAttributes( { label: labelValue } );
209
- } }
210
- autoComplete="off"
211
- onFocus={ () => setIsEditingControl( true ) }
212
- onBlur={ () => setIsEditingControl( false ) }
213
- />
214
- </ToolsPanelItem>
215
-
216
- <ToolsPanelItem
217
- hasValue={ () => !! url }
218
- label={ __( 'Link' ) }
219
- onDeselect={ () => setAttributes( { url: '' } ) }
220
- isShownByDefault
221
- >
222
- <TextControl
223
- __nextHasNoMarginBottom
224
- __next40pxDefaultSize
225
- label={ __( 'Link' ) }
226
- value={ url ? safeDecodeURI( url ) : '' }
227
- onChange={ ( urlValue ) => {
228
- setAttributes( {
229
- url: encodeURI( safeDecodeURI( urlValue ) ),
230
- } );
231
- } }
232
- autoComplete="off"
233
- type="url"
234
- onFocus={ () => {
235
- lastURLRef.current = url;
236
- setIsEditingControl( true );
237
- } }
238
- onBlur={ () => {
239
- // Defer the updateAttributes call to ensure entity connection isn't severed by accident.
240
- updateAttributes(
241
- { url: ! url ? lastURLRef.current : url },
242
- setAttributes,
243
- { ...attributes, url: lastURLRef.current }
244
- );
245
- setIsEditingControl( false );
246
- } }
247
- />
248
- </ToolsPanelItem>
249
-
250
- <ToolsPanelItem
251
- hasValue={ () => !! opensInNewTab }
252
- label={ __( 'Open in new tab' ) }
253
- onDeselect={ () => setAttributes( { opensInNewTab: false } ) }
254
- isShownByDefault
255
- >
256
- <CheckboxControl
257
- __nextHasNoMarginBottom
258
- label={ __( 'Open in new tab' ) }
259
- checked={ opensInNewTab }
260
- onChange={ ( value ) =>
261
- setAttributes( { opensInNewTab: value } )
262
- }
263
- />
264
- </ToolsPanelItem>
265
-
266
- <ToolsPanelItem
267
- hasValue={ () => !! description }
268
- label={ __( 'Description' ) }
269
- onDeselect={ () => setAttributes( { description: '' } ) }
270
- isShownByDefault
271
- >
272
- <TextareaControl
273
- __nextHasNoMarginBottom
274
- label={ __( 'Description' ) }
275
- value={ description || '' }
276
- onChange={ ( descriptionValue ) => {
277
- setAttributes( { description: descriptionValue } );
278
- } }
279
- help={ __(
280
- 'The description will be displayed in the menu if the current theme supports it.'
281
- ) }
282
- />
283
- </ToolsPanelItem>
284
-
285
- <ToolsPanelItem
286
- hasValue={ () => !! rel }
287
- label={ __( 'Rel attribute' ) }
288
- onDeselect={ () => setAttributes( { rel: '' } ) }
289
- isShownByDefault
290
- >
291
- <TextControl
292
- __nextHasNoMarginBottom
293
- __next40pxDefaultSize
294
- label={ __( 'Rel attribute' ) }
295
- value={ rel || '' }
296
- onChange={ ( relValue ) => {
297
- setAttributes( { rel: relValue } );
298
- } }
299
- autoComplete="off"
300
- help={ __(
301
- 'The relationship of the linked URL as space-separated link types.'
302
- ) }
303
- />
304
- </ToolsPanelItem>
305
- </ToolsPanel>
306
- );
307
- }
308
169
 
309
170
  export default function NavigationLinkEdit( {
310
171
  attributes,
@@ -0,0 +1,47 @@
1
+ # Navigation Blocks Shared Components
2
+
3
+ This directory contains shared components and utilities used by both the Navigation Link and Navigation Submenu blocks to reduce code duplication and ensure consistent behavior.
4
+
5
+ ## Purpose
6
+
7
+ The Navigation Link and Navigation Submenu blocks share significant functionality, particularly in their inspector controls (ToolsPanel). This shared directory was created to:
8
+
9
+ - **Reduce code duplication** - Eliminate identical code between the two blocks
10
+ - **Ensure consistency** - Both blocks now use the same components, preventing behavioral differences
11
+ - **Reduce maintenance burden** - Changes to shared functionality only need to be made in one place
12
+ - **Minimize bugs** - Less duplicated code means fewer places for bugs to hide
13
+ - **Improve testability** - Shared components can be tested once and reused
14
+
15
+ ## Current Shared Components
16
+
17
+ - **`Controls`** - Inspector controls component providing the ToolsPanel interface for both blocks
18
+
19
+ ## Future Direction
20
+
21
+ While this shared directory provides immediate benefits for reducing duplication, the long-term vision is to refactor towards a **unified Navigation Item block** that can behave differently based on context (link vs submenu). This would:
22
+
23
+ - Eliminate the need for separate Navigation Link and Navigation Submenu blocks
24
+ - Provide a single, more maintainable codebase
25
+ - Allow for more flexible navigation item types
26
+ - Simplify the user experience
27
+
28
+ However, this refactoring is beyond the current scope and would require significant architectural changes. For now, this shared directory provides a practical solution that:
29
+
30
+ - Maintains backward compatibility
31
+ - Reduces immediate technical debt
32
+ - Prepares the foundation for future unification
33
+ - Supports the integration of new features like Dynamic URL functionality
34
+
35
+ ## Testing
36
+
37
+ All shared components include comprehensive tests in the `test/` directory. The tests use pure mocking strategies to ensure isolated, reliable testing of component behavior.
38
+
39
+ ## Contributing
40
+
41
+ When adding new shared functionality:
42
+
43
+ 1. Place shared components in this directory
44
+ 2. Export them from `index.js`
45
+ 3. Add comprehensive tests
46
+ 4. Update both Navigation Link and Navigation Submenu blocks to use the shared component
47
+ 5. Remove any duplicated code from the individual blocks
@@ -0,0 +1,167 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ __experimentalToolsPanel as ToolsPanel,
6
+ __experimentalToolsPanelItem as ToolsPanelItem,
7
+ CheckboxControl,
8
+ TextControl,
9
+ TextareaControl,
10
+ } from '@wordpress/components';
11
+ import { __ } from '@wordpress/i18n';
12
+ import { useRef } from '@wordpress/element';
13
+ import { safeDecodeURI } from '@wordpress/url';
14
+ import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
15
+
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+ import { useToolsPanelDropdownMenuProps } from '../../utils/hooks';
20
+ import { updateAttributes } from '../update-attributes';
21
+
22
+ /**
23
+ * Shared Controls component for Navigation Link and Navigation Submenu blocks.
24
+ *
25
+ * This component provides the inspector controls (ToolsPanel) that are identical
26
+ * between both navigation blocks.
27
+ *
28
+ * @param {Object} props - Component props
29
+ * @param {Object} props.attributes - Block attributes
30
+ * @param {Function} props.setAttributes - Function to update block attributes
31
+ * @param {Function} props.setIsEditingControl - Function to set editing state (optional)
32
+ */
33
+ export function Controls( {
34
+ attributes,
35
+ setAttributes,
36
+ setIsEditingControl = () => {},
37
+ } ) {
38
+ const { label, url, description, rel, opensInNewTab } = attributes;
39
+ const lastURLRef = useRef( url );
40
+ const dropdownMenuProps = useToolsPanelDropdownMenuProps();
41
+
42
+ return (
43
+ <ToolsPanel
44
+ label={ __( 'Settings' ) }
45
+ resetAll={ () => {
46
+ setAttributes( {
47
+ label: '',
48
+ url: '',
49
+ description: '',
50
+ rel: '',
51
+ opensInNewTab: false,
52
+ } );
53
+ } }
54
+ dropdownMenuProps={ dropdownMenuProps }
55
+ >
56
+ <ToolsPanelItem
57
+ hasValue={ () => !! label }
58
+ label={ __( 'Text' ) }
59
+ onDeselect={ () => setAttributes( { label: '' } ) }
60
+ isShownByDefault
61
+ >
62
+ <TextControl
63
+ __nextHasNoMarginBottom
64
+ __next40pxDefaultSize
65
+ label={ __( 'Text' ) }
66
+ value={ label ? stripHTML( label ) : '' }
67
+ onChange={ ( labelValue ) => {
68
+ setAttributes( { label: labelValue } );
69
+ } }
70
+ autoComplete="off"
71
+ onFocus={ () => setIsEditingControl( true ) }
72
+ onBlur={ () => setIsEditingControl( false ) }
73
+ />
74
+ </ToolsPanelItem>
75
+
76
+ <ToolsPanelItem
77
+ hasValue={ () => !! url }
78
+ label={ __( 'Link' ) }
79
+ onDeselect={ () => setAttributes( { url: '' } ) }
80
+ isShownByDefault
81
+ >
82
+ <TextControl
83
+ __nextHasNoMarginBottom
84
+ __next40pxDefaultSize
85
+ label={ __( 'Link' ) }
86
+ value={ url ? safeDecodeURI( url ) : '' }
87
+ onChange={ ( urlValue ) => {
88
+ setAttributes( {
89
+ url: encodeURI( safeDecodeURI( urlValue ) ),
90
+ } );
91
+ } }
92
+ autoComplete="off"
93
+ type="url"
94
+ onFocus={ () => {
95
+ lastURLRef.current = url;
96
+ setIsEditingControl( true );
97
+ } }
98
+ onBlur={ () => {
99
+ // Defer the updateAttributes call to ensure entity connection isn't severed by accident.
100
+ updateAttributes(
101
+ { url: ! url ? lastURLRef.current : url },
102
+ setAttributes,
103
+ { ...attributes, url: lastURLRef.current }
104
+ );
105
+ setIsEditingControl( false );
106
+ } }
107
+ />
108
+ </ToolsPanelItem>
109
+
110
+ <ToolsPanelItem
111
+ hasValue={ () => !! opensInNewTab }
112
+ label={ __( 'Open in new tab' ) }
113
+ onDeselect={ () => setAttributes( { opensInNewTab: false } ) }
114
+ isShownByDefault
115
+ >
116
+ <CheckboxControl
117
+ __nextHasNoMarginBottom
118
+ label={ __( 'Open in new tab' ) }
119
+ checked={ opensInNewTab }
120
+ onChange={ ( value ) =>
121
+ setAttributes( { opensInNewTab: value } )
122
+ }
123
+ />
124
+ </ToolsPanelItem>
125
+
126
+ <ToolsPanelItem
127
+ hasValue={ () => !! description }
128
+ label={ __( 'Description' ) }
129
+ onDeselect={ () => setAttributes( { description: '' } ) }
130
+ isShownByDefault
131
+ >
132
+ <TextareaControl
133
+ __nextHasNoMarginBottom
134
+ label={ __( 'Description' ) }
135
+ value={ description || '' }
136
+ onChange={ ( descriptionValue ) => {
137
+ setAttributes( { description: descriptionValue } );
138
+ } }
139
+ help={ __(
140
+ 'The description will be displayed in the menu if the current theme supports it.'
141
+ ) }
142
+ />
143
+ </ToolsPanelItem>
144
+
145
+ <ToolsPanelItem
146
+ hasValue={ () => !! rel }
147
+ label={ __( 'Rel attribute' ) }
148
+ onDeselect={ () => setAttributes( { rel: '' } ) }
149
+ isShownByDefault
150
+ >
151
+ <TextControl
152
+ __nextHasNoMarginBottom
153
+ __next40pxDefaultSize
154
+ label={ __( 'Rel attribute' ) }
155
+ value={ rel || '' }
156
+ onChange={ ( relValue ) => {
157
+ setAttributes( { rel: relValue } );
158
+ } }
159
+ autoComplete="off"
160
+ help={ __(
161
+ 'The relationship of the linked URL as space-separated link types.'
162
+ ) }
163
+ />
164
+ </ToolsPanelItem>
165
+ </ToolsPanel>
166
+ );
167
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared components for Navigation Link and Navigation Submenu blocks.
3
+ *
4
+ * This module provides common functionality that can be used by both blocks
5
+ * to reduce code duplication and ensure consistent behavior.
6
+ */
7
+
8
+ export { Controls } from './controls';