@wordpress/block-library 9.43.1-next.v.202604091042.0 → 9.44.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 (85) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/cover/edit/index.cjs +8 -1
  3. package/build/cover/edit/index.cjs.map +2 -2
  4. package/build/embed/embed-preview.cjs +2 -1
  5. package/build/embed/embed-preview.cjs.map +2 -2
  6. package/build/image/image.cjs +28 -6
  7. package/build/image/image.cjs.map +2 -2
  8. package/build/navigation-link/link-ui/index.cjs +12 -1
  9. package/build/navigation-link/link-ui/index.cjs.map +2 -2
  10. package/build/paragraph/use-enter.cjs +4 -5
  11. package/build/paragraph/use-enter.cjs.map +2 -2
  12. package/build/search/block.json +5 -1
  13. package/build/search/edit.cjs +2 -0
  14. package/build/search/edit.cjs.map +2 -2
  15. package/build/separator/transforms.cjs +12 -4
  16. package/build/separator/transforms.cjs.map +2 -2
  17. package/build/tab/add-tab-toolbar-control.cjs +5 -17
  18. package/build/tab/add-tab-toolbar-control.cjs.map +2 -2
  19. package/build/tab/block.json +2 -1
  20. package/build/tab/remove-tab-toolbar-control.cjs +1 -4
  21. package/build/tab/remove-tab-toolbar-control.cjs.map +2 -2
  22. package/build/tabs/edit.cjs +45 -45
  23. package/build/tabs/edit.cjs.map +2 -2
  24. package/build/tabs-menu/block.json +1 -2
  25. package/build/tabs-menu-item/block.json +0 -6
  26. package/build/tabs-menu-item/edit.cjs +2 -8
  27. package/build/tabs-menu-item/edit.cjs.map +2 -2
  28. package/build-module/cover/edit/index.mjs +8 -1
  29. package/build-module/cover/edit/index.mjs.map +2 -2
  30. package/build-module/embed/embed-preview.mjs +2 -1
  31. package/build-module/embed/embed-preview.mjs.map +2 -2
  32. package/build-module/image/image.mjs +28 -6
  33. package/build-module/image/image.mjs.map +2 -2
  34. package/build-module/navigation-link/link-ui/index.mjs +12 -1
  35. package/build-module/navigation-link/link-ui/index.mjs.map +2 -2
  36. package/build-module/paragraph/use-enter.mjs +4 -5
  37. package/build-module/paragraph/use-enter.mjs.map +2 -2
  38. package/build-module/search/block.json +5 -1
  39. package/build-module/search/edit.mjs +2 -0
  40. package/build-module/search/edit.mjs.map +2 -2
  41. package/build-module/separator/transforms.mjs +17 -5
  42. package/build-module/separator/transforms.mjs.map +2 -2
  43. package/build-module/tab/add-tab-toolbar-control.mjs +6 -18
  44. package/build-module/tab/add-tab-toolbar-control.mjs.map +2 -2
  45. package/build-module/tab/block.json +2 -1
  46. package/build-module/tab/remove-tab-toolbar-control.mjs +1 -4
  47. package/build-module/tab/remove-tab-toolbar-control.mjs.map +2 -2
  48. package/build-module/tabs/edit.mjs +45 -45
  49. package/build-module/tabs/edit.mjs.map +2 -2
  50. package/build-module/tabs-menu/block.json +1 -2
  51. package/build-module/tabs-menu-item/block.json +0 -6
  52. package/build-module/tabs-menu-item/edit.mjs +3 -9
  53. package/build-module/tabs-menu-item/edit.mjs.map +2 -2
  54. package/build-style/post-author-biography/style-rtl.css +1 -0
  55. package/build-style/post-author-biography/style.css +1 -0
  56. package/build-style/style-rtl.css +10 -15
  57. package/build-style/style.css +10 -15
  58. package/build-style/tabs-menu-item/style-rtl.css +9 -6
  59. package/build-style/tabs-menu-item/style.css +9 -6
  60. package/package.json +38 -38
  61. package/src/cover/edit/index.js +9 -1
  62. package/src/embed/embed-preview.js +6 -5
  63. package/src/image/image.js +50 -5
  64. package/src/navigation-link/link-ui/index.js +12 -1
  65. package/src/paragraph/use-enter.js +5 -5
  66. package/src/post-author-biography/style.scss +1 -0
  67. package/src/search/block.json +5 -1
  68. package/src/search/edit.js +2 -0
  69. package/src/search/index.php +23 -3
  70. package/src/separator/transforms.js +19 -5
  71. package/src/style.scss +0 -1
  72. package/src/tab/add-tab-toolbar-control.js +24 -42
  73. package/src/tab/block.json +2 -1
  74. package/src/tab/index.php +21 -4
  75. package/src/tab/remove-tab-toolbar-control.js +1 -9
  76. package/src/tabs/edit.js +59 -66
  77. package/src/tabs/index.php +14 -15
  78. package/src/tabs-menu/block.json +1 -2
  79. package/src/tabs-menu/index.php +6 -17
  80. package/src/tabs-menu-item/block.json +0 -6
  81. package/src/tabs-menu-item/edit.js +3 -15
  82. package/src/tabs-menu-item/style.scss +10 -8
  83. package/build-style/tabs-menu/style-rtl.css +0 -8
  84. package/build-style/tabs-menu/style.css +0 -8
  85. package/src/tabs-menu/style.scss +0 -8
