From 865716d20560cf68e7b3dba022a79ddd7a3a2995 Mon Sep 17 00:00:00 2001 From: Marie Comet Date: Wed, 11 Feb 2026 11:46:17 +0100 Subject: [PATCH 1/5] relase 1.0.6 : allow to use nested tabs block --- .plugin-data | 2 +- CHANGELOG.md | 4 + blockparty-tabs.php | 4 +- package.json | 2 +- readme.txt | 4 + src/blockparty-tabs-nav-item/block.json | 2 +- src/blockparty-tabs-nav-item/edit.js | 2 + src/blockparty-tabs-nav/block.json | 2 +- src/blockparty-tabs-panel-item/block.json | 2 +- src/blockparty-tabs-panel-item/edit.js | 2 + src/blockparty-tabs-panels/block.json | 2 +- src/blockparty-tabs/SyncTabsActive.js | 104 ++++++++++++++++++++++ src/blockparty-tabs/TabsFocus.js | 44 --------- src/blockparty-tabs/block.json | 2 +- src/blockparty-tabs/edit.js | 2 - src/blockparty-tabs/script.js | 13 ++- 16 files changed, 131 insertions(+), 62 deletions(-) create mode 100644 src/blockparty-tabs/SyncTabsActive.js delete mode 100644 src/blockparty-tabs/TabsFocus.js diff --git a/.plugin-data b/.plugin-data index dca7deb..c4ee859 100644 --- a/.plugin-data +++ b/.plugin-data @@ -1,4 +1,4 @@ { - "version": "1.0.5", + "version": "1.0.6", "slug": "blockparty-tabs" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eeec90..fe7011a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.0.6 - 2026-02-11 + +- Update view and edit script to allow to use nested tabs block (tabs inside tab panel). + ## 1.0.5 - 2025-11-10 - Update block icons diff --git a/blockparty-tabs.php b/blockparty-tabs.php index 0b48737..fbcefff 100644 --- a/blockparty-tabs.php +++ b/blockparty-tabs.php @@ -4,7 +4,7 @@ * Description: Accessible Tabs block for WordPress gutenberg. * Requires at least: 6.2 * Requires PHP: 8.1 - * Version: 1.0.5 + * Version: 1.0.6 * Author: Be API Technical team * Author URI: https://beapi.fr * License: GPL-2.0-or-later @@ -14,7 +14,7 @@ namespace Blockparty\Tabs; -define( 'BLOCKPARTY_TABS_VERSION', '1.0.5' ); +define( 'BLOCKPARTY_TABS_VERSION', '1.0.6' ); define( 'BLOCKPARTY_TABS_URL', plugin_dir_url( __FILE__ ) ); define( 'BLOCKPARTY_TABS_DIR', plugin_dir_path( __FILE__ ) ); define( 'BLOCKPARTY_TABS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); diff --git a/package.json b/package.json index e1dfa1f..82bdcf0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockparty-tabs", - "version": "1.0.5", + "version": "1.0.6", "description": "Accessible tabs block for WordPress", "author": "Be API Technical team", "license": "GPL-2.0-or-later", diff --git a/readme.txt b/readme.txt index 843065a..c6148a1 100644 --- a/readme.txt +++ b/readme.txt @@ -31,6 +31,10 @@ directory take precedence. For example, `/assets/screenshot-1.png` would win ove == Changelog == += 1.0.6 = + +* Update view and edit script to allow to use nested tabs block (tabs inside tab panel). + = 1.0.5 = * Update block icons diff --git a/src/blockparty-tabs-nav-item/block.json b/src/blockparty-tabs-nav-item/block.json index 4e32968..dc0bbb8 100644 --- a/src/blockparty-tabs-nav-item/block.json +++ b/src/blockparty-tabs-nav-item/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 2, "name": "blockparty/tabs-nav-item", - "version": "1.0.5", + "version": "1.0.6", "title": "Tab", "category": "widgets", "description": "Accessible tabs block item", diff --git a/src/blockparty-tabs-nav-item/edit.js b/src/blockparty-tabs-nav-item/edit.js index 2dc11e9..e044d51 100644 --- a/src/blockparty-tabs-nav-item/edit.js +++ b/src/blockparty-tabs-nav-item/edit.js @@ -5,6 +5,7 @@ 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, @@ -47,6 +48,7 @@ export default function Edit( { return ( <> + + { + 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 ] + ); + + const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); + + // When this block is the active tab (closest to selection or selected), sync tabsActive. + useEffect( () => { + if ( ! selectedBlockClientId || ! tabsBlockId ) { + return; + } + const isActiveTab = + closestTabItemId === clientId || selectedBlockClientId === clientId; + if ( ! isActiveTab ) { + return; + } + updateBlockAttributes( tabsBlockId, { tabsActive: myIndex } ); + }, [ + selectedBlockClientId, + closestTabItemId, + clientId, + tabsBlockId, + myIndex, + ] ); + + return null; +} diff --git a/src/blockparty-tabs/TabsFocus.js b/src/blockparty-tabs/TabsFocus.js deleted file mode 100644 index ddd229d..0000000 --- a/src/blockparty-tabs/TabsFocus.js +++ /dev/null @@ -1,44 +0,0 @@ -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; - -const TabsFocus = ( { activeTab, setAttributes } ) => { - if ( activeTab >= 0 ) { - setAttributes( { tabsActive: activeTab } ); - } - return null; -}; - -export default compose( [ - withSelect( ( select, ownProps ) => { - const { - getBlockName, - getBlockIndex, - getSelectedBlockClientId, - getBlockParentsByBlockName, - hasSelectedInnerBlock, - } = select( 'core/block-editor' ); - if ( ! hasSelectedInnerBlock( ownProps.clientId, true ) ) { - return { - activeTab: -1, - setAttributes: ownProps.setAttributes, - }; - } - const selected = getSelectedBlockClientId(); - const name = getBlockName( selected ); - let currentIndex = getBlockIndex( selected ); - if ( - 'blockparty/tabs-panel-item' !== name && - 'blockparty/tabs-nav-item' !== name - ) { - const parents = getBlockParentsByBlockName( selected, [ - 'blockparty/tabs-nav-item', - 'blockparty/tabs-panel-item', - ] ); - currentIndex = getBlockIndex( parents[ 0 ] ); - } - return { - activeTab: currentIndex, - setAttributes: ownProps.setAttributes, - }; - } ), -] )( TabsFocus ); diff --git a/src/blockparty-tabs/block.json b/src/blockparty-tabs/block.json index b6e6f31..c5830d4 100644 --- a/src/blockparty-tabs/block.json +++ b/src/blockparty-tabs/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 2, "name": "blockparty/tabs", - "version": "1.0.5", + "version": "1.0.6", "title": "Tabs", "category": "widgets", "description": "Accessible tabs block", diff --git a/src/blockparty-tabs/edit.js b/src/blockparty-tabs/edit.js index fd56104..edea034 100644 --- a/src/blockparty-tabs/edit.js +++ b/src/blockparty-tabs/edit.js @@ -9,7 +9,6 @@ import { } from '@wordpress/block-editor'; import { PanelBody, PanelRow, TextControl } from '@wordpress/components'; import { select } from '@wordpress/data'; -import TabsFocus from './TabsFocus'; import { heading, justifyRight, @@ -119,7 +118,6 @@ export default function Edit( { attributes, setAttributes, clientId } ) { -
); diff --git a/src/blockparty-tabs/script.js b/src/blockparty-tabs/script.js index 38f8623..8d02185 100644 --- a/src/blockparty-tabs/script.js +++ b/src/blockparty-tabs/script.js @@ -18,18 +18,17 @@ class TabsAutomatic { this.firstTab = null; this.lastTab = null; - this.tabs = Array.from( - this.tablistNode.querySelectorAll( '[role=tab]' ) + this.tabnav = this.tablistNode.querySelector( + '.wp-block-blockparty-tabs-nav' ); + this.tabs = Array.from( this.tabnav.querySelectorAll( 'a[role=tab]' ) ); + this.tabpanelsNode = this.tabnav.nextElementSibling; + this.tabpanels = []; for ( let i = 0; i < this.tabs.length; i += 1 ) { const tab = this.tabs[ i ]; - const tabpanel = this.tablistNode - .closest( '.wp-block-blockparty-tabs' ) - .querySelectorAll( '.wp-block-blockparty-tabs-panels > *' )[ - i - ]; + const tabpanel = this.tabpanelsNode.children[ i ]; tab.tabIndex = -1; tab.setAttribute( 'aria-selected', 'false' ); From d8b4f3546206442a9d79736f99c4550b00807cf4 Mon Sep 17 00:00:00 2001 From: Marie Comet Date: Wed, 11 Feb 2026 12:01:47 +0100 Subject: [PATCH 2/5] fix jsdoc block and add missing useEffect depedencie --- src/blockparty-tabs/SyncTabsActive.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blockparty-tabs/SyncTabsActive.js b/src/blockparty-tabs/SyncTabsActive.js index d60101b..a0cbb5f 100644 --- a/src/blockparty-tabs/SyncTabsActive.js +++ b/src/blockparty-tabs/SyncTabsActive.js @@ -98,6 +98,7 @@ export default function SyncTabsActive( { clientId } ) { clientId, tabsBlockId, myIndex, + updateBlockAttributes, ] ); return null; From 129ed7e60e659f174f2c0928ec7fcdbb7dc30d01 Mon Sep 17 00:00:00 2001 From: Marie Comet Date: Wed, 11 Feb 2026 12:07:33 +0100 Subject: [PATCH 3/5] fix jsdoc block --- src/blockparty-tabs/SyncTabsActive.js | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/blockparty-tabs/SyncTabsActive.js b/src/blockparty-tabs/SyncTabsActive.js index a0cbb5f..bf4f7a8 100644 --- a/src/blockparty-tabs/SyncTabsActive.js +++ b/src/blockparty-tabs/SyncTabsActive.js @@ -1,20 +1,20 @@ /** - * Syncs the active tab state with the block selection in the editor. - * - * 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. - * - * @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. - */ +* Syncs the active tab state with the block selection in the editor. +* +* 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. +* +* @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'; From 88e9534ac8e2d72fd9be964dccf268a109226700 Mon Sep 17 00:00:00 2001 From: Marie Comet Date: Wed, 11 Feb 2026 13:40:22 +0100 Subject: [PATCH 4/5] fix jsdoc block --- src/blockparty-tabs/SyncTabsActive.js | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/blockparty-tabs/SyncTabsActive.js b/src/blockparty-tabs/SyncTabsActive.js index bf4f7a8..a0cbb5f 100644 --- a/src/blockparty-tabs/SyncTabsActive.js +++ b/src/blockparty-tabs/SyncTabsActive.js @@ -1,20 +1,20 @@ /** -* Syncs the active tab state with the block selection in the editor. -* -* 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. -* -* @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. -*/ + * Syncs the active tab state with the block selection in the editor. + * + * 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. + * + * @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'; From d50113f64b42b1b170c6d6c132eecb121eae8f35 Mon Sep 17 00:00:00 2001 From: Marie Comet Date: Wed, 11 Feb 2026 13:43:24 +0100 Subject: [PATCH 5/5] fix jsdoc block again' --- src/blockparty-tabs/SyncTabsActive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockparty-tabs/SyncTabsActive.js b/src/blockparty-tabs/SyncTabsActive.js index a0cbb5f..9785752 100644 --- a/src/blockparty-tabs/SyncTabsActive.js +++ b/src/blockparty-tabs/SyncTabsActive.js @@ -1,4 +1,4 @@ -/** + /** * Syncs the active tab state with the block selection in the editor. * * When this tab item (nav-item or panel-item) is the one selected or is the