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