@@ -1,17 +1,31 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
4
+ import {
5
+ createBlock,
6
+ getBlockVariations,
7
+ getDefaultBlockName,
8
+ } from '@wordpress/blocks';
5
9
 
6
10
  const transforms = {
7
11
  from: [
8
12
  {
9
13
  type: 'input',
10
14
  regExp: /^-{3,}$/,
11
- transform: () => [
12
- createBlock( 'core/separator' ),
13
- createBlock( getDefaultBlockName() ),
14
- ],
15
+ transform: () => {
16
+ // Check for default variation to apply default variation attributes.
17
+ const defaultVariation = getBlockVariations(
18
+ 'core/separator'
19
+ )?.find( ( variation ) => variation.isDefault );
20
+
21
+ return [
22
+ createBlock(
23
+ 'core/separator',
24
+ defaultVariation?.attributes ?? {}
25
+ ),
26
+ createBlock( getDefaultBlockName() ),
27
+ ];
28
+ },
15
29
  },
16
30
  {
17
31
  type: 'raw',
package/src/style.scss CHANGED
@@ -79,7 +79,6 @@
79
79
  @use "./table/style.scss" as *;
80
80
  @use "./table-of-contents/style.scss" as *;
81
81
  @use "./tabs/style.scss" as *;
82
- @use "./tabs-menu/style.scss" as *;
83
82
  @use "./tabs-menu-item/style.scss" as *;
84
83
  @use "./term-count/style.scss" as *;
85
84
  @use "./term-description/style.scss" as *;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { sprintf, __ } from '@wordpress/i18n';
4
+ import { __ } from '@wordpress/i18n';
5
5
  import { createBlock } from '@wordpress/blocks';
6
6
  import {
7
7
  BlockControls,
@@ -22,61 +22,43 @@ import { useDispatch, useSelect } from '@wordpress/data';
22
22
  export default function AddTabToolbarControl( { tabsClientId } ) {
23
23
  const { insertBlock } = useDispatch( blockEditorStore );
24
24
 
25
- const { tabPanelClientId, tabsMenuClientId, tabCount, existingAnchors } =
26
- useSelect(
27
- ( select ) => {
28
- if ( ! tabsClientId ) {
29
- return {
30
- tabPanelClientId: null,
31
- tabsMenuClientId: null,
32
- existingAnchors: [],
33
- };
34
- }
35
- const { getBlocks } = select( blockEditorStore );
36
- const innerBlocks = getBlocks( tabsClientId );
37
- const tabPanel = innerBlocks.find(
38
- ( block ) => block.name === 'core/tab-panel'
39
- );
40
- const tabsMenu = innerBlocks.find(
41
- ( block ) => block.name === 'core/tabs-menu'
42
- );
25
+ const { tabPanelClientId, tabsMenuClientId } = useSelect(
26
+ ( select ) => {
27
+ if ( ! tabsClientId ) {
43
28
  return {
44
- tabPanelClientId: tabPanel?.clientId || null,
45
- tabsMenuClientId: tabsMenu?.clientId || null,
46
- tabCount: tabPanel?.innerBlocks?.length || 0,
47
- existingAnchors: ( tabPanel?.innerBlocks || [] )
48
- .map( ( block ) => block.attributes.anchor )
49
- .filter( Boolean ),
29
+ tabPanelClientId: null,
30
+ tabsMenuClientId: null,
50
31
  };
51
- },
52
- [ tabsClientId ]
53
- );
32
+ }
33
+ const { getBlocks } = select( blockEditorStore );
34
+ const innerBlocks = getBlocks( tabsClientId );
35
+ const tabPanel = innerBlocks.find(
36
+ ( block ) => block.name === 'core/tab-panel'
37
+ );
38
+ const tabsMenu = innerBlocks.find(
39
+ ( block ) => block.name === 'core/tabs-menu'
40
+ );
41
+ return {
42
+ tabPanelClientId: tabPanel?.clientId || null,
43
+ tabsMenuClientId: tabsMenu?.clientId || null,
44
+ };
45
+ },
46
+ [ tabsClientId ]
47
+ );
54
48
 
55
49
  const addTab = () => {
56
50
  if ( ! tabPanelClientId ) {
57
51
  return;
58
52
  }
59
53
 
60
- // Start from count + 1 so the label stays sequential, then increment
61
- // until the anchor slot is free.
62
- const existingAnchorSet = new Set( existingAnchors );
63
- let tabNumber = tabCount + 1;
64
- while ( existingAnchorSet.has( `tab-${ tabNumber }` ) ) {
65
- tabNumber++;
66
- }
67
-
68
54
  const newTabBlock = createBlock( 'core/tab', {
69
- anchor: `tab-${ tabNumber }`,
70
- /* translators: %d: tab number */
71
- label: sprintf( __( 'Tab %d' ), tabNumber ),
55
+ label: __( 'Tab' ),
72
56
  } );
73
57
  insertBlock( newTabBlock, undefined, tabPanelClientId );
74
58
 
75
59
  // Insert a corresponding menu item into the tabs-menu.
76
60
  if ( tabsMenuClientId ) {
77
- const newMenuItemBlock = createBlock( 'core/tabs-menu-item', {
78
- anchor: `tab-${ tabNumber }-button`,
79
- } );
61
+ const newMenuItemBlock = createBlock( 'core/tabs-menu-item', {} );
80
62
  insertBlock( newMenuItemBlock, undefined, tabsMenuClientId );
81
63
  }
82
64
  };
@@ -17,7 +17,8 @@
17
17
  "parent": [ "core/tab-panel" ],
18
18
  "usesContext": [
19
19
  "core/tabs-activeTabIndex",
20
- "core/tabs-editorActiveTabIndex"
20
+ "core/tabs-editorActiveTabIndex",
21
+ "core/tabs-id"
21
22
  ],
22
23
  "supports": {
23
24
  "anchor": true,
package/src/tab/index.php CHANGED
@@ -11,17 +11,34 @@
11
11
  * @since 7.0.0
12
12
  *
13
13
  * @param array $attributes Block attributes.
14
- * @param string $content Block content.
14
+ * @param array $attributes Block attributes.
15
+ * @param string $content Block content.
16
+ * @param \WP_Block $block Block instance.
15
17
  *
16
18
  * @return string Updated HTML.
17
19
  */
18
- function block_core_tab_render( array $attributes, string $content ): string {
20
+ function block_core_tab_render( array $attributes, string $content, \WP_Block $block ): string {
21
+ $tabs_id = $block->context['core/tabs-id'] ?? '';
22
+
23
+ static $tab_counters = array();
24
+
25
+ if ( ! isset( $tab_counters[ $tabs_id ] ) ) {
26
+ $tab_counters[ $tabs_id ] = 0;
27
+ }
28
+
29
+ $tab_index = $tab_counters[ $tabs_id ];
30
+ ++$tab_counters[ $tabs_id ];
31
+
19
32
  $tag_processor = new WP_HTML_Tag_Processor( $content );
20
33
  $tag_processor->next_tag( array( 'class_name' => 'wp-block-tab' ) );
34
+
35
+ // Use the user's custom anchor if present, otherwise fall back to
36
+ // the generated position-based ID.
21
37
  $tab_id = (string) $tag_processor->get_attribute( 'id' );
22
- // If no id, generate a unique one
23
38
  if ( empty( $tab_id ) ) {
24
- $tab_id = sanitize_title( $attributes['label'] );
39
+ $tab_id = ! empty( $tabs_id )
40
+ ? $tabs_id . '-tab-' . $tab_index
41
+ : 'tab-' . $tab_index;
25
42
  $tag_processor->set_attribute( 'id', $tab_id );
26
43
  }
27
44
 
@@ -58,15 +58,7 @@ export default function RemoveTabToolbarControl( { tabsClientId } ) {
58
58
  const tabs = tabPanel?.innerBlocks || [];
59
59
  const menuItems = tabsMenu?.innerBlocks || [];
60
60
  const activeTab = tabs[ activeIndex ];
61
- // Match menu item by anchor (e.g. "tab-1" → "tab-1-button").
62
- const expectedMenuAnchor = activeTab?.attributes?.anchor
63
- ? `${ activeTab.attributes.anchor }-button`
64
- : null;
65
- const activeMenuItem = expectedMenuAnchor
66
- ? menuItems.find(
67
- ( m ) => m.attributes?.anchor === expectedMenuAnchor
68
- )
69
- : menuItems[ activeIndex ];
61
+ const activeMenuItem = menuItems[ activeIndex ];
70
62
  return {
71
63
  activeTabClientId: activeTab?.clientId || null,
72
64
  activeMenuItemClientId: activeMenuItem?.clientId || null,
package/src/tabs/edit.js CHANGED
@@ -9,12 +9,15 @@ import {
9
9
  } from '@wordpress/block-editor';
10
10
  import { useSelect, useDispatch } from '@wordpress/data';
11
11
  import { useMemo, useEffect, useRef } from '@wordpress/element';
12
+ import { __ } from '@wordpress/i18n';
12
13
 
13
14
  /**
14
15
  * Internal dependencies
15
16
  */
16
17
  import Controls from './controls';
17
18
 
19
+ const EMPTY_ARRAY = [];
20
+
18
21
  const TABS_TEMPLATE = [
19
22
  [
20
23
  'core/tabs-menu',
@@ -24,8 +27,8 @@ const TABS_TEMPLATE = [
24
27
  },
25
28
  },
26
29
  [
27
- [ 'core/tabs-menu-item', { anchor: 'tab-1-button' } ],
28
- [ 'core/tabs-menu-item', { anchor: 'tab-2-button' } ],
30
+ [ 'core/tabs-menu-item', {} ],
31
+ [ 'core/tabs-menu-item', {} ],
29
32
  ],
30
33
  ],
31
34
  [
@@ -39,16 +42,14 @@ const TABS_TEMPLATE = [
39
42
  [
40
43
  'core/tab',
41
44
  {
42
- anchor: 'tab-1',
43
- label: 'Tab 1',
45
+ label: __( 'Tab' ),
44
46
  },
45
47
  [ [ 'core/paragraph' ] ],
46
48
  ],
47
49
  [
48
50
  'core/tab',
49
51
  {
50
- anchor: 'tab-2',
51
- label: 'Tab 2',
52
+ label: __( 'Tab' ),
52
53
  },
53
54
  [ [ 'core/paragraph' ] ],
54
55
  ],
@@ -74,41 +75,24 @@ function Edit( {
74
75
  }
75
76
  }, [] ); // eslint-disable-line react-hooks/exhaustive-deps
76
77
 
77
- const { removeBlock } = useDispatch( blockEditorStore );
78
+ const { removeBlock, replaceInnerBlocks } = useDispatch( blockEditorStore );
78
79
 
79
- /**
80
- * Construct a list of core/tab blocks, used to create tabs-list context.
81
- * Also select menu items with their anchors for anchor-based deletion sync.
82
- */
83
- const { tabs, menuItems } = useSelect(
80
+ const { tabs, tabPanelClientId, menuItems } = useSelect(
84
81
  ( select ) => {
85
82
  const { getBlocks } = select( blockEditorStore );
86
83
  const innerBlocks = getBlocks( clientId );
87
84
 
88
- // Find tab-panel block and extract tab data.
89
85
  const tabPanel = innerBlocks.find(
90
86
  ( block ) => block.name === 'core/tab-panel'
91
87
  );
92
-
93
- // Find tabs-menu block and get its children with their anchors.
94
88
  const tabsMenu = innerBlocks.find(
95
89
  ( block ) => block.name === 'core/tabs-menu'
96
90
  );
97
91
 
98
92
  return {
99
- tabs: tabPanel
100
- ? tabPanel.innerBlocks.filter(
101
- ( block ) => block.name === 'core/tab'
102
- )
103
- : [],
104
- menuItems: tabsMenu
105
- ? getBlocks( tabsMenu.clientId )
106
- .filter( ( b ) => b.name === 'core/tabs-menu-item' )
107
- .map( ( b ) => ( {
108
- clientId: b.clientId,
109
- anchor: b.attributes.anchor ?? '',
110
- } ) )
111
- : [],
93
+ tabs: tabPanel?.innerBlocks ?? EMPTY_ARRAY,
94
+ tabPanelClientId: tabPanel?.clientId ?? null,
95
+ menuItems: tabsMenu?.innerBlocks ?? EMPTY_ARRAY,
112
96
  };
113
97
  },
114
98
  [ clientId ]
@@ -130,7 +114,6 @@ function Edit( {
130
114
  useEffect( () => {
131
115
  const currentTabs = tabs.map( ( tab ) => ( {
132
116
  clientId: tab.clientId,
133
- anchor: tab.attributes.anchor ?? '',
134
117
  } ) );
135
118
 
136
119
  if ( prevSyncStateRef.current === null ) {
@@ -146,6 +129,13 @@ function Edit( {
146
129
 
147
130
  const tabsRemoved = currentTabs.length < prevTabs.length;
148
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
+ );
149
139
 
150
140
  // Update snapshot to the current state.
151
141
  // Snapshot is updated eagerly; post-removal mutations keep it consistent
@@ -155,6 +145,23 @@ function Edit( {
155
145
  menuItems: [ ...menuItems ],
156
146
  };
157
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
+
158
165
  // Lists are in sync, nothing changed, or toolbar already removed both.
159
166
  if (
160
167
  ( ! tabsRemoved && ! menuItemsRemoved ) ||
@@ -169,45 +176,31 @@ function Edit( {
169
176
  );
170
177
 
171
178
  if ( tabsRemoved ) {
172
- prevTabs.forEach( ( prevTab ) => {
173
- if ( currentTabIds.has( prevTab.clientId ) ) {
174
- return;
175
- }
176
- const expectedMenuAnchor = prevTab.anchor
177
- ? `${ prevTab.anchor }-button`
178
- : null;
179
- const menuItemToRemove = expectedMenuAnchor
180
- ? menuItems.find( ( m ) => m.anchor === expectedMenuAnchor )
181
- : null;
182
- if ( menuItemToRemove ) {
183
- removeBlock( menuItemToRemove.clientId, false );
184
- prevSyncStateRef.current.menuItems =
185
- prevSyncStateRef.current.menuItems.filter(
186
- ( m ) => m.clientId !== menuItemToRemove.clientId
187
- );
188
- }
189
- } );
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
190
  } else {
191
- prevMenuItems.forEach( ( prevItem ) => {
192
- if ( currentMenuItemIds.has( prevItem.clientId ) ) {
193
- return;
194
- }
195
- const expectedTabAnchor =
196
- prevItem.anchor?.replace( /-button$/, '' ) ?? '';
197
- const tabToRemove = tabs.find(
198
- ( tab ) =>
199
- ( tab.attributes.anchor ?? '' ) === expectedTabAnchor
200
- );
201
- if ( tabToRemove ) {
202
- removeBlock( tabToRemove.clientId, false );
203
- prevSyncStateRef.current.tabs =
204
- prevSyncStateRef.current.tabs.filter(
205
- ( t ) => t.clientId !== tabToRemove.clientId
206
- );
207
- }
208
- } );
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
+ }
209
202
  }
210
- }, [ tabs, menuItems, removeBlock ] );
203
+ }, [ tabs, tabPanelClientId, menuItems, removeBlock, replaceInnerBlocks ] );
211
204
 
212
205
  /**
213
206
  * Memoize context value to prevent unnecessary re-renders.
@@ -10,11 +10,12 @@
10
10
  *
11
11
  * @since 7.0.0
12
12
  *
13
- * @param array $innerblocks Parsed inner blocks of tabs block.
13
+ * @param array $innerblocks Parsed inner blocks of tabs block.
14
+ * @param string $tabs_id Unique ID for the tabs instance, used to generate tab IDs.
14
15
  *
15
16
  * @return array List of tabs with id, label, index.
16
17
  */
17
- function block_core_tabs_generate_tabs_list( array $innerblocks = array() ): array {
18
+ function block_core_tabs_generate_tabs_list( array $innerblocks = array(), string $tabs_id = '' ): array {
18
19
  $tabs_list = array();
19
20
 
20
21
  // Find tab-panel block
@@ -26,17 +27,11 @@ function block_core_tabs_generate_tabs_list( array $innerblocks = array() ): arr
26
27
  $attrs = $tab_block['attrs'] ?? array();
27
28
  $tab_label = $attrs['label'] ?? '';
28
29
 
29
- // Try to get the ID from the rendered content
30
- $tab_id = $attrs['anchor'] ?? '';
31
- if ( empty( $tab_id ) && ! empty( $tab_block['innerHTML'] ) ) {
32
- $tag_processor = new WP_HTML_Tag_Processor( $tab_block['innerHTML'] );
33
- if ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-tab' ) ) ) {
34
- $tab_id = $tag_processor->get_attribute( 'id' ) ?? '';
35
- }
36
- }
37
- if ( empty( $tab_id ) ) {
38
- $tab_id = 'tab-' . $tab_index;
39
- }
30
+ $tab_id = ! empty( $attrs['anchor'] )
31
+ ? $attrs['anchor']
32
+ : ( ! empty( $tabs_id )
33
+ ? $tabs_id . '-tab-' . $tab_index
34
+ : 'tab-' . $tab_index );
40
35
 
41
36
  $tabs_list[] = array(
42
37
  'id' => esc_attr( $tab_id ),
@@ -67,9 +62,13 @@ function block_core_tabs_generate_tabs_list( array $innerblocks = array() ): arr
67
62
  */
68
63
  function block_core_tabs_provide_context( array $context, array $parsed_block ): array {
69
64
  if ( 'core/tabs' === $parsed_block['blockName'] ) {
70
- $tabs_list = block_core_tabs_generate_tabs_list( $parsed_block['innerBlocks'] ?? array() );
65
+ // Generate a unique ID for the tabs instance first, so it can be used
66
+ // to derive stable tab IDs. Used for 3rd party extensibility to identify
67
+ // the tabs instance.
68
+ $tabs_id = $parsed_block['attrs']['anchor'] ?? wp_unique_id( 'tabs_' );
69
+ $tabs_list = block_core_tabs_generate_tabs_list( $parsed_block['innerBlocks'] ?? array(), $tabs_id );
71
70
  $context['core/tabs-list'] = $tabs_list;
72
- $context['core/tabs-id'] = $parsed_block['attrs']['anchor'] ?? wp_unique_id( 'tabs_' ); // Generate a unique ID for each tabs instance. Used for 3rd party extensibility to identify the tabs instance.
71
+ $context['core/tabs-id'] = $tabs_id;
73
72
  }
74
73
 
75
74
  return $context;
@@ -63,6 +63,5 @@
63
63
  }
64
64
  },
65
65
  "editorScript": "file:./index.js",
66
- "editorStyle": "file:./editor.css",
67
- "style": "file:./style-index.css"
66
+ "editorStyle": "file:./editor.css"
68
67
  }
@@ -28,29 +28,18 @@ function block_core_tabs_menu_render_callback( array $attributes, string $conten
28
28
  }
29
29
 
30
30
  // Re-render each tabs-menu-item with per-item context (index, id, label).
31
- // Match by anchor so the correct tab is found even when the two lists
32
- // are in different orders.
33
- $buttons_html = '';
31
+ // Match by position so items align with their corresponding tabs.
32
+ $buttons_html = '';
33
+ $menu_item_position = 0;
34
34
 
35
35
  foreach ( $block->parsed_block['innerBlocks'] ?? array() as $parsed_menu_item ) {
36
36
  if ( 'core/tabs-menu-item' !== ( $parsed_menu_item['blockName'] ?? '' ) ) {
37
37
  continue;
38
38
  }
39
39
 
40
- // Find the tab anchor from the menu item anchor (e.g. "tab-1-button" → "tab-1").
41
- $menu_item_anchor = $parsed_menu_item['attrs']['anchor'] ?? '';
42
- $tab_anchor = preg_replace( '/-button$/', '', $menu_item_anchor );
43
-
44
- // Find the matching tab in $tabs_list by id.
45
- $tab = null;
46
- $tab_index = 0;
47
- foreach ( $tabs_list as $index => $candidate ) {
48
- if ( ( $candidate['id'] ?? '' ) === $tab_anchor ) {
49
- $tab = $candidate;
50
- $tab_index = $index;
51
- break;
52
- }
53
- }
40
+ $tab = $tabs_list[ $menu_item_position ] ?? null;
41
+ $tab_index = $menu_item_position;
42
+ ++$menu_item_position;
54
43
 
55
44
  // Skip menu items with no matching tab.
56
45
  if ( null === $tab ) {
@@ -17,12 +17,6 @@
17
17
  "core/tabs-menu-item-id",
18
18
  "core/tabs-menu-item-label"
19
19
  ],
20
- "attributes": {
21
- "anchor": {
22
- "type": "string",
23
- "default": ""
24
- }
25
- },
26
20
  "supports": {
27
21
  "html": false,
28
22
  "reusable": false,
@@ -6,7 +6,7 @@ import clsx from 'clsx';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { __, sprintf } from '@wordpress/i18n';
9
+ import { __ } from '@wordpress/i18n';
10
10
  import {
11
11
  useBlockProps,
12
12
  store as blockEditorStore,
@@ -23,7 +23,6 @@ import Controls from './controls';
23
23
  const EMPTY_ARRAY = [];
24
24
 
25
25
  function Edit( {
26
- attributes,
27
26
  context,
28
27
  clientId,
29
28
  __unstableLayoutClassNames: layoutClassNames,
@@ -75,14 +74,7 @@ function Edit( {
75
74
  [ clientId, tabsList ]
76
75
  );
77
76
 
78
- // Find the corresponding tab's anchor from this menu item's anchor
79
- // attribute (e.g., "tab-1-button" → "tab-1"), then look it up in tabsList.
80
- // Falls back to positional lookup when no anchor is set.
81
- const tabAnchor = attributes.anchor?.replace( /-button$/, '' ) ?? '';
82
- const tab =
83
- ( tabAnchor && tabsList.find( ( t ) => t.id === tabAnchor ) ) ||
84
- tabsList[ menuItemIndex ] ||
85
- {};
77
+ const tab = tabsList[ menuItemIndex ] || {};
86
78
 
87
79
  // tabListIndex is the tab's position in tabsList, used for active-state
88
80
  // checks and click handling.
@@ -146,11 +138,7 @@ function Edit( {
146
138
  <RichText
147
139
  tagName="span"
148
140
  withoutInteractiveFormatting
149
- placeholder={ sprintf(
150
- /* translators: %d is the tab index + 1 */
151
- __( 'Tab title %d' ),
152
- menuItemIndex + 1
153
- ) }
141
+ placeholder={ __( 'Tab title' ) }
154
142
  value={ label }
155
143
  onChange={ handleLabelChange }
156
144
  />
@@ -7,6 +7,7 @@
7
7
  cursor: pointer;
8
8
  flex-basis: inherit !important;
9
9
  flex-grow: inherit !important;
10
+ position: relative;
10
11
 
11
12
  // Button reset
12
13
  border: none;
@@ -26,13 +27,14 @@
26
27
  text-transform: inherit;
27
28
  text-align: inherit;
28
29
 
29
- &:focus-visible {
30
- outline-offset: 2px;
31
- }
32
-
33
- &[aria-selected="true"],
34
- &.is-active {
35
- background-color: #000;
36
- color: #fff;
30
+ &[aria-selected="true"]::before,
31
+ &.is-active::before {
32
+ content: "";
33
+ position: absolute;
34
+ border-bottom: 2px solid currentColor;
35
+ pointer-events: none;
36
+ left: 0;
37
+ width: 100%;
38
+ bottom: 0;
37
39
  }
38
40
  }
@@ -1,8 +0,0 @@
1
- .wp-block-tabs-menu {
2
- display: flex;
3
- align-items: flex-end;
4
- min-width: fit-content;
5
- border-bottom-width: 1px;
6
- border-bottom-style: solid;
7
- border-bottom-color: #000;
8
- }
@@ -1,8 +0,0 @@
1
- .wp-block-tabs-menu {
2
- display: flex;
3
- align-items: flex-end;
4
- min-width: fit-content;
5
- border-bottom-width: 1px;
6
- border-bottom-style: solid;
7
- border-bottom-color: #000;
8
- }
@@ -1,8 +0,0 @@
1
- .wp-block-tabs-menu {
2
- display: flex;
3
- align-items: flex-end;
4
- min-width: fit-content;
5
- border-bottom-width: 1px; // A default border divider between the tabs-menu and the tab-panel
6
- border-bottom-style: solid;
7
- border-bottom-color: #000;
8
- }