Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions packages/block-library/src/tab-list/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
__experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles,
} from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect, useRef } from '@wordpress/element';
import { useEffect, useMemo, useRef } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -29,40 +29,48 @@ const EMPTY_ARRAY = [];
function Edit( {
attributes,
clientId,
context,
__unstableLayoutClassNames: layoutClassNames,
} ) {
const tabsList = context[ 'core/tabs-list' ] || EMPTY_ARRAY;

const colorProps = useColorProps( attributes );
const borderProps = useBorderProps( attributes );
const spacingProps = getSpacingClassesAndStyles( attributes );

const { tabsClientId, editorActiveTabIndex, activeTabIndex } = useSelect(
( select ) => {
const { getBlockRootClientId, getBlockAttributes } =
select( blockEditorStore );

const _tabsClientId = getBlockRootClientId( clientId );
const tabsAttributes = _tabsClientId
? getBlockAttributes( _tabsClientId )
: {};

return {
tabsClientId: _tabsClientId,
editorActiveTabIndex: tabsAttributes?.editorActiveTabIndex,
activeTabIndex: tabsAttributes?.activeTabIndex ?? 0,
};
},
[ clientId ]
);
const { tabsClientId, tabPanels, editorActiveTabIndex, activeTabIndex } =
useSelect(
( select ) => {
const { getBlockRootClientId, getBlockAttributes, getBlocks } =
select( blockEditorStore );

const rootClientId = getBlockRootClientId( clientId );
const tabsAttributes = getBlockAttributes( rootClientId );
const tabPanelsBlock = getBlocks( rootClientId )?.find(
( block ) => block.name === 'core/tab-panels'
);

return {
tabsClientId: rootClientId,
tabPanels: tabPanelsBlock?.innerBlocks ?? EMPTY_ARRAY,
editorActiveTabIndex: tabsAttributes?.editorActiveTabIndex,
activeTabIndex: tabsAttributes?.activeTabIndex ?? 0,
};
},
[ clientId ]
);
const { isBlockSelected, hasSelectedInnerBlock } =
useSelect( blockEditorStore );
const { updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
const { insertTab, removeTab } = useTabActions( tabsClientId );

const effectiveActiveIndex = editorActiveTabIndex ?? activeTabIndex;
const tabsList = useMemo(
() =>
tabPanels.map( ( tab ) => ( {
label: tab.attributes.label || '',
clientId: tab.clientId,
} ) ),
[ tabPanels ]
);
Comment on lines +66 to +73

@Mamaduka Mamaduka Jul 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deriving this value within useSelect produced a warning. Fixed via 0c72520.

Screenshot

Image


function selectTabPanel( tabIndex ) {
if ( tabsClientId && tabIndex !== effectiveActiveIndex ) {
Expand Down
2 changes: 0 additions & 2 deletions packages/block-library/src/tab-panel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ _Defined via the [`usesContext` and `providesContext`](https://developer.wordpre

**Uses context:**

- `core/tabs-activeTabIndex`
- `core/tabs-editorActiveTabIndex`
- `core/tabs-id`

**Provides context:**
Expand Down
6 changes: 1 addition & 5 deletions packages/block-library/src/tab-panel/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@
}
},
"parent": [ "core/tab-panels" ],
"usesContext": [
"core/tabs-activeTabIndex",
"core/tabs-editorActiveTabIndex",
"core/tabs-id"
],
"usesContext": [ "core/tabs-id" ],
"supports": {
"anchor": true,
"html": false,
Expand Down
43 changes: 19 additions & 24 deletions packages/block-library/src/tab-panel/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { useMemo, useEffect } from '@wordpress/element';
import { useEffect } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -24,25 +24,30 @@ const TEMPLATE = [
],
];

export default function Edit( { clientId, context, isSelected } ) {
// Consume tab indices from context
const activeTabIndex = context[ 'core/tabs-activeTabIndex' ];
const editorActiveTabIndex = context[ 'core/tabs-editorActiveTabIndex' ];
const effectiveActiveIndex = editorActiveTabIndex ?? activeTabIndex;

const { blockIndex, hasInnerBlocksSelected, tabsClientId } = useSelect(
export default function Edit( { clientId, isSelected } ) {
const {
activeTabIndex,
editorActiveTabIndex,
blockIndex,
hasInnerBlocksSelected,
tabsClientId,
} = useSelect(
( select ) => {
const {
getBlockRootClientId,
getBlockIndex,
hasSelectedInnerBlock,
getBlockAttributes,
} = select( blockEditorStore );

// Get the tab-panel parent first
const tabPanelsClientId = getBlockRootClientId( clientId );
// Then get the tabs parent
const _tabsClientId = getBlockRootClientId( tabPanelsClientId );

// Read the active tab indices directly from the tabs block.
const tabsAttributes = getBlockAttributes( _tabsClientId ) ?? {};

// Get data about this instance of core/tab.
const _blockIndex = getBlockIndex( clientId );
const _hasInnerBlocksSelected = hasSelectedInnerBlock(
Expand All @@ -51,6 +56,8 @@ export default function Edit( { clientId, context, isSelected } ) {
);

return {
activeTabIndex: tabsAttributes.activeTabIndex,
editorActiveTabIndex: tabsAttributes.editorActiveTabIndex,
blockIndex: _blockIndex,
hasInnerBlocksSelected: _hasInnerBlocksSelected,
tabsClientId: _tabsClientId,
Expand All @@ -59,6 +66,8 @@ export default function Edit( { clientId, context, isSelected } ) {
[ clientId ]
);

const effectiveActiveIndex = editorActiveTabIndex ?? activeTabIndex;

const { updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );

Expand Down Expand Up @@ -93,22 +102,8 @@ export default function Edit( { clientId, context, isSelected } ) {
// Determine if this is the default tab (for the "Default Tab" toggle in controls)
const isDefaultTab = activeTabIndex === blockIndex;

/**
* This hook determines if the current tab panel should be visible.
* This is true if it is the editor active tab, or if it is selected directly.
*/
const isSelectedTab = useMemo( () => {
// Show if this tab is directly selected or has selected inner blocks
if ( isSelected || hasInnerBlocksSelected ) {
return true;
}
// Always show the active tab (at effectiveActiveIndex) regardless of other selection state.
// This ensures the tab panel remains visible when editing labels in tab-list.
if ( isActiveTab ) {
return true;
}
return false;
}, [ isSelected, hasInnerBlocksSelected, isActiveTab ] );
// Visible when selected, containing the selection, or the active tab.
const isSelectedTab = isSelected || hasInnerBlocksSelected || isActiveTab;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small cleanup: isSelectedTab is now a plain derived boolean instead of a useMemo, which was memoizing a zero-cost expression.


const blockProps = useBlockProps( {
hidden: ! isSelectedTab,
Expand Down
5 changes: 0 additions & 5 deletions packages/block-library/src/tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ _Defined via the [`usesContext` and `providesContext`](https://developer.wordpre
- `core/tabs-list`
- `core/tabs-id`

**Provides context:**

- `core/tabs-activeTabIndex` → attribute `activeTabIndex`
- `core/tabs-editorActiveTabIndex` → attribute `editorActiveTabIndex`

## Block Markup

This is a [**hybrid block**](https://developer.wordpress.org/block-editor/getting-started/fundamentals/static-dynamic-rendering/). It saves static markup that the server may enhance during rendering.
Expand Down
4 changes: 0 additions & 4 deletions packages/block-library/src/tabs/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@
"__experimentalFontFamily": true
}
},
"providesContext": {
"core/tabs-activeTabIndex": "activeTabIndex",
"core/tabs-editorActiveTabIndex": "editorActiveTabIndex"
},
"usesContext": [ "core/tabs-list", "core/tabs-id" ],
"style": "wp-block-tabs",
"viewScriptModule": "@wordpress/block-library/tabs/view"
Expand Down
71 changes: 7 additions & 64 deletions packages/block-library/src/tabs/edit.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
/**
* WordPress dependencies
*/
import {
useBlockProps,
useInnerBlocksProps,
BlockContextProvider,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import TabToolbarControls from './tab-toolbar-controls';
import useTabListItemsSync from './use-tab-list-items-sync';

const EMPTY_ARRAY = [];

/**
* Only the two structural child blocks are specified here — without inner
* block entries for core/tab-list or core/tab-panels.
Expand All @@ -32,54 +23,8 @@ const EMPTY_ARRAY = [];
*/
const TABS_TEMPLATE = [ [ 'core/tab-list' ], [ 'core/tab-panels' ] ];

function Edit( { clientId, attributes } ) {
const { anchor, activeTabIndex, editorActiveTabIndex } = attributes;

const { tabPanels, tabListClientId } = useSelect(
( select ) => {
const { getBlocks } = select( blockEditorStore );
const innerBlocks = getBlocks( clientId );

const tabPanelsBlock = innerBlocks.find(
( block ) => block.name === 'core/tab-panels'
);
const tabList = innerBlocks.find(
( block ) => block.name === 'core/tab-list'
);

return {
tabPanels: tabPanelsBlock?.innerBlocks ?? EMPTY_ARRAY,
tabListClientId: tabList?.clientId ?? null,
};
},
[ clientId ]
);

useTabListItemsSync( { tabPanels, tabListClientId } );

/**
* Memoize context value to prevent unnecessary re-renders.
*/
const contextValue = useMemo( () => {
/**
* Compute tabs list from innerblocks to provide via context.
* This traverses the tab-panels block to find all tab-panel blocks
* and extracts their label and anchor for the tab-list to consume.
*/
const tabList = tabPanels.map( ( tab, index ) => ( {
id: tab.attributes.anchor || `tab-${ index }`,
label: tab.attributes.label || '',
clientId: tab.clientId,
index,
} ) );

return {
'core/tabs-list': tabList,
'core/tabs-id': anchor,
'core/tabs-activeTabIndex': activeTabIndex,
'core/tabs-editorActiveTabIndex': editorActiveTabIndex,
};
}, [ tabPanels, anchor, activeTabIndex, editorActiveTabIndex ] );
function Edit( { clientId } ) {
useTabListItemsSync( clientId );

const blockProps = useBlockProps();

Expand All @@ -91,12 +36,10 @@ function Edit( { clientId, attributes } ) {
} );

return (
<BlockContextProvider value={ contextValue }>
<div { ...innerBlockProps }>
<TabToolbarControls tabsClientId={ clientId } />
{ innerBlockProps.children }
</div>
</BlockContextProvider>
<div { ...innerBlockProps }>
<TabToolbarControls tabsClientId={ clientId } />
{ innerBlockProps.children }
</div>
);
}

Expand Down
28 changes: 24 additions & 4 deletions packages/block-library/src/tabs/use-tab-list-items-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,38 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';

const EMPTY_ARRAY = [];

/**
* Keep the tab-list block's `tabs` attribute in sync with the tab-panel blocks.
*
* Whenever the list of core/tab-panel blocks changes (add, remove, reorder, or
* label edit), this hook updates the `tabs` attribute on the core/tab-list
* block so that save.js can render the correct buttons.
*
* @param {Object} props
* @param {Array} props.tabPanels Raw core/tab-panel block objects.
* @param {string|null} props.tabListClientId Client ID of the core/tab-list block.
* @param {string} tabsClientId Client ID of the core/tabs block.
*/
export default function useTabListItemsSync( { tabPanels, tabListClientId } ) {
export default function useTabListItemsSync( tabsClientId ) {
const { tabPanels, tabListClientId } = useSelect(
( select ) => {
const { getBlocks } = select( blockEditorStore );
const innerBlocks = getBlocks( tabsClientId );

const tabPanelsBlock = innerBlocks.find(
( block ) => block.name === 'core/tab-panels'
);
const tabList = innerBlocks.find(
( block ) => block.name === 'core/tab-list'
);

return {
tabPanels: tabPanelsBlock?.innerBlocks ?? EMPTY_ARRAY,
tabListClientId: tabList?.clientId ?? null,
};
},
[ tabsClientId ]
);

const { updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
const { getBlockAttributes } = useSelect( blockEditorStore );
Expand Down
Loading