UNPKG

@wordpress/block-library

Version:
128 lines (112 loc) 3.69 kB
import { createBlock } from '@wordpress/blocks'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useDispatch, useRegistry } from '@wordpress/data'; /** * Provides callbacks to insert, remove and move tabs for a tabs block. * * The hook intentionally avoids subscribing to the store: all data is derived * lazily inside the callbacks via `registry.select`, so consumers don't * re-render when the tab structure changes. * * @param {string|null} tabsClientId The client ID of the parent tabs block. * @return {{ insertTab: Function, removeTab: Function, moveTab: Function }} Tab action callbacks. */ export default function useTabActions( tabsClientId ) { const registry = useRegistry(); const { insertBlock, removeBlock, moveBlocksToPosition, updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent, } = useDispatch( blockEditorStore ); // Derive the current tabs state at call time without subscribing. const getTabsState = () => { const { getBlocks, getBlockAttributes } = registry.select( blockEditorStore ); const tabsAttributes = tabsClientId ? getBlockAttributes( tabsClientId ) : undefined; const tabPanels = tabsClientId ? getBlocks( tabsClientId ).find( ( block ) => block.name === 'core/tab-panels' ) : undefined; return { tabPanelsClientId: tabPanels?.clientId ?? null, tabPanelBlocks: tabPanels?.innerBlocks ?? [], activeIndex: tabsAttributes?.editorActiveTabIndex ?? tabsAttributes?.activeTabIndex ?? 0, }; }; // Insert a new tab and make it active. Defaults to appending at the end. const insertTab = ( atIndex ) => { const { tabPanelsClientId, tabPanelBlocks } = getTabsState(); if ( ! tabPanelsClientId ) { return; } const newIndex = atIndex ?? tabPanelBlocks.length; insertBlock( createBlock( 'core/tab-panel' ), newIndex, tabPanelsClientId, false ); // Switch editor active tab to the new tab. __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes( tabsClientId, { editorActiveTabIndex: newIndex, } ); }; // Remove a tab and move the active tab to an adjacent one. Defaults to the // currently active tab. const removeTab = ( atIndex ) => { const { tabPanelBlocks, activeIndex } = getTabsState(); const tabCount = tabPanelBlocks.length; if ( tabCount <= 1 ) { return; } const removeIndex = atIndex ?? activeIndex; const target = tabPanelBlocks[ removeIndex ]; if ( ! target ) { return; } // Calculate new active index after removal. const newActiveIndex = removeIndex >= tabCount - 1 ? tabCount - 2 : removeIndex; // Switch editor to the adjacent tab and remove the current one. __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes( tabsClientId, { editorActiveTabIndex: newActiveIndex, } ); removeBlock( target.clientId, false ); }; // Move the active tab one position left or right by reordering the // underlying tab-panel. The tab-list labels follow their panel // automatically via useTabListItemsSync. const moveTab = ( direction ) => { const { tabPanelsClientId, tabPanelBlocks, activeIndex } = getTabsState(); if ( ! tabPanelsClientId ) { return; } const toIndex = activeIndex + direction; const target = tabPanelBlocks[ activeIndex ]; if ( ! target || toIndex < 0 || toIndex >= tabPanelBlocks.length ) { return; } __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes( tabsClientId, { editorActiveTabIndex: toIndex, } ); moveBlocksToPosition( [ target.clientId ], tabPanelsClientId, tabPanelsClientId, toIndex ); }; return { insertTab, removeTab, moveTab }; }