@wordpress/block-library 9.41.0 → 9.41.1-next.v.202603161435.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 (199) hide show
  1. package/build/cover/edit/cover-placeholder.cjs +7 -0
  2. package/build/cover/edit/cover-placeholder.cjs.map +2 -2
  3. package/build/html/block.json +2 -1
  4. package/build/html/modal.cjs +142 -147
  5. package/build/html/modal.cjs.map +3 -3
  6. package/build/icon/block.json +3 -12
  7. package/build/image/edit.cjs +7 -0
  8. package/build/image/edit.cjs.map +2 -2
  9. package/build/image/image.cjs +5 -9
  10. package/build/image/image.cjs.map +2 -2
  11. package/build/media-text/media-container.cjs +6 -0
  12. package/build/media-text/media-container.cjs.map +2 -2
  13. package/build/navigation/edit/index.cjs +21 -14
  14. package/build/navigation/edit/index.cjs.map +3 -3
  15. package/build/navigation/view.cjs +9 -2
  16. package/build/navigation/view.cjs.map +2 -2
  17. package/build/navigation-link/block.json +5 -0
  18. package/build/navigation-link/shared/use-link-preview.cjs +29 -0
  19. package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
  20. package/build/nextpage/block.json +0 -1
  21. package/build/paragraph/edit.cjs +1 -1
  22. package/build/paragraph/edit.cjs.map +1 -1
  23. package/build/playlist/edit.cjs +3 -23
  24. package/build/playlist/edit.cjs.map +3 -3
  25. package/build/playlist/utils.cjs +48 -0
  26. package/build/playlist/utils.cjs.map +7 -0
  27. package/build/playlist-track/block.json +0 -0
  28. package/build/post-excerpt/block.json +1 -3
  29. package/build/post-excerpt/deprecated.cjs +112 -0
  30. package/build/post-excerpt/deprecated.cjs.map +7 -0
  31. package/build/post-excerpt/edit.cjs +11 -30
  32. package/build/post-excerpt/edit.cjs.map +3 -3
  33. package/build/post-excerpt/index.cjs +3 -1
  34. package/build/post-excerpt/index.cjs.map +3 -3
  35. package/build/private-apis.cjs +3 -1
  36. package/build/private-apis.cjs.map +2 -2
  37. package/build/shortcode/block.json +2 -1
  38. package/build/site-logo/edit.cjs +1 -3
  39. package/build/site-logo/edit.cjs.map +2 -2
  40. package/build/tab/add-tab-toolbar-control.cjs +22 -5
  41. package/build/tab/add-tab-toolbar-control.cjs.map +2 -2
  42. package/build/tab/remove-tab-toolbar-control.cjs +19 -1
  43. package/build/tab/remove-tab-toolbar-control.cjs.map +2 -2
  44. package/build/tabs/edit.cjs +85 -7
  45. package/build/tabs/edit.cjs.map +2 -2
  46. package/build/tabs/index.cjs +12 -2
  47. package/build/tabs/index.cjs.map +2 -2
  48. package/build/tabs-menu/block.json +1 -6
  49. package/build/tabs-menu/edit.cjs +11 -151
  50. package/build/tabs-menu/edit.cjs.map +3 -3
  51. package/build/tabs-menu/save.cjs.map +2 -2
  52. package/build/tabs-menu-item/block.json +14 -11
  53. package/build/tabs-menu-item/controls.cjs +2 -133
  54. package/build/tabs-menu-item/controls.cjs.map +3 -3
  55. package/build/tabs-menu-item/edit.cjs +44 -56
  56. package/build/tabs-menu-item/edit.cjs.map +3 -3
  57. package/build/tabs-menu-item/save.cjs +0 -1
  58. package/build/tabs-menu-item/save.cjs.map +2 -2
  59. package/build/template-part/edit/index.cjs +6 -4
  60. package/build/template-part/edit/index.cjs.map +2 -2
  61. package/build/utils/media-control.cjs +72 -29
  62. package/build/utils/media-control.cjs.map +3 -3
  63. package/build-module/cover/edit/cover-placeholder.mjs +7 -0
  64. package/build-module/cover/edit/cover-placeholder.mjs.map +2 -2
  65. package/build-module/html/block.json +2 -1
  66. package/build-module/html/modal.mjs +144 -149
  67. package/build-module/html/modal.mjs.map +2 -2
  68. package/build-module/icon/block.json +3 -12
  69. package/build-module/image/edit.mjs +7 -0
  70. package/build-module/image/edit.mjs.map +2 -2
  71. package/build-module/image/image.mjs +5 -9
  72. package/build-module/image/image.mjs.map +2 -2
  73. package/build-module/media-text/media-container.mjs +7 -1
  74. package/build-module/media-text/media-container.mjs.map +2 -2
  75. package/build-module/navigation/edit/index.mjs +22 -14
  76. package/build-module/navigation/edit/index.mjs.map +2 -2
  77. package/build-module/navigation/view.mjs +9 -2
  78. package/build-module/navigation/view.mjs.map +2 -2
  79. package/build-module/navigation-link/block.json +5 -0
  80. package/build-module/navigation-link/shared/use-link-preview.mjs +28 -0
  81. package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
  82. package/build-module/nextpage/block.json +0 -1
  83. package/build-module/paragraph/edit.mjs +2 -2
  84. package/build-module/paragraph/edit.mjs.map +1 -1
  85. package/build-module/playlist/edit.mjs +2 -18
  86. package/build-module/playlist/edit.mjs.map +2 -2
  87. package/build-module/playlist/utils.mjs +23 -0
  88. package/build-module/playlist/utils.mjs.map +7 -0
  89. package/build-module/playlist-track/block.json +0 -0
  90. package/build-module/post-excerpt/block.json +1 -3
  91. package/build-module/post-excerpt/deprecated.mjs +81 -0
  92. package/build-module/post-excerpt/deprecated.mjs.map +7 -0
  93. package/build-module/post-excerpt/edit.mjs +12 -34
  94. package/build-module/post-excerpt/edit.mjs.map +2 -2
  95. package/build-module/post-excerpt/index.mjs +3 -1
  96. package/build-module/post-excerpt/index.mjs.map +2 -2
  97. package/build-module/private-apis.mjs +3 -1
  98. package/build-module/private-apis.mjs.map +2 -2
  99. package/build-module/shortcode/block.json +2 -1
  100. package/build-module/site-logo/edit.mjs +1 -3
  101. package/build-module/site-logo/edit.mjs.map +2 -2
  102. package/build-module/tab/add-tab-toolbar-control.mjs +22 -5
  103. package/build-module/tab/add-tab-toolbar-control.mjs.map +2 -2
  104. package/build-module/tab/remove-tab-toolbar-control.mjs +19 -1
  105. package/build-module/tab/remove-tab-toolbar-control.mjs.map +2 -2
  106. package/build-module/tabs/edit.mjs +87 -9
  107. package/build-module/tabs/edit.mjs.map +2 -2
  108. package/build-module/tabs/index.mjs +12 -2
  109. package/build-module/tabs/index.mjs.map +2 -2
  110. package/build-module/tabs-menu/block.json +1 -6
  111. package/build-module/tabs-menu/edit.mjs +13 -162
  112. package/build-module/tabs-menu/edit.mjs.map +2 -2
  113. package/build-module/tabs-menu/save.mjs.map +2 -2
  114. package/build-module/tabs-menu-item/block.json +14 -11
  115. package/build-module/tabs-menu-item/controls.mjs +4 -143
  116. package/build-module/tabs-menu-item/controls.mjs.map +2 -2
  117. package/build-module/tabs-menu-item/edit.mjs +45 -57
  118. package/build-module/tabs-menu-item/edit.mjs.map +3 -3
  119. package/build-module/tabs-menu-item/save.mjs +0 -1
  120. package/build-module/tabs-menu-item/save.mjs.map +2 -2
  121. package/build-module/template-part/edit/index.mjs +6 -4
  122. package/build-module/template-part/edit/index.mjs.map +2 -2
  123. package/build-module/utils/media-control.mjs +73 -30
  124. package/build-module/utils/media-control.mjs.map +2 -2
  125. package/build-style/common-rtl.css +1 -0
  126. package/build-style/common.css +1 -0
  127. package/build-style/editor-rtl.css +55 -17
  128. package/build-style/editor.css +55 -17
  129. package/build-style/html/editor-rtl.css +10 -6
  130. package/build-style/html/editor.css +10 -6
  131. package/build-style/navigation/style-rtl.css +15 -1
  132. package/build-style/navigation/style.css +15 -1
  133. package/build-style/navigation-overlay-close/style-rtl.css +3 -3
  134. package/build-style/navigation-overlay-close/style.css +3 -3
  135. package/build-style/playlist/style-rtl.css +4 -0
  136. package/build-style/playlist/style.css +4 -0
  137. package/build-style/style-rtl.css +23 -4
  138. package/build-style/style.css +23 -4
  139. package/build-style/tabs-menu/editor-rtl.css +5 -3
  140. package/build-style/tabs-menu/editor.css +5 -3
  141. package/package.json +38 -38
  142. package/src/accordion-item/index.php +17 -5
  143. package/src/common.scss +1 -0
  144. package/src/cover/edit/cover-placeholder.js +8 -0
  145. package/src/cover/index.php +8 -0
  146. package/src/details/index.php +47 -0
  147. package/src/html/block.json +2 -1
  148. package/src/html/editor.scss +15 -5
  149. package/src/html/modal.js +26 -22
  150. package/src/icon/block.json +3 -12
  151. package/src/image/edit.js +8 -0
  152. package/src/image/image.js +8 -13
  153. package/src/media-text/media-container.js +8 -1
  154. package/src/navigation/edit/index.js +26 -14
  155. package/src/navigation/index.php +27 -13
  156. package/src/navigation/style.scss +17 -1
  157. package/src/navigation/view.js +14 -2
  158. package/src/navigation-link/block.json +5 -0
  159. package/src/navigation-link/index.php +10 -10
  160. package/src/navigation-link/shared/test/use-link-preview.test.js +149 -0
  161. package/src/navigation-link/shared/use-link-preview.js +43 -1
  162. package/src/navigation-overlay-close/style.scss +3 -3
  163. package/src/navigation-submenu/index.php +17 -11
  164. package/src/nextpage/block.json +0 -1
  165. package/src/paragraph/edit.js +2 -2
  166. package/src/playlist/edit.js +1 -34
  167. package/src/playlist/style.scss +5 -0
  168. package/src/playlist/test/edit.js +1 -1
  169. package/src/playlist/utils.js +42 -0
  170. package/src/playlist-track/block.json +0 -0
  171. package/src/playlist-track/edit.js +0 -0
  172. package/src/playlist-track/index.js +0 -0
  173. package/src/playlist-track/index.php +0 -0
  174. package/src/playlist-track/init.js +0 -0
  175. package/src/playlist-track/style.scss +0 -0
  176. package/src/post-excerpt/block.json +1 -3
  177. package/src/post-excerpt/deprecated.js +84 -0
  178. package/src/post-excerpt/edit.js +14 -39
  179. package/src/post-excerpt/index.js +2 -0
  180. package/src/private-apis.js +2 -0
  181. package/src/shortcode/block.json +2 -1
  182. package/src/site-logo/edit.js +1 -3
  183. package/src/tab/add-tab-toolbar-control.js +48 -23
  184. package/src/tab/remove-tab-toolbar-control.js +30 -10
  185. package/src/tabs/edit.js +133 -10
  186. package/src/tabs/index.js +12 -2
  187. package/src/tabs-menu/block.json +1 -6
  188. package/src/tabs-menu/edit.js +13 -214
  189. package/src/tabs-menu/editor.scss +7 -3
  190. package/src/tabs-menu/index.php +42 -27
  191. package/src/tabs-menu/save.js +0 -4
  192. package/src/tabs-menu-item/block.json +14 -11
  193. package/src/tabs-menu-item/controls.js +4 -167
  194. package/src/tabs-menu-item/edit.js +60 -69
  195. package/src/tabs-menu-item/index.php +11 -23
  196. package/src/tabs-menu-item/save.js +0 -1
  197. package/src/template-part/edit/index.js +5 -1
  198. package/src/utils/media-control.js +61 -21
  199. package/src/utils/media-control.scss +54 -18
