@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
@@ -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
  } );
@@ -4,7 +4,7 @@
4
4
  import {
5
5
  Button,
6
6
  DropZone,
7
- FlexItem,
7
+ FlexBlock,
8
8
  Spinner,
9
9
  __experimentalItemGroup as ItemGroup,
10
10
  __experimentalHStack as HStack,
@@ -14,36 +14,62 @@ import {
14
14
  MediaReplaceFlow,
15
15
  store as blockEditorStore,
16
16
  } from '@wordpress/block-editor';
17
+ import { focus } from '@wordpress/dom';
18
+ import { useRef } from '@wordpress/element';
17
19
  import { __ } from '@wordpress/i18n';
18
20
  import { useSelect } from '@wordpress/data';
21
+ import { reset as resetIcon } from '@wordpress/icons';
22
+ import { getFilename } from '@wordpress/url';
23
+
24
+ /**
25
+ * Focuses the toggle button.
26
+ *
27
+ * @param {Object} containerRef - ref object containing current element
28
+ */
29
+ const focusToggleButton = ( containerRef ) => {
30
+ // Use requestAnimationFrame to ensure DOM updates are complete.
31
+ window.requestAnimationFrame( () => {
32
+ const [ toggleButton ] = focus.tabbable.find( containerRef?.current );
33
+ if ( ! toggleButton ) {
34
+ return;
35
+ }
36
+
37
+ toggleButton.focus();
38
+ } );
39
+ };
19
40
 
20
41
  /**
21
42
  * MediaControlPreview - Preview component showing media thumbnail and filename
22
43
  *
23
44
  * @param {Object} props
24
45
  * @param {string} props.url Media URL for thumbnail
25
- * @param {string} props.alt Alt text for image
26
46
  * @param {string} props.filename Filename to display
27
47
  * @param {Object} props.itemGroupProps Optional props to pass to ItemGroup
28
48
  * @param {string} props.className Optional className for Truncate
49
+ * @param {string} props.label Optional label for accessibility
29
50
  * @return {Element} Preview component
30
51
  */
31
52
  export function MediaControlPreview( {
32
53
  url,
33
- alt,
34
54
  filename,
35
55
  itemGroupProps,
36
56
  className,
57
+ label,
37
58
  } ) {
38
59
  return (
39
60
  <ItemGroup { ...itemGroupProps } as="span">
40
- <HStack justify="flex-start" as="span">
41
- <img src={ url } alt={ alt } />
42
- <FlexItem as="span">
61
+ <HStack justify="flex-start">
62
+ <span
63
+ className="block-library-utils__media-control__inspector-image-indicator"
64
+ style={ {
65
+ backgroundImage: url ? `url(${ url })` : undefined,
66
+ } }
67
+ />
68
+ <FlexBlock>
43
69
  <Truncate numberOfLines={ 1 } className={ className }>
44
- { filename }
70
+ { filename ?? label }
45
71
  </Truncate>
46
- </FlexItem>
72
+ </FlexBlock>
47
73
  </HStack>
48
74
  </ItemGroup>
49
75
  );
@@ -55,7 +81,6 @@ export function MediaControlPreview( {
55
81
  * @param {Object} props
56
82
  * @param {number} props.mediaId Media attachment ID
57
83
  * @param {string} props.mediaUrl Media URL
58
- * @param {string} props.alt Alt text for preview
59
84
  * @param {string} props.filename Filename to display
60
85
  * @param {Array} props.allowedTypes Allowed media types
61
86
  * @param {Function} props.onSelect Callback when media selected
@@ -69,7 +94,6 @@ export function MediaControlPreview( {
69
94
  export function MediaControl( {
70
95
  mediaId,
71
96
  mediaUrl,
72
- alt = '',
73
97
  filename,
74
98
  allowedTypes,
75
99
  onSelect,
@@ -77,7 +101,7 @@ export function MediaControl( {
77
101
  onError,
78
102
  onReset,
79
103
  isUploading = false,
80
- emptyLabel = __( 'Add media' ),
104
+ emptyLabel = __( 'Media' ),
81
105
  } ) {
82
106
  const { getSettings } = useSelect( blockEditorStore );
83
107
  const onFilesDrop = ( filesList ) => {
@@ -95,10 +119,15 @@ export function MediaControl( {
95
119
  multiple: false,
96
120
  } );
97
121
  };
122
+ const containerRef = useRef();
98
123
 
99
124
  return (
100
- <div className="block-library-utils__media-control">
125
+ <div
126
+ ref={ containerRef }
127
+ className="block-library-utils__media-control"
128
+ >
101
129
  <MediaReplaceFlow
130
+ className="block-library-utils__media-control__replace-flow"
102
131
  mediaId={ mediaId }
103
132
  mediaURL={ mediaUrl }
104
133
  allowedTypes={ allowedTypes }
@@ -106,15 +135,14 @@ export function MediaControl( {
106
135
  onSelectURL={ onSelectURL }
107
136
  onError={ onError }
108
137
  name={
109
- mediaUrl ? (
110
- <MediaControlPreview
111
- url={ mediaUrl }
112
- alt={ alt }
113
- filename={ filename }
114
- />
115
- ) : (
116
- emptyLabel
117
- )
138
+ <MediaControlPreview
139
+ url={ mediaUrl }
140
+ filename={ filename }
141
+ className="block-library-utils__media-control__inspector-media-replace-title"
142
+ label={
143
+ mediaUrl ? getFilename( filename ) : emptyLabel
144
+ }
145
+ />
118
146
  }
119
147
  renderToggle={ ( props ) => (
120
148
  <Button { ...props } __next40pxDefaultSize>
@@ -123,6 +151,18 @@ export function MediaControl( {
123
151
  ) }
124
152
  onReset={ onReset }
125
153
  />
154
+ { mediaUrl && onReset && (
155
+ <Button
156
+ label={ __( 'Reset' ) }
157
+ className="block-library-utils__media-control__reset"
158
+ size="small"
159
+ icon={ resetIcon }
160
+ onClick={ () => {
161
+ onReset();
162
+ focusToggleButton( containerRef );
163
+ } }
164
+ />
165
+ ) }
126
166
  <DropZone onFilesDrop={ onFilesDrop } />
127
167
  </div>
128
168
  );