UNPKG

@wordpress/block-library

Version:
216 lines (215 loc) 6.53 kB
// packages/block-library/src/tabs/view.js import { store, getContext, getElement, withSyncEvent } from "@wordpress/interactivity"; var { actions, state } = store( "core/tabs", { state: { /** * Gets a contextually aware list of tabs for the current tabs block. * * @type {Array} */ get tabsList() { const context = getContext(); const tabsId = context?.tabsId; const tabsList = state[tabsId]; return tabsList; }, /** * Gets the index of the active tab element whether it * is a tab label or tab panel. * * @type {number|null} */ get tabIndex() { const { attributes } = getElement(); const tabId = attributes?.id?.replace("tab__", "") || null; if (!tabId) { return null; } const { tabsList } = state; const tabIndex = tabsList.findIndex((t) => t === tabId); return tabIndex; }, /** * Whether the tab panel or tab label is the active tab. * * @type {boolean} */ get isActiveTab() { const { activeTabIndex } = getContext(); const { tabIndex } = state; return activeTabIndex === tabIndex; }, /** * The value of the hidden attribute for tab panels. * * Inactive panels use the `until-found` state rather than being * hidden outright, so their content stays reachable by the * browser's find-in-page and by fragment navigation. The browser * fires `beforematch` on the panel before revealing it, which is * where the matching tab gets activated. * * @type {string|null} */ get isHidden() { return state.isActiveTab ? null : "until-found"; }, /** * The value of the tabindex attribute for tab buttons and tab * panels. Only the active tab should be in the tab sequence. * * An inactive panel is hidden with `until-found`, which leaves it * in the layout, so it stays focusable unless it is taken out of * the tab sequence here. * * @type {number} */ get tabIndexAttribute() { return state.isActiveTab ? 0 : -1; } }, actions: { /** * Handles the keydown events for the tab label and tabs controller. * * @param {KeyboardEvent} event The keydown event. */ handleTabKeyDown: withSyncEvent((event) => { const { tabIndex, tabsList } = state; if (tabIndex === null) { return; } if (event.key === "ArrowRight") { event.preventDefault(); actions.moveFocus(tabIndex + 1); } else if (event.key === "ArrowLeft") { event.preventDefault(); actions.moveFocus(tabIndex - 1); } else if (event.key === "Home") { event.preventDefault(); actions.moveFocus(0); } else if (event.key === "End") { event.preventDefault(); actions.moveFocus(tabsList.length - 1); } }), /** * Handles the click event for the tab label. * * @param {MouseEvent} event The click event. */ handleTabClick: withSyncEvent((event) => { event.preventDefault(); const { tabIndex } = state; if (tabIndex !== null) { actions.setActiveTab(tabIndex); } }), /** * Activates the tab whose panel the browser is about to reveal. * * Fired on a panel hidden with `until-found` when find-in-page or * a fragment navigation matches content inside it. */ handleBeforeMatch: () => { const { tabIndex } = state; if (tabIndex !== null) { actions.setActiveTab(tabIndex); } }, /** * Moves focus to a specific tab without activating it. * * @param {number} tabIndex The index to move focus to. */ moveFocus: (tabIndex) => { const { tabsList } = state; if (!tabsList || tabsList.length === 0) { return; } let newIndex = tabIndex; if (newIndex < 0) { newIndex = tabsList.length - 1; } else if (newIndex >= tabsList.length) { newIndex = 0; } const tabId = tabsList[newIndex]; const tabElement = document.getElementById("tab__" + tabId); if (tabElement) { tabElement.focus(); } }, /** * Sets the active tab index. * * @param {number} tabIndex The index of the active tab. * @param {boolean} scrollToTab Whether to scroll to the tab element. */ setActiveTab: (tabIndex, scrollToTab = false) => { const { tabsList } = state; if (!tabsList || tabsList.length === 0) { return; } let newIndex = tabIndex; if (newIndex < 0) { newIndex = 0; } else if (newIndex >= tabsList.length) { newIndex = tabsList.length - 1; } const context = getContext(); context.activeTabIndex = newIndex; if (scrollToTab) { const tabId = tabsList[newIndex]; const tabElement = document.getElementById(tabId); if (tabElement) { setTimeout(() => { tabElement.scrollIntoView({ behavior: "smooth" }); }, 100); } } } }, callbacks: { activateTabByHash: () => { const { tabsList } = state; if (!tabsList || tabsList.length === 0) { return; } const targetElement = document.querySelector(":target"); if (!targetElement) { return; } const panelIndex = tabsList.findIndex( (t) => t === targetElement.id ); if (panelIndex >= 0) { actions.setActiveTab(panelIndex, true); return; } let panel = targetElement.closest(".wp-block-tab-panel"); let tabIndex = -1; while (panel && tabIndex < 0) { tabIndex = tabsList.findIndex((t) => t === panel.id); panel = panel.parentElement?.closest(".wp-block-tab-panel") ?? null; } if (tabIndex < 0) { return; } actions.setActiveTab(tabIndex); window.setTimeout(() => { targetElement.scrollIntoView(); }, 0); } } }, { lock: true } ); //# sourceMappingURL=view.mjs.map