@@ -1,7 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
-
5
1
  /**
6
2
  * WordPress dependencies
7
3
  */
@@ -4,21 +4,25 @@
4
4
  "apiVersion": 3,
5
5
  "name": "core/tabs-menu-item",
6
6
  "title": "Tab Menu Item",
7
- "description": "A single tab button in the tabs menu. Used as a template for styling all tab buttons.",
7
+ "description": "A single tab button in the tabs menu.",
8
8
  "version": "1.0.0",
9
9
  "category": "design",
10
10
  "textdomain": "default",
11
11
  "parent": [ "core/tabs-menu" ],
12
12
  "usesContext": [
13
- "core/tabs-menu-item-index",
14
- "core/tabs-menu-item-id",
15
- "core/tabs-menu-item-label",
16
- "core/tabs-menu-item-clientId",
17
13
  "core/tabs-list",
18
14
  "core/tabs-activeTabIndex",
19
- "core/tabs-editorActiveTabIndex"
15
+ "core/tabs-editorActiveTabIndex",
16
+ "core/tabs-menu-item-index",
17
+ "core/tabs-menu-item-id",
18
+ "core/tabs-menu-item-label"
20
19
  ],
21
- "attributes": {},
20
+ "attributes": {
21
+ "anchor": {
22
+ "type": "string",
23
+ "default": ""
24
+ }
25
+ },
22
26
  "supports": {
23
27
  "html": false,
24
28
  "reusable": false,
@@ -31,7 +35,6 @@
31
35
  "text": true
32
36
  }
