@wordpress/block-library 9.41.1-next.v.202603102151.0 → 9.42.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 (140) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/cover/edit/cover-placeholder.cjs +7 -0
  3. package/build/cover/edit/cover-placeholder.cjs.map +2 -2
  4. package/build/html/modal.cjs +151 -229
  5. package/build/html/modal.cjs.map +2 -2
  6. package/build/image/edit.cjs +7 -0
  7. package/build/image/edit.cjs.map +2 -2
  8. package/build/media-text/media-container.cjs +6 -0
  9. package/build/media-text/media-container.cjs.map +2 -2
  10. package/build/navigation/edit/index.cjs +5 -4
  11. package/build/navigation/edit/index.cjs.map +2 -2
  12. package/build/navigation-link/shared/use-link-preview.cjs +29 -0
  13. package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
  14. package/build/nextpage/block.json +0 -1
  15. package/build/playlist-track/block.json +0 -0
  16. package/build/post-date/block.json +1 -3
  17. package/build/post-date/deprecated.cjs +82 -6
  18. package/build/post-date/deprecated.cjs.map +3 -3
  19. package/build/post-date/edit.cjs +49 -62
  20. package/build/post-date/edit.cjs.map +3 -3
  21. package/build/site-logo/edit.cjs +1 -3
  22. package/build/site-logo/edit.cjs.map +2 -2
  23. package/build/site-title/index.cjs +5 -1
  24. package/build/site-title/index.cjs.map +2 -2
  25. package/build/tab/add-tab-toolbar-control.cjs +22 -5
  26. package/build/tab/add-tab-toolbar-control.cjs.map +2 -2
  27. package/build/tab/remove-tab-toolbar-control.cjs +19 -1
  28. package/build/tab/remove-tab-toolbar-control.cjs.map +2 -2
  29. package/build/tabs/edit.cjs +85 -7
  30. package/build/tabs/edit.cjs.map +2 -2
  31. package/build/tabs/index.cjs +12 -2
  32. package/build/tabs/index.cjs.map +2 -2
  33. package/build/tabs-menu/block.json +1 -6
  34. package/build/tabs-menu/edit.cjs +11 -151
  35. package/build/tabs-menu/edit.cjs.map +3 -3
  36. package/build/tabs-menu/save.cjs.map +2 -2
  37. package/build/tabs-menu-item/block.json +14 -11
  38. package/build/tabs-menu-item/controls.cjs +2 -133
  39. package/build/tabs-menu-item/controls.cjs.map +3 -3
  40. package/build/tabs-menu-item/edit.cjs +44 -56
  41. package/build/tabs-menu-item/edit.cjs.map +3 -3
  42. package/build/tabs-menu-item/save.cjs +0 -1
  43. package/build/tabs-menu-item/save.cjs.map +2 -2
  44. package/build/utils/media-control.cjs +72 -29
  45. package/build/utils/media-control.cjs.map +3 -3
  46. package/build-module/cover/edit/cover-placeholder.mjs +7 -0
  47. package/build-module/cover/edit/cover-placeholder.mjs.map +2 -2
  48. package/build-module/html/modal.mjs +151 -229
  49. package/build-module/html/modal.mjs.map +2 -2
  50. package/build-module/image/edit.mjs +7 -0
  51. package/build-module/image/edit.mjs.map +2 -2
  52. package/build-module/media-text/media-container.mjs +7 -1
  53. package/build-module/media-text/media-container.mjs.map +2 -2
  54. package/build-module/navigation/edit/index.mjs +5 -4
  55. package/build-module/navigation/edit/index.mjs.map +2 -2
  56. package/build-module/navigation-link/shared/use-link-preview.mjs +28 -0
  57. package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
  58. package/build-module/nextpage/block.json +0 -1
  59. package/build-module/playlist-track/block.json +0 -0
  60. package/build-module/post-date/block.json +1 -3
  61. package/build-module/post-date/deprecated.mjs +82 -6
  62. package/build-module/post-date/deprecated.mjs.map +2 -2
  63. package/build-module/post-date/edit.mjs +49 -63
  64. package/build-module/post-date/edit.mjs.map +2 -2
  65. package/build-module/site-logo/edit.mjs +1 -3
  66. package/build-module/site-logo/edit.mjs.map +2 -2
  67. package/build-module/site-title/index.mjs +5 -1
  68. package/build-module/site-title/index.mjs.map +2 -2
  69. package/build-module/tab/add-tab-toolbar-control.mjs +22 -5
  70. package/build-module/tab/add-tab-toolbar-control.mjs.map +2 -2
  71. package/build-module/tab/remove-tab-toolbar-control.mjs +19 -1
  72. package/build-module/tab/remove-tab-toolbar-control.mjs.map +2 -2
  73. package/build-module/tabs/edit.mjs +87 -9
  74. package/build-module/tabs/edit.mjs.map +2 -2
  75. package/build-module/tabs/index.mjs +12 -2
  76. package/build-module/tabs/index.mjs.map +2 -2
  77. package/build-module/tabs-menu/block.json +1 -6
  78. package/build-module/tabs-menu/edit.mjs +13 -162
  79. package/build-module/tabs-menu/edit.mjs.map +2 -2
  80. package/build-module/tabs-menu/save.mjs.map +2 -2
  81. package/build-module/tabs-menu-item/block.json +14 -11
  82. package/build-module/tabs-menu-item/controls.mjs +4 -143
  83. package/build-module/tabs-menu-item/controls.mjs.map +2 -2
  84. package/build-module/tabs-menu-item/edit.mjs +45 -57
  85. package/build-module/tabs-menu-item/edit.mjs.map +3 -3
  86. package/build-module/tabs-menu-item/save.mjs +0 -1
  87. package/build-module/tabs-menu-item/save.mjs.map +2 -2
  88. package/build-module/utils/media-control.mjs +73 -30
  89. package/build-module/utils/media-control.mjs.map +2 -2
  90. package/build-style/editor-rtl.css +45 -11
  91. package/build-style/editor.css +45 -11
  92. package/build-style/navigation/style-rtl.css +4 -0
  93. package/build-style/navigation/style.css +4 -0
  94. package/build-style/navigation-overlay-close/style-rtl.css +3 -3
  95. package/build-style/navigation-overlay-close/style.css +3 -3
  96. package/build-style/style-rtl.css +7 -3
  97. package/build-style/style.css +7 -3
  98. package/build-style/tabs-menu/editor-rtl.css +5 -3
  99. package/build-style/tabs-menu/editor.css +5 -3
  100. package/package.json +38 -38
  101. package/src/cover/edit/cover-placeholder.js +8 -0
  102. package/src/html/modal.js +6 -77
  103. package/src/image/edit.js +8 -0
  104. package/src/media-text/media-container.js +8 -1
  105. package/src/navigation/edit/index.js +6 -4
  106. package/src/navigation/index.php +24 -17
  107. package/src/navigation/style.scss +10 -0
  108. package/src/navigation-link/index.php +9 -9
  109. package/src/navigation-link/shared/test/use-link-preview.test.js +149 -0
  110. package/src/navigation-link/shared/use-link-preview.js +43 -1
  111. package/src/navigation-overlay-close/style.scss +3 -3
  112. package/src/navigation-submenu/index.php +17 -11
  113. package/src/nextpage/block.json +0 -1
  114. package/src/playlist-track/block.json +0 -0
  115. package/src/playlist-track/edit.js +0 -0
  116. package/src/playlist-track/index.js +0 -0
  117. package/src/playlist-track/index.php +0 -0
  118. package/src/playlist-track/init.js +0 -0
  119. package/src/playlist-track/style.scss +0 -0
  120. package/src/post-date/block.json +1 -3
  121. package/src/post-date/deprecated.js +86 -6
  122. package/src/post-date/edit.js +65 -82
  123. package/src/site-logo/edit.js +1 -3
  124. package/src/site-title/index.js +5 -1
  125. package/src/tab/add-tab-toolbar-control.js +48 -23
  126. package/src/tab/remove-tab-toolbar-control.js +30 -10
  127. package/src/tabs/edit.js +133 -10
  128. package/src/tabs/index.js +12 -2
  129. package/src/tabs-menu/block.json +1 -6
  130. package/src/tabs-menu/edit.js +13 -214
  131. package/src/tabs-menu/editor.scss +7 -3
  132. package/src/tabs-menu/index.php +42 -27
  133. package/src/tabs-menu/save.js +0 -4
  134. package/src/tabs-menu-item/block.json +14 -11
  135. package/src/tabs-menu-item/controls.js +4 -167
  136. package/src/tabs-menu-item/edit.js +60 -69
  137. package/src/tabs-menu-item/index.php +11 -23
  138. package/src/tabs-menu-item/save.js +0 -1
  139. package/src/utils/media-control.js +61 -21
  140. package/src/utils/media-control.scss +54 -18
