@wordpress/block-library
Version:
Block library for the WordPress editor.
216 lines (215 loc) • 6.53 kB
JavaScript
// 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