33
37
  },
34
- "shadow": true,
35
38
  "typography": {
36
39
  "fontSize": true,
37
40
  "__experimentalFontFamily": true,
@@ -40,9 +43,6 @@
40
43
  "fontSize": true
41
44
  }
42
45
  },
43
- "layout": {
44
- "allowEditing": false
45
- },
46
46
  "spacing": {
47
47
  "padding": true,
48
48
  "__experimentalDefaultControls": {
@@ -54,6 +54,9 @@
54
54
  "color": true,
55
55
  "width": true,
56
56
  "style": true
57
+ },
58
+ "layout": {
59
+ "allowEditing": false
57
60
  }
58
61
  },
59
62
  "editorScript": "file:./index.js",
@@ -1,24 +1,7 @@
1
- /**
2
- * External dependencies
3
- */
4
- import clsx from 'clsx';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
9
- import { __, isRTL } from '@wordpress/i18n';
10
- import {
11
- BlockControls,
12
- store as blockEditorStore,
13
- } from '@wordpress/block-editor';
14
- import { ToolbarGroup, ToolbarItem, Button } from '@wordpress/components';
15
- import {
16
- chevronLeft,
17
- chevronRight,
18
- chevronUp,
19
- chevronDown,
20
- } from '@wordpress/icons';
21
- import { useDispatch, useSelect } from '@wordpress/data';
4
+ import { BlockControls } from '@wordpress/block-editor';
22
5
 