@@ -9,7 +9,6 @@
9
9
  "parent": [ "core/post-content" ],
10
10
  "textdomain": "default",
11
11
  "supports": {
12
- "anchor": true,
13
12
  "customClassName": false,
14
13
  "className": false,
15
14
  "html": false,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -11,9 +11,6 @@
11
11
  "type": "string",
12
12
  "role": "content"
13
13
  },
14
- "textAlign": {
15
- "type": "string"
16
- },
17
14
  "format": {
18
15
  "type": "string"
19
16
  },
@@ -46,6 +43,7 @@
46
43
  "typography": {
47
44
  "fontSize": true,
48
45
  "lineHeight": true,
46
+ "textAlign": true,
49
47
  "__experimentalFontFamily": true,
50
48
  "__experimentalFontWeight": true,
51
49
  "__experimentalFontStyle": true,
@@ -7,6 +7,84 @@ import clsx from 'clsx';
7
7
  * Internal dependencies
8
8
  */
9
9
  import migrateFontFamily from '../utils/migrate-font-family';
10
+ import migrateTextAlign from '../utils/migrate-text-align';
11
+
12
+ const v4 = {
13
+ attributes: {
14
+ datetime: {
15
+ type: 'string',
16
+ role: 'content',
17
+ },
18
+ textAlign: {
19
+ type: 'string',
20
+ },
21
+ format: {
22
+ type: 'string',
23
+ },
24
+ isLink: {
25
+ type: 'boolean',
26
+ default: false,
27
+ role: 'content',
28
+ },
29
+ },
30
+ supports: {
31
+ anchor: true,
32
+ html: false,
33
+ color: {
34
+ gradients: true,
35
+ link: true,
36
+ __experimentalDefaultControls: {
37
+ background: true,
38
+ text: true,
39
+ link: true,
40
+ },
41
+ },
42
+ spacing: {
43
+ margin: true,
44
+ padding: true,
45
+ },
46
+ typography: {
47
+ fontSize: true,
48
+ lineHeight: true,
49
+ __experimentalFontFamily: true,
50
+ __experimentalFontWeight: true,
51
+ __experimentalFontStyle: true,
52
+ __experimentalTextTransform: true,
53
+ __experimentalTextDecoration: true,
54
+ __experimentalLetterSpacing: true,
55
+ __experimentalDefaultControls: {
56
+ fontSize: true,
57
+ },
58
+ },
59
+ interactivity: {
60
+ clientNavigation: true,
61
+ },
62
+ __experimentalBorder: {
63
+ radius: true,
64
+ color: true,
65
+ width: true,
66
+ style: true,
67
+ __experimentalDefaultControls: {
68
+ radius: true,
69
+ color: true,
70
+ width: true,
71
+ style: true,
72
+ },
73
+ },
74
+ },
75
+ save() {
76
+ return null;
77
+ },
78
+ migrate: migrateTextAlign,
79
+ isEligible( attributes ) {
80
+ return (
81
+ !! attributes.textAlign ||
82
+ !! attributes.className?.match(
83
+ /\bhas-text-align-(left|center|right)\b/
84
+ )
85
+ );
86
+ },
87
+ };
10
88
 
11
89
  const v3 = {
12
90
  attributes: {
@@ -87,7 +165,7 @@ const v3 = {
87
165
  ...otherAttributes
88
166
  } ) {
89
167
  // Change the block bindings source argument name from "key" to "field".
90
- return {
168
+ return migrateTextAlign( {
91
169
  metadata: {
92
170
  bindings: {
93
171
  datetime: {
@@ -99,7 +177,7 @@ const v3 = {
99
177
  ...otherMetadata,
100
178
  },
101
179
  ...otherAttributes,
102
- };
180
+ } );
103
181
  },
104
182
  isEligible( attributes ) {
105
183
  return (
@@ -184,7 +262,7 @@ const v2 = {
184
262
  );
185
263
  }
186
264
 
187
- return {
265
+ return migrateTextAlign( {
188
266
  ...otherAttributes,
189
267
  className,
190
268
  metadata: {
@@ -196,7 +274,7 @@ const v2 = {
196
274
  },
197
275
  },
198
276
  },
199
- };
277
+ } );
200
278
  }
201
279
  },
202
280
  isEligible( attributes ) {
@@ -240,7 +318,9 @@ const v1 = {
240
318
  save() {
241
319
  return null;
242
320
  },
243
- migrate: migrateFontFamily,
321
+ migrate( attributes ) {
322
+ return migrateTextAlign( migrateFontFamily( attributes ) );
323
+ },
244
324
  isEligible( { style } ) {
245
325
  return style?.typography?.fontFamily;
246
326
  },
@@ -254,4 +334,4 @@ const v1 = {
254
334
  *
255
335
  * See block-deprecation.md
256
336
  */
257
- export default [ v3, v2, v1 ];
337
+ export default [ v4, v3, v2, v1 ];
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import clsx from 'clsx';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -14,7 +9,6 @@ import {
14
9
  getSettings as getDateSettings,
15
10
  } from '@wordpress/date';
16
11
  import {
17
- AlignmentControl,
18
12
  BlockControls,
19
13
  InspectorControls,
20
14
  store as blockEditorStore,
@@ -41,19 +35,18 @@ import { store as blocksStore } from '@wordpress/blocks';
41
35
  * Internal dependencies
42
36
  */
43
37
  import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
38
+ import useDeprecatedTextAlign from '../utils/deprecated-text-align-attributes';
44
39
 
45
- export default function PostDateEdit( {
46
- attributes,
47
- context: { postType: postTypeSlug, queryId },
48
- setAttributes,
49
- name,
50
- } ) {
51
- const { datetime, textAlign, format, isLink } = attributes;
52
- const blockProps = useBlockProps( {
53
- className: clsx( {
54
- [ `has-text-align-${ textAlign }` ]: textAlign,
55
- } ),
56
- } );
40
+ export default function PostDateEdit( props ) {
41
+ const {
42
+ attributes,
43
+ context: { postType: postTypeSlug, queryId },
44
+ setAttributes,
45
+ name,
46
+ } = props;
47
+ useDeprecatedTextAlign( props );
48
+ const { datetime, format, isLink } = attributes;
49
+ const blockProps = useBlockProps();
57
50
  const dropdownMenuProps = useToolsPanelDropdownMenuProps();
58
51
 
59
52
  // Use internal state instead of a ref to make sure that the component
@@ -126,71 +119,61 @@ export default function PostDateEdit( {
126
119
  }
127
120
  return (
128
121
  <>
129
- { ( blockEditingMode === 'default' ||
130
- ! isDescendentOfQueryLoop ) && (
131
- <BlockControls group="block">
132
- <AlignmentControl
133
- value={ textAlign }
134
- onChange={ ( nextAlign ) => {
135
- setAttributes( { textAlign: nextAlign } );
136
- } }
137
- />
138
-
139
- { activeBlockVariationName !== 'post-date-modified' &&
140
- ( ! isDescendentOfQueryLoop ||
141
- ! activeBlockVariationName ) && (
142
- <ToolbarGroup>
143
- <Dropdown
144
- popoverProps={ popoverProps }
145
- renderContent={ ( { onClose } ) => (
146
- <PublishDateTimePicker
147
- title={
148
- activeBlockVariationName ===
149
- 'post-date'
150
- ? __( 'Publish Date' )
151
- : __( 'Date' )
152
- }
153
- currentDate={ datetime }
154
- onChange={ ( newDatetime ) =>
155
- setAttributes( {
156
- datetime: newDatetime,
157
- } )
158
- }
159
- is12Hour={ is12HourFormat(
160
- siteTimeFormat
161
- ) }
162
- onClose={ onClose }
163
- dateOrder={
164
- /* translators: Order of day, month, and year. Available formats are 'dmy', 'mdy', and 'ymd'. */
165
- _x( 'dmy', 'date order' )
166
- }
122
+ { ( blockEditingMode === 'default' || ! isDescendentOfQueryLoop ) &&
123
+ activeBlockVariationName !== 'post-date-modified' &&
124
+ ( ! isDescendentOfQueryLoop || ! activeBlockVariationName ) && (
125
+ <BlockControls group="block">
126
+ <ToolbarGroup>
127
+ <Dropdown
128
+ popoverProps={ popoverProps }
129
+ renderContent={ ( { onClose } ) => (
130
+ <PublishDateTimePicker
131
+ title={
132
+ activeBlockVariationName ===
133
+ 'post-date'
134
+ ? __( 'Publish Date' )
135
+ : __( 'Date' )
136
+ }
137
+ currentDate={ datetime }
138
+ onChange={ ( newDatetime ) =>
139
+ setAttributes( {
140
+ datetime: newDatetime,
141
+ } )
142
+ }
143
+ is12Hour={ is12HourFormat(
144
+ siteTimeFormat
145
+ ) }
146
+ onClose={ onClose }
147
+ dateOrder={
148
+ /* translators: Order of day, month, and year. Available formats are 'dmy', 'mdy', and 'ymd'. */
149
+ _x( 'dmy', 'date order' )
150
+ }
151
+ />
152
+ ) }
153
+ renderToggle={ ( { isOpen, onToggle } ) => {
154
+ const openOnArrowDown = ( event ) => {
155
+ if (
156
+ ! isOpen &&
157
+ event.keyCode === DOWN
158
+ ) {
159
+ event.preventDefault();
160
+ onToggle();
161
+ }
162
+ };
163
+ return (
164
+ <ToolbarButton
165
+ aria-expanded={ isOpen }
166
+ icon={ pencil }
167
+ title={ __( 'Change Date' ) }
168
+ onClick={ onToggle }
169
+ onKeyDown={ openOnArrowDown }
167
170
  />
168
- ) }
169
- renderToggle={ ( { isOpen, onToggle } ) => {
170
- const openOnArrowDown = ( event ) => {
171
- if (
172
- ! isOpen &&
173
- event.keyCode === DOWN
174
- ) {
175
- event.preventDefault();
176
- onToggle();
177
- }
178
- };
179
- return (
180
- <ToolbarButton
181
- aria-expanded={ isOpen }
182
- icon={ pencil }
183
- title={ __( 'Change Date' ) }
184
- onClick={ onToggle }
185
- onKeyDown={ openOnArrowDown }
186
- />
187
- );
188
- } }
189
- />
190
- </ToolbarGroup>
191
- ) }
192
- </BlockControls>
193
- ) }
171
+ );
172
+ } }
173
+ />
174
+ </ToolbarGroup>
175
+ </BlockControls>
176
+ ) }
194
177
 
195
178
  <InspectorControls>
196
179
  <ToolsPanel
@@ -620,7 +620,6 @@ export default function LogoEdit( {
620
620
  >
621
621
  <MediaControlPreview
622
622
  url={ mediaItemData?.source_url }
623
- alt={ mediaItemData?.alt_text }
624
623
  filename={
625
624
  mediaItemData?.media_details?.sizes?.full
626
625
  ?.file || mediaItemData?.slug
@@ -642,7 +641,6 @@ export default function LogoEdit( {
642
641
  <MediaControl
643
642
  mediaId={ siteLogoId }
644
643
  mediaUrl={ logoUrl }
645
- alt={ mediaItemData?.alt_text }
646
644
  filename={
647
645
  mediaItemData?.media_details?.sizes?.full
648
646
  ?.file || mediaItemData?.slug
@@ -652,7 +650,7 @@ export default function LogoEdit( {
652
650
  onError={ onUploadError }
653
651
  onReset={ onRemoveLogo }
654
652
  isUploading={ !! temporaryURL }
655
- emptyLabel={ __( 'Choose logo' ) }
653
+ emptyLabel={ __( 'Logo' ) }
656
654
  />
657
655
  </ToolsPanelItem>
658
656
  ) }
@@ -20,7 +20,11 @@ export const settings = {
20
20
  example: {
21
21
  viewportWidth: 350,
22
22
  attributes: {
23
- textAlign: 'center',
23
+ style: {
24
+ typography: {
25
+ textAlign: 'center',
26
+ },
27
+ },
24
28
  },
25
29
  },
26
30
  edit,
@@ -12,7 +12,8 @@ import { useDispatch, useSelect } from '@wordpress/data';
12
12
 
13
13
  /**
14
14
  * "Add tab" button in the block toolbar for the tab block.
15
- * Inserts new tabs into the tab-panel block.
15
+ * Inserts a new core/tab into the tab-panel and a new core/tabs-menu-item
16
+ * into the tabs-menu, keeping both in sync.
16
17
  *
17
18
  * @param {Object} props
18
19
  * @param {string} props.tabsClientId The client ID of the parent tabs block.
@@ -21,39 +22,63 @@ import { useDispatch, useSelect } from '@wordpress/data';
21
22
  export default function AddTabToolbarControl( { tabsClientId } ) {
22
23
  const { insertBlock } = useDispatch( blockEditorStore );
23
24
 
24
- // Find the tab-panel block within the tabs block
25
- const { tabPanelClientId, nextTabIndex } = useSelect(
26
- ( select ) => {
27
- if ( ! tabsClientId ) {
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
+ );
28
43
  return {
29
- tabPanelClientId: null,
30
- nextTabIndex: 0,
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 ),
31
50
  };
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
- return {
39
- tabPanelClientId: tabPanel?.clientId || null,
40
- nextTabIndex: ( tabPanel?.innerBlocks.length || 0 ) + 1,
41
- };
42
- },
43
- [ tabsClientId ]
44
- );
51
+ },
52
+ [ tabsClientId ]
53
+ );
45
54
 
46
55
  const addTab = () => {
47
56
  if ( ! tabPanelClientId ) {
48
57
  return;
49
58
  }
59
+
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
+
50
68
  const newTabBlock = createBlock( 'core/tab', {
51
- anchor: 'tab-' + nextTabIndex,
69
+ anchor: `tab-${ tabNumber }`,
52
70
  /* translators: %d: tab number */
53
- label: sprintf( __( 'Tab %d' ), nextTabIndex ),
71
+ label: sprintf( __( 'Tab %d' ), tabNumber ),
54
72
  } );
55
73
  insertBlock( newTabBlock, undefined, tabPanelClientId );
56
- // @TODO: Possible select and focus the tabs-menu-item active tab RichText editor?
74
+
75
+ // Insert a corresponding menu item into the tabs-menu.
76
+ if ( tabsMenuClientId ) {
77
+ const newMenuItemBlock = createBlock( 'core/tabs-menu-item', {
78
+ anchor: `tab-${ tabNumber }-button`,
79
+ } );
80
+ insertBlock( newMenuItemBlock, undefined, tabsMenuClientId );
81
+ }
57
82
  };
58
83
 
59
84
  return (
@@ -11,7 +11,8 @@ import { useDispatch, useSelect } from '@wordpress/data';
11
11
 
12
12
  /**
13
13
  * "Remove Tab" button in the block toolbar for the tab block.
14
- * Removes the currently active tab from the tab-panel block.
14
+ * Removes the currently active core/tab and its corresponding
15
+ * core/tabs-menu-item, keeping both in sync.
15
16
  *
16
17
  * @param {Object} props
17
18
  * @param {string} props.tabsClientId The client ID of the parent tabs block.
@@ -25,12 +26,17 @@ export default function RemoveTabToolbarControl( { tabsClientId } ) {
25
26
  __unstableMarkNextChangeAsNotPersistent,
26
27
  } = useDispatch( blockEditorStore );
27
28
 
28
- // Find the tab-panel block, active tab, and tab count within the tabs block
29
- const { activeTabClientId, tabCount, editorActiveTabIndex } = useSelect(
29
+ const {
30
+ activeTabClientId,
31
+ activeMenuItemClientId,
32
+ tabCount,
33
+ editorActiveTabIndex,
34
+ } = useSelect(
30
35
  ( select ) => {
31
36
  if ( ! tabsClientId ) {
32
37
  return {
33
38
  activeTabClientId: null,
39
+ activeMenuItemClientId: null,
34
40
  tabCount: 0,
35
41
  editorActiveTabIndex: 0,
36
42
  };
@@ -46,10 +52,24 @@ export default function RemoveTabToolbarControl( { tabsClientId } ) {
46
52
  const tabPanel = innerBlocks.find(
47
53
  ( block ) => block.name === 'core/tab-panel'
48
54
  );
55
+ const tabsMenu = innerBlocks.find(
56
+ ( block ) => block.name === 'core/tabs-menu'
57
+ );
49
58
  const tabs = tabPanel?.innerBlocks || [];
59
+ const menuItems = tabsMenu?.innerBlocks || [];
50
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 ];
51
70
  return {
52
71
  activeTabClientId: activeTab?.clientId || null,
72
+ activeMenuItemClientId: activeMenuItem?.clientId || null,
53
73
  tabCount: tabs.length,
54
74
  editorActiveTabIndex: activeIndex,
55
75
  };
@@ -62,28 +82,28 @@ export default function RemoveTabToolbarControl( { tabsClientId } ) {
62
82
  return;
63
83
  }
64
84
 
65
- // Calculate new active index after removal
85
+ // Calculate new active index after removal.
66
86
  const newActiveIndex =
67
87
  editorActiveTabIndex >= tabCount - 1
68
- ? tabCount - 2 // If removing last tab, select the previous one
69
- : editorActiveTabIndex; // Otherwise keep the same index (next tab shifts into position)
88
+ ? tabCount - 2
89
+ : editorActiveTabIndex;
70
90
 
71
- // Update the active tab index before removing
72
91
  __unstableMarkNextChangeAsNotPersistent();
73
92
  updateBlockAttributes( tabsClientId, {
74
93
  editorActiveTabIndex: newActiveIndex,
75
94
  } );
76
95
 
77
- // Remove the tab
96
+ // Remove the tab content block and the corresponding menu item.
78
97
  removeBlock( activeTabClientId, false );
98
+ if ( activeMenuItemClientId ) {
99
+ removeBlock( activeMenuItemClientId, false );
100
+ }
79
101
 
80
- // Select the tabs block after removal
81
102
  if ( tabsClientId ) {
82
103
  selectBlock( tabsClientId );
83
104
  }
84
105
  };
85
106
 
86
- // Don't show the button if there's only one tab or no active tab
87
107
  const isDisabled = tabCount <= 1 || ! activeTabClientId;
88
108
 
89
109
  return (