@wordpress/block-library 9.44.0 → 9.44.1-next.v.202604201441.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 (119) hide show
  1. package/build/button/constants.cjs +1 -1
  2. package/build/button/constants.cjs.map +1 -1
  3. package/build/group/block.json +2 -1
  4. package/build/image/constants.cjs +1 -1
  5. package/build/image/constants.cjs.map +2 -2
  6. package/build/image/edit.cjs +1 -3
  7. package/build/image/edit.cjs.map +2 -2
  8. package/build/image/image.cjs +20 -9
  9. package/build/image/image.cjs.map +2 -2
  10. package/build/latest-posts/edit.cjs +1 -1
  11. package/build/latest-posts/edit.cjs.map +1 -1
  12. package/build/paragraph/use-enter.cjs +12 -23
  13. package/build/paragraph/use-enter.cjs.map +2 -2
  14. package/build/post-featured-image/edit.cjs +2 -5
  15. package/build/post-featured-image/edit.cjs.map +2 -2
  16. package/build/post-template/edit.cjs +8 -2
  17. package/build/post-template/edit.cjs.map +2 -2
  18. package/build/pullquote/block.json +1 -4
  19. package/build/site-logo/edit.cjs +2 -2
  20. package/build/site-logo/edit.cjs.map +1 -1
  21. package/build/tab/block.json +2 -2
  22. package/build/tab/edit.cjs +3 -21
  23. package/build/tab/edit.cjs.map +3 -3
  24. package/build/tab-panel/block.json +1 -1
  25. package/build/tab-panel/edit.cjs +1 -4
  26. package/build/tab-panel/edit.cjs.map +2 -2
  27. package/build/tabs/block.json +0 -1
  28. package/build/tabs/edit.cjs +10 -86
  29. package/build/tabs/edit.cjs.map +3 -3
  30. package/build/tabs/use-tab-menu-sync.cjs +192 -0
  31. package/build/tabs/use-tab-menu-sync.cjs.map +7 -0
  32. package/build/tabs-menu/block.json +1 -1
  33. package/build/tabs-menu/edit.cjs +5 -10
  34. package/build/tabs-menu/edit.cjs.map +3 -3
  35. package/build/tabs-menu-item/block.json +1 -4
  36. package/build/tabs-menu-item/edit.cjs +3 -12
  37. package/build/tabs-menu-item/edit.cjs.map +2 -2
  38. package/build-module/button/constants.mjs +1 -1
  39. package/build-module/button/constants.mjs.map +1 -1
  40. package/build-module/group/block.json +2 -1
  41. package/build-module/image/constants.mjs +1 -1
  42. package/build-module/image/constants.mjs.map +2 -2
  43. package/build-module/image/edit.mjs +1 -3
  44. package/build-module/image/edit.mjs.map +2 -2
  45. package/build-module/image/image.mjs +20 -9
  46. package/build-module/image/image.mjs.map +2 -2
  47. package/build-module/latest-posts/edit.mjs +1 -1
  48. package/build-module/latest-posts/edit.mjs.map +1 -1
  49. package/build-module/paragraph/use-enter.mjs +13 -23
  50. package/build-module/paragraph/use-enter.mjs.map +2 -2
  51. package/build-module/post-featured-image/edit.mjs +2 -5
  52. package/build-module/post-featured-image/edit.mjs.map +2 -2
  53. package/build-module/post-template/edit.mjs +8 -2
  54. package/build-module/post-template/edit.mjs.map +2 -2
  55. package/build-module/pullquote/block.json +1 -4
  56. package/build-module/site-logo/edit.mjs +2 -2
  57. package/build-module/site-logo/edit.mjs.map +1 -1
  58. package/build-module/tab/block.json +2 -2
  59. package/build-module/tab/edit.mjs +3 -21
  60. package/build-module/tab/edit.mjs.map +2 -2
  61. package/build-module/tab-panel/block.json +1 -1
  62. package/build-module/tab-panel/edit.mjs +1 -4
  63. package/build-module/tab-panel/edit.mjs.map +2 -2
  64. package/build-module/tabs/block.json +0 -1
  65. package/build-module/tabs/edit.mjs +12 -88
  66. package/build-module/tabs/edit.mjs.map +2 -2
  67. package/build-module/tabs/use-tab-menu-sync.mjs +171 -0
  68. package/build-module/tabs/use-tab-menu-sync.mjs.map +7 -0
  69. package/build-module/tabs-menu/block.json +1 -1
  70. package/build-module/tabs-menu/edit.mjs +5 -10
  71. package/build-module/tabs-menu/edit.mjs.map +2 -2
  72. package/build-module/tabs-menu-item/block.json +1 -4
  73. package/build-module/tabs-menu-item/edit.mjs +3 -12
  74. package/build-module/tabs-menu-item/edit.mjs.map +2 -2
  75. package/build-style/latest-comments/style-rtl.css +4 -4
  76. package/build-style/latest-comments/style.css +4 -4
  77. package/build-style/post-template/style-rtl.css +1 -1
  78. package/build-style/post-template/style.css +1 -1
  79. package/build-style/style-rtl.css +5 -8
  80. package/build-style/style.css +5 -8
  81. package/build-style/tabs/style-rtl.css +0 -3
  82. package/build-style/tabs/style.css +0 -3
  83. package/package.json +38 -38
  84. package/src/block/test/edit.native.js +1 -1
  85. package/src/button/constants.js +1 -1
  86. package/src/button/test/get-updated-link-attributes.js +6 -10
  87. package/src/group/block.json +2 -1
  88. package/src/image/constants.js +1 -1
  89. package/src/image/edit.js +0 -2
  90. package/src/image/image.js +33 -22
  91. package/src/image/test/edit.native.js +2 -2
  92. package/src/latest-comments/style.scss +7 -7
  93. package/src/latest-posts/edit.js +1 -1
  94. package/src/latest-posts/index.php +1 -1
  95. package/src/navigation/edit/test/overlay-template-part-selector.js +1 -0
  96. package/src/paragraph/use-enter.js +18 -24
  97. package/src/post-featured-image/edit.js +2 -9
  98. package/src/post-template/edit.js +7 -1
  99. package/src/post-template/index.php +3 -0
  100. package/src/post-template/style.scss +2 -2
  101. package/src/pullquote/block.json +1 -4
  102. package/src/site-logo/edit.js +2 -2
  103. package/src/tab/block.json +2 -2
  104. package/src/tab/edit.js +2 -27
  105. package/src/tab-panel/block.json +1 -1
  106. package/src/tab-panel/edit.js +2 -4
  107. package/src/tabs/block.json +0 -1
  108. package/src/tabs/edit.js +11 -134
  109. package/src/tabs/style.scss +0 -3
  110. package/src/tabs/use-tab-menu-sync.js +239 -0
  111. package/src/tabs-menu/block.json +1 -1
  112. package/src/tabs-menu/edit.js +6 -15
  113. package/src/tabs-menu-item/block.json +1 -4
  114. package/src/tabs-menu-item/edit.js +3 -12
  115. package/build/tab/slug-from-label.cjs +0 -37
  116. package/build/tab/slug-from-label.cjs.map +0 -7
  117. package/build-module/tab/slug-from-label.mjs +0 -16
  118. package/build-module/tab/slug-from-label.mjs.map +0 -7
  119. package/src/tab/slug-from-label.js +0 -26