23
6
  /**
24
7
  * Internal dependencies
@@ -26,157 +9,11 @@ import { useDispatch, useSelect } from '@wordpress/data';
26
9
  import AddTabToolbarControl from '../tab/add-tab-toolbar-control';
27
10
  import RemoveTabToolbarControl from '../tab/remove-tab-toolbar-control';
28
11
 
29
- function TabBlockMover( {
30
- tabClientId,
31
- tabIndex,
32
- tabsCount,
33
- tabsMenuClientId,
34
- tabsClientId,
35
- } ) {
36
- const {
37
- moveBlocksUp,
38
- moveBlocksDown,
39
- updateBlockAttributes,
40
- __unstableMarkNextChangeAsNotPersistent,
41
- } = useDispatch( blockEditorStore );
42
-
43
- const { tabPanelClientId, orientation } = useSelect(
44
- ( select ) => {
45
- const { getBlockRootClientId, getBlockAttributes } =
46
- select( blockEditorStore );
47
- // Get orientation directly from the tabs-menu block's layout attribute.
48
- // This is more reliable than getBlockListSettings which is set asynchronously.
49
- const tabsMenuAttributes = tabsMenuClientId
50
- ? getBlockAttributes( tabsMenuClientId )
51
- : null;
52
- return {
53
- tabPanelClientId: getBlockRootClientId( tabClientId ),
54
- orientation:
55
- tabsMenuAttributes?.layout?.orientation || 'horizontal',
56
- };
57
- },
58
- [ tabClientId, tabsMenuClientId ]
59
- );
60
-
61
- const isFirst = tabIndex === 0;
62
- const isLast = tabIndex === tabsCount - 1;
63
- const isHorizontal = orientation === 'horizontal';
64
-
65
- // Icons and labels based on orientation (respects RTL for horizontal)
66
- let upIcon, downIcon, upLabel, downLabel;
67
- if ( isHorizontal ) {
68
- if ( isRTL() ) {
69
- upIcon = chevronRight;
70
- downIcon = chevronLeft;
71
- upLabel = __( 'Move tab right' );
72
- downLabel = __( 'Move tab left' );
73
- } else {
74
- upIcon = chevronLeft;
75
- downIcon = chevronRight;
76
- upLabel = __( 'Move tab left' );
77
- downLabel = __( 'Move tab right' );
78
- }
79
- } else {
80
- upIcon = chevronUp;
81
- downIcon = chevronDown;
82
- upLabel = __( 'Move tab up' );
83
- downLabel = __( 'Move tab down' );
84
- }
85
-
86
- // Handle moving tab and updating active index to follow the moved tab
87
- const handleMoveUp = () => {
88
- moveBlocksUp( [ tabClientId ], tabPanelClientId );
89
- // Update editorActiveTabIndex to follow the moved tab
90
- if ( tabsClientId ) {
91
- __unstableMarkNextChangeAsNotPersistent();
92
- updateBlockAttributes( tabsClientId, {
93
- editorActiveTabIndex: tabIndex - 1,
94
- } );
95
- }
96
- };
97
-
98
- const handleMoveDown = () => {
99
- moveBlocksDown( [ tabClientId ], tabPanelClientId );
100
- // Update editorActiveTabIndex to follow the moved tab
101
- if ( tabsClientId ) {
102
- __unstableMarkNextChangeAsNotPersistent();
103
- updateBlockAttributes( tabsClientId, {
104
- editorActiveTabIndex: tabIndex + 1,
105
- } );
106
- }
107
- };
108
-
109
- // Don't render if only one tab
110
- if ( tabsCount <= 1 ) {
111
- return null;
112
- }
113
-
114
- return (
115
- <BlockControls group="parent">
116
- <ToolbarGroup
117
- className={ clsx( 'block-editor-block-mover', {
118
- 'is-horizontal': isHorizontal,
119
- } ) }
120
- >
121
- <div className="block-editor-block-mover__move-button-container">
122
- <ToolbarItem>
123
- { ( itemProps ) => (
124
- <Button
125
- className={ clsx(
126
- 'block-editor-block-mover-button',
127
- 'is-up-button'
128
- ) }
129
- icon={ upIcon }
130
- label={ upLabel }
131
- disabled={ isFirst }
132
- accessibleWhenDisabled
133
- onClick={ handleMoveUp }
134
- __next40pxDefaultSize
135
- { ...itemProps }
136
- />
137
- ) }
138
- </ToolbarItem>
139
- <ToolbarItem>
140
- { ( itemProps ) => (
141
- <Button
142
- className={ clsx(
143
- 'block-editor-block-mover-button',
144
- 'is-down-button'
145
- ) }
146
- icon={ downIcon }
147
- label={ downLabel }
148
- disabled={ isLast }
149
- accessibleWhenDisabled
150
- onClick={ handleMoveDown }
151
- __next40pxDefaultSize
152
- { ...itemProps }
153
- />
154
- ) }
155
- </ToolbarItem>
156
- </div>
157
- </ToolbarGroup>
158
- </BlockControls>
159
- );
160
- }
161
-
162
- export default function Controls( {
163
- tabsClientId,
164
- tabClientId,
165
- tabIndex,
166
- tabsCount,
167
- tabsMenuClientId,
168
- } ) {
12
+ export default function Controls( { tabsClientId } ) {
169
13
  return (
170
- <>
171
- <TabBlockMover
172
- tabClientId={ tabClientId }
173
- tabIndex={ tabIndex }
174
- tabsCount={ tabsCount }
175
- tabsMenuClientId={ tabsMenuClientId }
176
- tabsClientId={ tabsClientId }
177
- />
14
+ <BlockControls>
178
15
  <AddTabToolbarControl tabsClientId={ tabsClientId } />
179
16
  <RemoveTabToolbarControl tabsClientId={ tabsClientId } />
180
- </>
17
+ </BlockControls>
181
18
  );
182
19
  }
@@ -13,132 +13,127 @@ import {
13
13
  RichText,
14
14
  } from '@wordpress/block-editor';
15
15
  import { useSelect, useDispatch } from '@wordpress/data';
16
- import { useCallback, useMemo } from '@wordpress/element';
16
+ import { useMemo, useCallback } from '@wordpress/element';
17
17
 
18
18
  /**
19
19
  * Internal dependencies
20
20
  */
