Skip to content
Open
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
3 changes: 0 additions & 3 deletions src/blockparty-tabs-nav-item/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { getBlockType } from '@wordpress/blocks';
import { select } from '@wordpress/data';
import ComposeBlockControls from './ComposeBlockControls';
import getSynchedID from '../blockparty-tabs/GetSynchedID';
import SyncTabsActive from '../blockparty-tabs/SyncTabsActive';

export default function Edit( {
attributes,
setAttributes,
Expand Down Expand Up @@ -53,7 +51,6 @@ export default function Edit( {

return (
<>
<SyncTabsActive clientId={ clientId } />
<ComposeBlockControls
clientId={ clientId }
index={ index }
Expand Down
3 changes: 0 additions & 3 deletions src/blockparty-tabs-panel-item/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import classnames from 'classnames';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import { select } from '@wordpress/data';
import getSynchedID from '../blockparty-tabs/GetSynchedID';
import SyncTabsActive from '../blockparty-tabs/SyncTabsActive';

export default function Edit( {
setAttributes,
clientId,
Expand Down Expand Up @@ -47,7 +45,6 @@ export default function Edit( {

return (
<div { ...blockProps }>
<SyncTabsActive clientId={ clientId } />
<InnerBlocks
allowedBlocks={ allowedBlocks }
templateLock={ false }
Expand Down
164 changes: 84 additions & 80 deletions src/blockparty-tabs/SyncTabsActive.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,109 @@
/**
* Syncs the active tab state with the block selection in the editor.
* Syncs each blockparty/tabs block's tabsActive attribute with the editor selection.
*
* When this tab item (nav-item or panel-item) is the one selected or is the
* closest tab item to the current block selection, updates the parent
* blockparty/tabs block's tabsActive attribute to this item's index.
*
* This direct link (the clicked block updates its own tabs block) ensures
* correct behavior with nested tabs: only the tabs block that owns the
* selected tab is updated.
* Runs once per tabs block (not per tab item) for performance. When the selected
* block or its ancestors imply a tab item owned by this tabs block, updates
* tabsActive to that item's index. Nested tabs only update their own block.
*
* @since 1.0.6
*
* @param {Object} props Component props.
* @param {string} props.clientId This block's clientId (tabs-nav-item or tabs-panel-item).
* @return {null} Renders nothing.
*/

import { useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { useSelect, useDispatch, select } from '@wordpress/data';

/** Block names that represent a single tab (nav link or panel). */
const TAB_ITEM_NAMES = [
'blockparty/tabs-nav-item',
'blockparty/tabs-panel-item',
];

export default function SyncTabsActive( { clientId } ) {
const { selectedBlockClientId, closestTabItemId, tabsBlockId, myIndex } =
useSelect(
( select ) => {
const {
getBlockName,
getBlockIndex,
getSelectedBlockClientId,
getBlockParents,
getBlockRootClientId,
} = select( 'core/block-editor' );
const selected = getSelectedBlockClientId();
if ( ! selected ) {
return {
selectedBlockClientId: null,
closestTabItemId: null,
tabsBlockId: null,
myIndex: 0,
};
}
// Find the tab item closest to the selection: the selected block if it
// is a tab item, otherwise the first tab item in the ancestor chain.
let closest = null;
if (
TAB_ITEM_NAMES.indexOf( getBlockName( selected ) ) !== -1
) {
closest = selected;
} else {
const parents = getBlockParents( selected );
for ( let i = 0; i < parents.length; i += 1 ) {
if (
TAB_ITEM_NAMES.indexOf(
getBlockName( parents[ i ] )
) !== -1
) {
closest = parents[ i ];
break;
}
}
}
// Parent chain: this block → tabs-nav or tabs-panels → blockparty/tabs.
const directParent = getBlockRootClientId( clientId );
const tabsId = directParent
? getBlockRootClientId( directParent )
: null;
return {
selectedBlockClientId: selected,
closestTabItemId: closest,
tabsBlockId: tabsId,
myIndex: getBlockIndex( clientId ),
};
},
[ clientId ]
);
/**
* Finds the tab item (nav or panel) closest to the current selection.
*
* @param {Function} selectStore Bound select from useSelect.
* @return {string|null} Closest tab item clientId, or null.
*/
function getClosestTabItemClientId( selectStore ) {
const { getBlockName, getSelectedBlockClientId, getBlockParents } =
selectStore( 'core/block-editor' );
const selected = getSelectedBlockClientId();
if ( ! selected ) {
return null;
}
if ( TAB_ITEM_NAMES.indexOf( getBlockName( selected ) ) !== -1 ) {
return selected;
}
const parents = getBlockParents( selected );
for ( let i = 0; i < parents.length; i += 1 ) {
if ( TAB_ITEM_NAMES.indexOf( getBlockName( parents[ i ] ) ) !== -1 ) {
return parents[ i ];
}
}
return null;
}

/**
* Resolves the blockparty/tabs clientId that owns a tab item (same chain as legacy per-item sync).
*
* @param {Function} selectStore Bound select from useSelect.
* @param {string} tabItemId tabs-nav-item or tabs-panel-item clientId.
* @return {string|null} Owning blockparty/tabs clientId.
*/
function getOwningTabsBlockClientId( selectStore, tabItemId ) {
const { getBlockRootClientId } = selectStore( 'core/block-editor' );
const directParent = getBlockRootClientId( tabItemId );
return directParent ? getBlockRootClientId( directParent ) : null;
}

/**
* Subscribes to selection and syncs this tabs block's tabsActive when appropriate.
*
* @param {string} tabsBlockClientId This blockparty/tabs block clientId.
* @return {void}
*/
export function useSyncTabsActiveForTabsBlock( tabsBlockClientId ) {
const { shouldSync, targetTabsActive } = useSelect(
( selectStore ) => {
const closest = getClosestTabItemClientId( selectStore );
if ( ! closest ) {
return { shouldSync: false, targetTabsActive: 0 };
}
const owningTabsId = getOwningTabsBlockClientId(
selectStore,
closest
);
if ( owningTabsId !== tabsBlockClientId ) {
return { shouldSync: false, targetTabsActive: 0 };
}
const { getBlockIndex } = selectStore( 'core/block-editor' );
return {
shouldSync: true,
targetTabsActive: getBlockIndex( closest ),
};
},
[ tabsBlockClientId ]
);

const { updateBlockAttributes } = useDispatch( 'core/block-editor' );

// When this block is the active tab (closest to selection or selected), sync tabsActive.
useEffect( () => {
if ( ! selectedBlockClientId || ! tabsBlockId ) {
if ( ! shouldSync ) {
return;
}
const isActiveTab =
closestTabItemId === clientId || selectedBlockClientId === clientId;
if ( ! isActiveTab ) {
const current =
select( 'core/block-editor' ).getBlockAttributes(
tabsBlockClientId
)?.tabsActive;
if ( current === targetTabsActive ) {
return;
}
updateBlockAttributes( tabsBlockId, { tabsActive: myIndex } );
updateBlockAttributes( tabsBlockClientId, {
tabsActive: targetTabsActive,
} );
}, [
selectedBlockClientId,
closestTabItemId,
clientId,
tabsBlockId,
myIndex,
shouldSync,
targetTabsActive,
tabsBlockClientId,
updateBlockAttributes,
] );

return null;
}
2 changes: 2 additions & 0 deletions src/blockparty-tabs/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
justifySpaceBetween,
} from '@wordpress/icons';
import './editor.scss';
import { useSyncTabsActiveForTabsBlock } from './SyncTabsActive';

const DEFAULT_TABS_POSITIONS = [
{
Expand Down Expand Up @@ -60,6 +61,7 @@ const setTabsIndex = ( setAttributes, clientId ) => {
};

export default function Edit( { attributes, setAttributes, clientId } ) {
useSyncTabsActiveForTabsBlock( clientId );
setTabsIndex( setAttributes, clientId );
const { title, mode } = attributes;
const blockProps = useBlockProps( {
Expand Down
Loading