package/src/tab/edit.js CHANGED
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import clsx from 'clsx';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -19,7 +14,6 @@ import { useMemo, useRef, useEffect } from '@wordpress/element';
19
14
  * Internal dependencies
20
15
  */
21
16
  import Controls from './controls';
22
- import slugFromLabel from './slug-from-label';
23
17
 
24
18
  const TEMPLATE = [
25
19
  [
@@ -32,19 +26,11 @@ const TEMPLATE = [
32
26
 
33
27
  const { cancelAnimationFrame } = window;
34
28
 
35
- export default function Edit( {
36
- attributes,
37
- clientId,
38
- context,
39
- isSelected,
40
- __unstableLayoutClassNames: layoutClassNames,
41
- } ) {
29
+ export default function Edit( { clientId, context, isSelected } ) {
42
30
  const focusRef = useRef();
43
31
 
44
- const { anchor, label } = attributes;
45
-
46
32
  // Consume tab indices from context
47
- const activeTabIndex = context[ 'core/tabs-activeTabIndex' ] ?? 0;
33
+ const activeTabIndex = context[ 'core/tabs-activeTabIndex' ];
48
34
  const editorActiveTabIndex = context[ 'core/tabs-editorActiveTabIndex' ];
49
35
  const effectiveActiveIndex = editorActiveTabIndex ?? activeTabIndex;
50
36
 
@@ -137,20 +123,9 @@ export default function Edit( {
137
123
  return false;
138
124
  }, [ isSelected, hasInnerBlocksSelected, isActiveTab ] );
139
125
 
140
- // Use a custom anchor, if set. Otherwise fall back to the slug generated from the label text.
141
- const tabPanelId = useMemo(
142
- () => anchor || slugFromLabel( label, blockIndex ),
143
- [ anchor, label, blockIndex ]
144
- );
145
- const tabLabelId = useMemo( () => `${ tabPanelId }--tab`, [ tabPanelId ] );
146
-
147
126
  const blockProps = useBlockProps( {
148
127
  hidden: ! isSelectedTab,
149
- 'aria-labelledby': tabLabelId,
150
- id: tabPanelId,
151
- role: 'tabpanel',
152
128
  tabIndex: isSelectedTab ? 0 : -1,
153
- className: clsx( 'wp-block-tab__editor-content', layoutClassNames ),
154
129
  } );
155
130
 
156
131
  const innerBlocksProps = useInnerBlocksProps( blockProps, {
@@ -5,7 +5,6 @@
5
5
  "name": "core/tab-panel",
6
6
  "title": "Tab Panel",
7
7
  "description": "Container for tab panel content in a tabbed interface.",
8
- "version": "1.0.0",
9
8
  "category": "design",
10
9
  "textdomain": "default",
11
10
  "parent": [ "core/tabs" ],
@@ -15,6 +14,7 @@
15
14
  "anchor": false,
16
15
  "html": false,
17
16
  "reusable": false,
17
+ "visibility": false,
18
18
  "lock": false,
19
19
  "dimensions": {
20
20
  "aspectRatio": false,
@@ -27,10 +27,8 @@ export default function Edit( { clientId } ) {
27
27
 
28
28
  // Get the parent tabs block clientId
29
29
  const tabsClientId = useSelect(
30
- ( select ) => {
31
- const { getBlockRootClientId } = select( blockEditorStore );
32
- return getBlockRootClientId( clientId );
33
- },
30
+ ( select ) =>
31
+ select( blockEditorStore ).getBlockRootClientId( clientId ),
34
32
  [ clientId ]
35
33
  );
36
34
 
@@ -5,7 +5,6 @@
5
5
  "name": "core/tabs",
6
6
  "title": "Tabs",
7
7
  "description": "Display content in a tabbed interface to help users navigate detailed content with ease.",
8
- "version": "1.0.0",
9
8
  "category": "design",
10
9
  "textdomain": "default",
11
10
  "allowedBlocks": [ "core/tabs-menu", "core/tab-panel" ],
package/src/tabs/edit.js CHANGED
@@ -7,25 +7,22 @@ import {
7
7
  BlockContextProvider,
8
8
  store as blockEditorStore,
9
9
  } from '@wordpress/block-editor';
10
- import { useSelect, useDispatch } from '@wordpress/data';
11
- import { useMemo, useEffect, useRef } from '@wordpress/element';
10
+ import { useSelect } from '@wordpress/data';
11
+ import { useMemo, useEffect } from '@wordpress/element';
12
12
  import { __ } from '@wordpress/i18n';
13
13
 
14
14
  /**
15
15
  * Internal dependencies
16
16
  */
17
17
  import Controls from './controls';
18
+ import useTabMenuSync from './use-tab-menu-sync';
18
19
 
19
20
  const EMPTY_ARRAY = [];
20
21
 
21
22
  const TABS_TEMPLATE = [
22
23
  [
23
24
  'core/tabs-menu',
24
- {
25
- lock: {
26
- remove: true,
27
- },
28
- },
25
+ {},
29
26
  [
30
27
  [ 'core/tabs-menu-item', {} ],
31
28
  [ 'core/tabs-menu-item', {} ],
@@ -33,11 +30,7 @@ const TABS_TEMPLATE = [
33
30
  ],
34
31
  [
35
32
  'core/tab-panel',
36
- {
37
- lock: {
38
- remove: true,
39
- },
40
- },
33
+ {},
41
34
  [
42
35
  [
43
36
  'core/tab',
@@ -57,12 +50,7 @@ const TABS_TEMPLATE = [
57
50
  ],
58
51
  ];
59
52
 
60
- function Edit( {
61
- clientId,
62
- attributes,
63
- setAttributes,
64
- __unstableLayoutClassNames: layoutClassNames,
65
- } ) {
53
+ function Edit( { clientId, attributes, setAttributes } ) {
66
54
  const { anchor, activeTabIndex, editorActiveTabIndex } = attributes;
67
55
 
68
56
  /**
@@ -75,9 +63,7 @@ function Edit( {
75
63
  }
76
64
  }, [] ); // eslint-disable-line react-hooks/exhaustive-deps
77
65
 
78
- const { removeBlock, replaceInnerBlocks } = useDispatch( blockEditorStore );
79
-
80
- const { tabs, tabPanelClientId, menuItems } = useSelect(
66
+ const { tabs, tabPanelClientId, menuItems, tabsMenuClientId } = useSelect(
81
67
  ( select ) => {
82
68
  const { getBlocks } = select( blockEditorStore );
83
69
  const innerBlocks = getBlocks( clientId );
@@ -93,114 +79,13 @@ function Edit( {
93
79
  tabs: tabPanel?.innerBlocks ?? EMPTY_ARRAY,
94
80
  tabPanelClientId: tabPanel?.clientId ?? null,
95
81
  menuItems: tabsMenu?.innerBlocks ?? EMPTY_ARRAY,
82
+ tabsMenuClientId: tabsMenu?.clientId ?? null,
96
83
  };
97
84
  },
98
85
  [ clientId ]
99
86
  );
100
87
 
101
- /**
102
- * Keep tabs and menu items in sync when either is deleted directly (e.g.
103
- * via the Backspace key or List View).
104
- *
105
- * TODO: This effect only handles deletions. The two lists can get out of
106
- * sync in other cases: if a user pastes a core/tab block into the tab-panel
107
- * (or duplicates one), no corresponding tabs-menu-item is created; if a
108
- * user drags and drops a tabs-menu-item, the tab panel is not copied with
109
- * it. We should extend this effect to handle insertions, detecting when
110
- * tabs.length > menuItems.length and inserting the missing menu
111
- * item(s) at the correct index.
112
- */
113
- const prevSyncStateRef = useRef( null );
114
- useEffect( () => {
115
- const currentTabs = tabs.map( ( tab ) => ( {
116
- clientId: tab.clientId,
117
- } ) );
118
-
119
- if ( prevSyncStateRef.current === null ) {
120
- prevSyncStateRef.current = {
121
- tabs: currentTabs,
122
- menuItems: [ ...menuItems ],
123
- };
124
- return;
125
- }
126
-
127
- const { tabs: prevTabs, menuItems: prevMenuItems } =
128
- prevSyncStateRef.current;
129
-
130
- const tabsRemoved = currentTabs.length < prevTabs.length;
131
- const menuItemsRemoved = menuItems.length < prevMenuItems.length;
132
- const menuItemsReordered =
133
- ! tabsRemoved &&
134
- ! menuItemsRemoved &&
135
- menuItems.length === prevMenuItems.length &&
136
- menuItems.some(
137
- ( m, i ) => m.clientId !== prevMenuItems[ i ]?.clientId
138
- );
139
-
140
- // Update snapshot to the current state.
141
- // Snapshot is updated eagerly; post-removal mutations keep it consistent
142
- // so the next effect invocation sees a stable baseline.
143
- prevSyncStateRef.current = {
144
- tabs: currentTabs,
145
- menuItems: [ ...menuItems ],
146
- };
147
-
148
- // When menu items are reordered, move the corresponding tab content
149
- // blocks to match the new order.
150
- if ( menuItemsReordered && tabPanelClientId ) {
151
- const reorderedTabs = menuItems
152
- .map( ( menuItem ) => {
153
- const oldIndex = prevMenuItems.findIndex(
154
- ( pm ) => pm.clientId === menuItem.clientId
155
- );
156
- return oldIndex !== -1 ? tabs[ oldIndex ] : null;
157
- } )
158
- .filter( Boolean );
159
- if ( reorderedTabs.length === tabs.length ) {
160
- replaceInnerBlocks( tabPanelClientId, reorderedTabs, false );
161
- }
162
- return;
163
- }
164
-
165
- // Lists are in sync, nothing changed, or toolbar already removed both.
166
- if (
167
- ( ! tabsRemoved && ! menuItemsRemoved ) ||
168
- ( tabsRemoved && menuItemsRemoved )
169
- ) {
170
- return;
171
- }
172
-
173
- const currentTabIds = new Set( currentTabs.map( ( t ) => t.clientId ) );
174
- const currentMenuItemIds = new Set(
175
- menuItems.map( ( m ) => m.clientId )
176
- );
177
-
178
- if ( tabsRemoved ) {
179
- // Remove the menu item at the same position.
180
- const removedIndex = prevTabs.findIndex(
181
- ( t ) => ! currentTabIds.has( t.clientId )
182
- );
183
- if ( removedIndex >= 0 && menuItems[ removedIndex ] ) {
184
- removeBlock( menuItems[ removedIndex ].clientId, false );
185
- prevSyncStateRef.current.menuItems =
186
- prevSyncStateRef.current.menuItems.filter(
187
- ( _, i ) => i !== removedIndex
188
- );
189
- }
190
- } else {
191
- // Remove the tab at the same position.
192
- const removedIndex = prevMenuItems.findIndex(
193
- ( m ) => ! currentMenuItemIds.has( m.clientId )
194
- );
195
- if ( removedIndex >= 0 && tabs[ removedIndex ] ) {
196
- removeBlock( tabs[ removedIndex ].clientId, false );
197
- prevSyncStateRef.current.tabs =
198
- prevSyncStateRef.current.tabs.filter(
199
- ( _, i ) => i !== removedIndex
200
- );
201
- }
202
- }
203
- }, [ tabs, tabPanelClientId, menuItems, removeBlock, replaceInnerBlocks ] );
88
+ useTabMenuSync( { tabs, menuItems, tabPanelClientId, tabsMenuClientId } );
204
89
 
205
90
  /**
206
91
  * Memoize context value to prevent unnecessary re-renders.
@@ -226,20 +111,12 @@ function Edit( {
226
111
  };
227
112
  }, [ tabs, anchor, activeTabIndex, editorActiveTabIndex ] );
228
113
 
229
- /**
230
- * Block props for the tabs container.
231
- */
232
- const blockProps = useBlockProps( {
233
- className: layoutClassNames,
234
- } );
114
+ const blockProps = useBlockProps();
235
115
 
236
- /**
237
- * Innerblocks props for the tabs container.
238
- */
239
116
  const innerBlockProps = useInnerBlocksProps( blockProps, {
240
117
  __experimentalCaptureToolbars: true,
241
118
  template: TABS_TEMPLATE,
242
- templateLock: false,
119
+ templateLock: 'all',
243
120
  renderAppender: false,
244
121
  } );
245
122
 
@@ -1,6 +1,3 @@
1
1
  .wp-block-tabs {
2
2
  box-sizing: border-box;
3
- .wp-block-tabs__title {
4
- display: none;
5
- }
6
3
  }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { createBlock } from '@wordpress/blocks';
5
+ import { store as blockEditorStore } from '@wordpress/block-editor';
6
+ import { useDispatch } from '@wordpress/data';
7
+ import { useEffect, useRef } from '@wordpress/element';
8
+
9
+ /**
10
+ * Keep tabs and menu items in sync when the lists change due to direct
11
+ * user actions (deletion, paste, duplicate, drag-and-drop).
12
+ *
13
+ * Deletion: when one side shrinks, remove the counterpart at the same index.
14
+ * Insertion: when one side grows without the other, insert the missing
15
+ * counterpart at the matching index.
16
+ *
17
+ * When both lists change simultaneously (e.g. the "Add Tab" toolbar button,
18
+ * which inserts both at once), no action is needed and the effect exits early.
19
+ *
20
+ * @param {Object} props
21
+ * @param {Array} props.tabs Raw core/tab block objects.
22
+ * @param {Array} props.menuItems Raw core/tabs-menu-item block objects.
23
+ * @param {string|null} props.tabPanelClientId Client ID of the core/tab-panel block.
24
+ * @param {string|null} props.tabsMenuClientId Client ID of the core/tabs-menu block.
25
+ */
26
+ export default function useTabMenuSync( {
27
+ tabs,
28
+ menuItems,
29
+ tabPanelClientId,
30
+ tabsMenuClientId,
31
+ } ) {
32
+ const {
33
+ removeBlock,
34
+ insertBlocks,
35
+ replaceInnerBlocks,
36
+ __unstableMarkNextChangeAsNotPersistent,
37
+ } = useDispatch( blockEditorStore );
38
+
39
+ const prevSyncStateRef = useRef( null );
40
+ useEffect( () => {
41
+ if ( prevSyncStateRef.current === null ) {
42
+ prevSyncStateRef.current = {
43
+ tabs: [ ...tabs ],
44
+ menuItems: [ ...menuItems ],
45
+ };
46
+ return;
47
+ }
48
+
49
+ const { tabs: prevTabs, menuItems: prevMenuItems } =
50
+ prevSyncStateRef.current;
51
+
52
+ const tabCountChange = tabs.length - prevTabs.length;
53
+ const menuItemCountChange = menuItems.length - prevMenuItems.length;
54
+
55
+ const tabsInserted = tabCountChange > 0;
56
+ const menuItemsInserted = menuItemCountChange > 0;
57
+
58
+ // Both sides changed by the same amount.
59
+ // Covers: no-op re-renders, "Add Tab" toolbar, and toolbar-remove.
60
+ // Also handles drag-and-drop reordering of menu items.
61
+ if ( tabCountChange === menuItemCountChange ) {
62
+ // When lengths are equal but order changed, the user reordered menu
63
+ // items via drag-and-drop. Reorder the tab content blocks to match.
64
+ if (
65
+ tabCountChange === 0 &&
66
+ tabPanelClientId &&
67
+ menuItems.some(
68
+ ( m, i ) => m.clientId !== prevMenuItems[ i ]?.clientId
69
+ )
70
+ ) {
71
+ const reorderedTabs = menuItems
72
+ .map( ( menuItem ) => {
73
+ const oldIndex = prevMenuItems.findIndex(
74
+ ( pm ) => pm.clientId === menuItem.clientId
75
+ );
76
+ return oldIndex !== -1 ? tabs[ oldIndex ] : null;
77
+ } )
78
+ .filter( Boolean );
79
+ if ( reorderedTabs.length === tabs.length ) {
80
+ __unstableMarkNextChangeAsNotPersistent();
81
+ replaceInnerBlocks(
82
+ tabPanelClientId,
83
+ reorderedTabs,
84
+ false
85
+ );
86
+ }
87
+ }
88
+ prevSyncStateRef.current = {
89
+ tabs: [ ...tabs ],
90
+ menuItems: [ ...menuItems ],
91
+ };
92
+ return;
93
+ }
94
+
95
+ // Both sides changed in the same direction but by different amounts.
96
+ // Bail without making a partial fix.
97
+ if (
98
+ ( tabCountChange > 0 && menuItemCountChange > 0 ) ||
99
+ ( tabCountChange < 0 && menuItemCountChange < 0 )
100
+ ) {
101
+ prevSyncStateRef.current = {
102
+ tabs: [ ...tabs ],
103
+ menuItems: [ ...menuItems ],
104
+ };
105
+ return;
106
+ }
107
+
108
+ // If the required container block isn't available yet, bail without
109
+ // updating the snapshot so the next render re-evaluates the same count change.
110
+ if ( tabsInserted && ! tabsMenuClientId ) {
111
+ return;
112
+ }
113
+ if ( menuItemsInserted && ! tabPanelClientId ) {
114
+ return;
115
+ }
116
+
117
+ // Update snapshot to the current state.
118
+ prevSyncStateRef.current = {
119
+ tabs: [ ...tabs ],
120
+ menuItems: [ ...menuItems ],
121
+ };
122
+
123
+ const currentTabIds = new Set( tabs.map( ( t ) => t.clientId ) );
124
+ const currentMenuItemIds = new Set(
125
+ menuItems.map( ( m ) => m.clientId )
126
+ );
127
+
128
+ if ( tabCountChange < 0 ) {
129
+ // Remove the menu item at the same position as each deleted tab.
130
+ const removedIndices = prevTabs
131
+ .map( ( t, i ) =>
132
+ ! currentTabIds.has( t.clientId ) ? i : -1
133
+ )
134
+ .filter( ( i ) => i !== -1 );
135
+ const removedSet = new Set( removedIndices );
136
+ removedIndices.forEach( ( removedIndex ) => {
137
+ if ( menuItems[ removedIndex ] ) {
138
+ __unstableMarkNextChangeAsNotPersistent();
139
+ removeBlock( menuItems[ removedIndex ].clientId, false );
140
+ }
141
+ } );
142
+ prevSyncStateRef.current.menuItems =
143
+ prevSyncStateRef.current.menuItems.filter(
144
+ ( _, i ) => ! removedSet.has( i )
145
+ );
146
+ } else if ( menuItemCountChange < 0 ) {
147
+ // Remove the tab at the same position as each deleted menu item.
148
+ const removedIndices = prevMenuItems
149
+ .map( ( m, i ) =>
150
+ ! currentMenuItemIds.has( m.clientId ) ? i : -1
151
+ )
152
+ .filter( ( i ) => i !== -1 );
153
+ const removedSet = new Set( removedIndices );
154
+ removedIndices.forEach( ( removedIndex ) => {
155
+ if ( tabs[ removedIndex ] ) {
156
+ __unstableMarkNextChangeAsNotPersistent();
157
+ removeBlock( tabs[ removedIndex ].clientId, false );
158
+ }
159
+ } );
160
+ prevSyncStateRef.current.tabs =
161
+ prevSyncStateRef.current.tabs.filter(
162
+ ( _, i ) => ! removedSet.has( i )
163
+ );
164
+ } else if ( tabsInserted ) {
165
+ // A tab was pasted or duplicated — insert matching menu items.
166
+ const prevTabIds = new Set( prevTabs.map( ( t ) => t.clientId ) );
167
+ const newMenuItems = tabs
168
+ .map( ( tab, tabIndex ) =>
169
+ ! prevTabIds.has( tab.clientId )
170
+ ? {
171
+ tabIndex,
172
+ block: createBlock( 'core/tabs-menu-item', {} ),
173
+ }
174
+ : null
175
+ )
176
+ .filter( Boolean );
177
+
178
+ if ( newMenuItems.length > 0 ) {
179
+ __unstableMarkNextChangeAsNotPersistent();
180
+ insertBlocks(
181
+ newMenuItems.map( ( { block } ) => block ),
182
+ newMenuItems[ 0 ].tabIndex,
183
+ tabsMenuClientId,
184
+ false
185
+ );
186
+ newMenuItems.forEach( ( { tabIndex, block } ) => {
187
+ prevSyncStateRef.current.menuItems.splice( tabIndex, 0, {
188
+ clientId: block.clientId,
189
+ } );
190
+ } );
191
+ }
192
+ } else if ( menuItemsInserted ) {
193
+ // A menu item was pasted or duplicated — insert matching tabs,
194
+ // copying the label from the adjacent tab.
195
+ const prevMenuItemIds = new Set(
196
+ prevMenuItems.map( ( m ) => m.clientId )
197
+ );
198
+ const newTabs = menuItems
199
+ .map( ( menuItem, menuItemIndex ) => {
200
+ if ( prevMenuItemIds.has( menuItem.clientId ) ) {
201
+ return null;
202
+ }
203
+ const label =
204
+ tabs[ menuItemIndex - 1 ]?.attributes?.label ??
205
+ tabs[ menuItemIndex ]?.attributes?.label ??
206
+ '';
207
+ return {
208
+ menuItemIndex,
209
+ block: createBlock( 'core/tab', { label } ),
210
+ };
211
+ } )
212
+ .filter( Boolean );
213
+
214
+ if ( newTabs.length > 0 ) {
215
+ __unstableMarkNextChangeAsNotPersistent();
216
+ insertBlocks(
217
+ newTabs.map( ( { block } ) => block ),
218
+ newTabs[ 0 ].menuItemIndex,
219
+ tabPanelClientId,
220
+ false
221
+ );
222
+ newTabs.forEach( ( { menuItemIndex, block } ) => {
223
+ prevSyncStateRef.current.tabs.splice( menuItemIndex, 0, {
224
+ clientId: block.clientId,
225
+ } );
226
+ } );
227
+ }
228
+ }
229
+ }, [
230
+ tabs,
231
+ menuItems,
232
+ removeBlock,
233
+ insertBlocks,
234
+ replaceInnerBlocks,
235
+ __unstableMarkNextChangeAsNotPersistent,
236
+ tabsMenuClientId,
237
+ tabPanelClientId,
238
+ ] );
239
+ }
@@ -5,7 +5,6 @@
5
5
  "name": "core/tabs-menu",
6
6
  "title": "Tabs Menu",
7
7
  "description": "Display the tab buttons for a tabbed interface.",
8
- "version": "1.0.0",
9
8
  "category": "design",
10
9
  "textdomain": "default",
11
10
  "parent": [ "core/tabs" ],
@@ -15,6 +14,7 @@
15
14
  "supports": {
16
15
  "html": false,
17
16
  "reusable": false,
17
+ "visibility": false,
18
18
  "lock": false,
19
19
  "dimensions": {
20
20
  "aspectRatio": false,
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import clsx from 'clsx';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -19,23 +14,19 @@ import { useSelect } from '@wordpress/data';
19
14
  import AddTabToolbarControl from '../tab/add-tab-toolbar-control';
20
15
  import RemoveTabToolbarControl from '../tab/remove-tab-toolbar-control';
21
16
 
22
- function Edit( { clientId, __unstableLayoutClassNames: layoutClassNames } ) {
23
- const { tabsClientId } = useSelect(
24
- ( select ) => ( {
25
- tabsClientId:
26
- select( blockEditorStore ).getBlockRootClientId( clientId ),
27
- } ),
17
+ function Edit( { clientId } ) {
18
+ const tabsClientId = useSelect(
19
+ ( select ) =>
20
+ select( blockEditorStore ).getBlockRootClientId( clientId ),
28
21
  [ clientId ]
29
22
  );
30
23
 
31
- const blockProps = useBlockProps( {
32
- className: clsx( layoutClassNames ),
33
- role: 'tablist',
34
- } );
24
+ const blockProps = useBlockProps();
35
25
 
36
26
  const innerBlocksProps = useInnerBlocksProps( blockProps, {
37
27
  allowedBlocks: [ 'core/tabs-menu-item' ],
38
28
  orientation: 'horizontal',
29
+ templateLock: false,
39
30
  renderAppender: false,
40
31
  } );
41
32
 
@@ -5,7 +5,6 @@
5
5
  "name": "core/tabs-menu-item",
6
6
  "title": "Tab Menu Item",
7
7
  "description": "A single tab button in the tabs menu.",
8
- "version": "1.0.0",
9
8
  "category": "design",
10
9
  "textdomain": "default",
11
10
  "parent": [ "core/tabs-menu" ],
@@ -20,6 +19,7 @@
20
19
  "supports": {
21
20
  "html": false,
22
21
  "reusable": false,
22
+ "visibility": false,
23
23
  "lock": false,
24
24
  "color": {
25
25
  "background": true,
@@ -48,9 +48,6 @@
48
48
  "color": true,
49
49
  "width": true,
50
50
  "style": true
51
- },
52
- "layout": {
53
- "allowEditing": false
54
51
  }
55
52
  },
56
53
  "editorScript": "file:./index.js",
@@ -22,13 +22,9 @@ import Controls from './controls';
22
22
 
23
23
  const EMPTY_ARRAY = [];
24
24
 
25
- function Edit( {
26
- context,
27
- clientId,
28
- __unstableLayoutClassNames: layoutClassNames,
29
- } ) {
25
+ function Edit( { context, clientId } ) {
30
26
  const tabsList = context[ 'core/tabs-list' ] || EMPTY_ARRAY;
31
- const activeTabIndex = context[ 'core/tabs-activeTabIndex' ] ?? 0;
27
+ const activeTabIndex = context[ 'core/tabs-activeTabIndex' ];
32
28
  const editorActiveTabIndex = context[ 'core/tabs-editorActiveTabIndex' ];
33
29
 
34
30
  const effectiveActiveIndex = useMemo( () => {
@@ -80,7 +76,6 @@ function Edit( {
80
76
  // checks and click handling.
81
77
  const tabListIndex = tab.index ?? menuItemIndex;
82
78
 
83
- const tabId = tab.id || `tab-${ menuItemIndex }`;
84
79
  const tabClientId = tab.clientId || '';
85
80
  const label = tab.label || '';
86
81
 
@@ -119,14 +114,10 @@ function Edit( {
119
114
  );
120
115
 
121
116
  const blockProps = useBlockProps( {
122
- className: clsx( layoutClassNames, {
117
+ className: clsx( {
123
118
  'is-active': isActive,
124
119
  'is-selected': isSelected,
125
120
  } ),
126
- 'aria-controls': tabId,
127
- 'aria-selected': isActive,
128
- id: `${ tabId }--tab`,
129
- role: 'tab',
130
121
  tabIndex: -1,
131
122
  onClick: handleTabClick,
132
123
  } );