21
- import slugFromLabel from '../tab/slug-from-label';
22
21
  import Controls from './controls';
23
22
 
24
- export default function Edit( {
23
+ const EMPTY_ARRAY = [];
24
+
25
+ function Edit( {
26
+ attributes,
25
27
  context,
26
28
  clientId,
27
29
  __unstableLayoutClassNames: layoutClassNames,
28
30
  } ) {
29
- // Context from tabs-menu (per-item context via BlockContextProvider)
30
- const tabIndex = context[ 'core/tabs-menu-item-index' ] ?? 0;
31
- const tabId = context[ 'core/tabs-menu-item-id' ] ?? '';
32
- const tabLabel = context[ 'core/tabs-menu-item-label' ] ?? '';
33
- const tabClientId = context[ 'core/tabs-menu-item-clientId' ] ?? '';
34
-
35
- // Context from parent tabs block, memoized to prevent unnecessary re-renders.
36
- const contextTabsList = context[ 'core/tabs-list' ];
37
- const tabsList = useMemo(
38
- () => contextTabsList || [],
39
- [ contextTabsList ]
40
- );
31
+ const tabsList = context[ 'core/tabs-list' ] || EMPTY_ARRAY;
41
32
  const activeTabIndex = context[ 'core/tabs-activeTabIndex' ] ?? 0;
42
33
  const editorActiveTabIndex = context[ 'core/tabs-editorActiveTabIndex' ];
43
34
 
44
- // Memoize effectiveActiveIndex to ensure it updates when context changes
45
35
  const effectiveActiveIndex = useMemo( () => {
46
36
  return editorActiveTabIndex ?? activeTabIndex;
47
37
  }, [ editorActiveTabIndex, activeTabIndex ] );
48
38
 
49
- const isActiveTab = tabIndex === effectiveActiveIndex;
50
-
51
- const { __unstableMarkNextChangeAsNotPersistent } =
52
- useDispatch( blockEditorStore );
53
-
54
- // Get parent tabs clientId for updating editorActiveTabIndex
55
- const { tabsClientId, tabsMenuClientId, selectedTabClientId } = useSelect(
39
+ const { menuItemIndex, tabsClientId, selectedTabClientId } = useSelect(
56
40
  ( select ) => {
57
41
  const {
42
+ getBlockOrder,
58
43
  getBlockRootClientId,
59
44
  getSelectedBlockClientIds,
60
45
  hasSelectedInnerBlock,
61
46
  } = select( blockEditorStore );
62
- // tabs-menu-item -> tabs-menu -> tabs
47
+
63
48
  const _tabsMenuClientId = getBlockRootClientId( clientId );
64
49
  const _tabsClientId = _tabsMenuClientId
65
50
  ? getBlockRootClientId( _tabsMenuClientId )
66
51
  : null;
67
52
 
68
- const selectedIds = getSelectedBlockClientIds();
53
+ const siblings = getBlockOrder( _tabsMenuClientId );
54
+ const _menuItemIndex = siblings.indexOf( clientId );
69
55
 
70
- // Find if any tab is selected
71
- let selectedTab = null;
56
+ // Find which tab panel block is currently selected.
57
+ const selectedIds = getSelectedBlockClientIds();
58
+ let _selectedTabClientId = null;
72
59
  for ( const tab of tabsList ) {
73
60
  if (
74
61
  selectedIds.includes( tab.clientId ) ||
75
62
  hasSelectedInnerBlock( tab.clientId, true )
76
63
  ) {
77
- selectedTab = tab.clientId;
64
+ _selectedTabClientId = tab.clientId;
78
65
  break;
79
66
  }
80
67
  }
81
68
 
82
69
  return {
70
+ menuItemIndex: _menuItemIndex,
83
71
  tabsClientId: _tabsClientId,
84
- tabsMenuClientId: _tabsMenuClientId,
85
- selectedTabClientId: selectedTab,
72
+ selectedTabClientId: _selectedTabClientId,
86
73
  };
87
74
  },
88
75
  [ clientId, tabsList ]
89
76
  );
90
77
 
91
- const isSelectedTab = tabClientId === selectedTabClientId;
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
+ {};
92
86
 
93
- // Update tab label in the tab block
94
- const { updateBlockAttributes } = useDispatch( blockEditorStore );
87
+ // tabListIndex is the tab's position in tabsList, used for active-state
88
+ // checks and click handling.
89
+ const tabListIndex = tab.index ?? menuItemIndex;
95
90
 
96
- const handleLabelChange = useCallback(
97
- ( newLabel ) => {
98
- if ( tabClientId ) {
99
- updateBlockAttributes( tabClientId, {
100
- label: newLabel,
101
- anchor: slugFromLabel( newLabel, tabIndex ),
102
- } );
103
- }
104
- },
105
- [ updateBlockAttributes, tabClientId, tabIndex ]
106
- );
91
+ const tabId = tab.id || `tab-${ menuItemIndex }`;
92
+ const tabClientId = tab.clientId || '';
93
+ const label = tab.label || '';
94
+
95
+ const isActive = tabListIndex === effectiveActiveIndex;
96
+ const isSelected = tabClientId === selectedTabClientId;
97
+
98
+ const { __unstableMarkNextChangeAsNotPersistent, updateBlockAttributes } =
99
+ useDispatch( blockEditorStore );
107
100
 
108
- // Update editor active tab index on parent tabs block when tab is clicked
109
101
  const handleTabClick = useCallback(
110
102
  ( event ) => {
111
103
  event.preventDefault();
112
-
113
- // Update the parent tabs block's editorActiveTabIndex (ephemeral, not persisted)
114
- if ( tabsClientId && tabIndex !== effectiveActiveIndex ) {
104
+ if ( tabsClientId && tabListIndex !== effectiveActiveIndex ) {
115
105
  __unstableMarkNextChangeAsNotPersistent();
116
106
  updateBlockAttributes( tabsClientId, {
117
- editorActiveTabIndex: tabIndex,
107
+ editorActiveTabIndex: tabListIndex,
118
108
  } );
119
109
  }
120
110
  },
121
111
  [
122
112
  tabsClientId,
123
- tabIndex,
113
+ tabListIndex,
124
114
  effectiveActiveIndex,
125
115
  updateBlockAttributes,
126
116
  __unstableMarkNextChangeAsNotPersistent,
127
117
  ]
128
118
  );
129
119
 
130
- const tabPanelId = tabId || `tab-${ tabIndex }`;
131
- const tabLabelId = `${ tabPanelId }--tab`;
120
+ const handleLabelChange = useCallback(
121
+ ( newLabel ) => {
122
+ if ( tabClientId ) {
123
+ updateBlockAttributes( tabClientId, { label: newLabel } );
124
+ }
125
+ },
126
+ [ tabClientId, updateBlockAttributes ]
127
+ );
132
128
 
133
- // Use blockProps for core style engine support
134
129
  const blockProps = useBlockProps( {
135
130
  className: clsx( layoutClassNames, {
136
- 'is-active': isActiveTab,
137
- 'is-selected': isSelectedTab,
131
+ 'is-active': isActive,
132
+ 'is-selected': isSelected,
138
133
  } ),
139
- 'aria-controls': tabPanelId,
140
- 'aria-selected': isActiveTab,
141
- id: tabLabelId,
134
+ 'aria-controls': tabId,
135
+ 'aria-selected': isActive,
136
+ id: `${ tabId }--tab`,
142
137
  role: 'tab',
143
138
  tabIndex: -1,
144
139
  onClick: handleTabClick,
@@ -146,26 +141,22 @@ export default function Edit( {
146
141
 
147
142
  return (
148
143
  <>
149
- <Controls
150
- tabsClientId={ tabsClientId }
151
- tabClientId={ tabClientId }
152
- tabIndex={ tabIndex }
153
- tabsCount={ tabsList.length }
154
- tabsMenuClientId={ tabsMenuClientId }
155
- />
156
- <div { ...blockProps }>
144
+ <Controls tabsClientId={ tabsClientId } />
145
+ <button { ...blockProps } type="button">
157
146
  <RichText
158
147
  tagName="span"
159
148
  withoutInteractiveFormatting
160
149
  placeholder={ sprintf(
161
150
  /* translators: %d is the tab index + 1 */
162
151
  __( 'Tab title %d' ),
163
- tabIndex + 1
152
+ menuItemIndex + 1
164
153
  ) }
165
- value={ tabLabel || '' }
154
+ value={ label }
166
155
  onChange={ handleLabelChange }
167
156
  />
168
- </div>
157
+ </button>
169
158
  </>
170
159
  );
171
160
  }
161
+
162
+ export default Edit;
@@ -8,18 +8,19 @@
8
8
  /**
9
9
  * Render callback for core/tabs-menu-item.
10
10
  *
11
- * Applies IAPI directives and tab-specific attributes to the saved content.
11
+ * Injects the tab label and IAPI directives into the saved button HTML.
12
+ * Per-item context (index, id, label) is provided by the parent tabs-menu
13
+ * render callback before this is called.
12
14
  *
13
15
  * @since 7.0.0
14
16
  *
15
17
  * @param array $attributes Block attributes.
16
- * @param string $content Block content.
18
+ * @param string $content Block content (styled button from save.js).
17
19
  * @param \WP_Block $block WP_Block instance.
18
20
  *
19
21
  * @return string Updated HTML.
20
22
  */
21
23
  function block_core_tabs_menu_item_render_callback( array $attributes, string $content, \WP_Block $block ): string {
22
- // Get tab-specific context
23
24
  $tab_index = $block->context['core/tabs-menu-item-index'] ?? 0;
24
25
  $tab_id = $block->context['core/tabs-menu-item-id'] ?? '';
25
26
  $tab_label = $block->context['core/tabs-menu-item-label'] ?? '';
@@ -28,42 +29,29 @@ function block_core_tabs_menu_item_render_callback( array $attributes, string $c
28
29
  $tab_id = 'tab-' . $tab_index;
29
30
  }
30
31
 
31
- // Process the content to add IAPI directives
32
+ // Add Interactivity API directives and tab-specific attributes to the button.
32
33
  $tag_processor = new WP_HTML_Tag_Processor( $content );
33
34
 
34
35
  if ( $tag_processor->next_tag() ) {
35
- // Remove hidden attribute and template class (from save.js)
36
- $tag_processor->remove_attribute( 'hidden' );
37
-
38
- // Set tab-specific attributes
39
36
  $tag_processor->set_attribute( 'id', 'tab__' . $tab_id );
40
37
  $tag_processor->set_attribute( 'aria-controls', $tab_id );
41
-
42
- // Add IAPI directives
43
38
  $tag_processor->set_attribute( 'data-wp-on--click', 'actions.handleTabClick' );
44
39
  $tag_processor->set_attribute( 'data-wp-on--keydown', 'actions.handleTabKeyDown' );
45
40
  $tag_processor->set_attribute( 'data-wp-bind--aria-selected', 'state.isActiveTab' );
46
41
  $tag_processor->set_attribute( 'data-wp-bind--tabindex', 'state.tabIndexAttribute' );
47
-
48
- // Add context for this specific tab item
49
42
  $tag_processor->set_attribute(
50
43
  'data-wp-context',
51
44
  wp_json_encode( array( 'tabIndex' => $tab_index ) )
52
45
  );
53
46
  }
54
47
 
55
- // Get updated HTML and inject the label
56
- $output = $tag_processor->get_updated_html();
57
-
58
- // The save.js outputs <button><span class="screen-reader-text">...</span></button>
59
- // Replace the button content with the actual tab label
60
- $output = preg_replace(
61
- '/(<button[^>]*>).*?(<\/button>)/s',
62
- '$1' . '<span>' . wp_kses_post( $tab_label ) . '</span>' . '$2',
63
- $output
48
+ // Inject the tab label into the button.
49
+ return preg_replace(
50
+ '/(<button\b[^>]*>).*?(<\/button>)/s',
51
+ '$1<span>' . wp_kses_post( $tab_label ) . '</span>$2',
52
+ $tag_processor->get_updated_html(),
53
+ 1
64
54
  );
65
-
66
- return $output;
67
55
  }
68
56
 
69
57
  /**
@@ -5,7 +5,6 @@ import { useBlockProps } from '@wordpress/block-editor';
5
5
 
6
6
  export default function save() {
7
7
  const blockProps = useBlockProps.save( {
8
- className: 'wp-block-tabs-menu-item__template',
9
8
  type: 'button',
10
9
  role: 'tab',
11
10
  } );
@@ -128,11 +128,13 @@ export default function TemplatePartEdit( {
128
128
  onNavigateToEntityRecord,
129
129
  title,
130
130
  canUserEdit,
131
+ canUserEditBlock,
131
132
  } = useSelect(
132
133
  ( select ) => {
133
134
  const { getEditedEntityRecord, hasFinishedResolution } =
134
135
  select( coreStore );
135
- const { getBlockCount, getSettings } = select( blockEditorStore );
136
+ const { getBlockCount, getSettings, canEditBlock } =
137
+ select( blockEditorStore );
136
138
 
137
139
  const getEntityArgs = [
138
140
  'postType',
@@ -170,6 +172,7 @@ export default function TemplatePartEdit( {
170
172
  getSettings().onNavigateToEntityRecord,
171
173
  title: entityRecord?.title,
172
174
  canUserEdit: !! _canUserEdit,
175
+ canUserEditBlock: canEditBlock( clientId ),
173
176
  };
174
177
  },
175
178
  [ templatePartId, attributes.area, clientId ]
@@ -284,6 +287,7 @@ export default function TemplatePartEdit( {
284
287
  // Only enable for single selection that matches the current block.
285
288
  // Ensures menu item doesn't render multiple times.
286
289
  if (
290
+ ! canUserEditBlock ||
287
291
  ! (
288
292
  selectedClientIds.length === 1 &&
289
293
  clientId === selectedClientIds[ 0 ]