UNPKG

@zag-js/tabs

Version:

Core logic for the tabs widget implemented as a state machine

300 lines (298 loc) • 9.73 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/tabs.machine.ts var tabs_machine_exports = {}; __export(tabs_machine_exports, { machine: () => machine }); module.exports = __toCommonJS(tabs_machine_exports); var import_core = require("@zag-js/core"); var import_dom_query = require("@zag-js/dom-query"); var import_utils = require("@zag-js/utils"); var dom = __toESM(require("./tabs.dom.js")); var { createMachine } = (0, import_core.setup)(); var machine = createMachine({ props({ props }) { return { dir: "ltr", orientation: "horizontal", activationMode: "automatic", loopFocus: true, composite: true, navigate(details) { (0, import_dom_query.clickIfLink)(details.node); }, defaultValue: null, ...props }; }, initialState() { return "idle"; }, context({ prop, bindable }) { return { value: bindable(() => ({ defaultValue: prop("defaultValue"), value: prop("value"), onChange(value) { prop("onValueChange")?.({ value }); } })), focusedValue: bindable(() => ({ defaultValue: prop("value") || prop("defaultValue"), sync: true, onChange(value) { prop("onFocusChange")?.({ focusedValue: value }); } })), ssr: bindable(() => ({ defaultValue: true })), indicatorRect: bindable(() => ({ defaultValue: null })) }; }, watch({ context, prop, track, action }) { track([() => context.get("value")], () => { action(["syncIndicatorRect", "syncTabIndex", "navigateIfNeeded"]); }); track([() => prop("dir"), () => prop("orientation")], () => { action(["syncIndicatorRect"]); }); }, on: { SET_VALUE: { actions: ["setValue"] }, CLEAR_VALUE: { actions: ["clearValue"] }, SET_INDICATOR_RECT: { actions: ["setIndicatorRect"] }, SYNC_TAB_INDEX: { actions: ["syncTabIndex"] } }, entry: ["syncIndicatorRect", "syncTabIndex", "syncSsr"], exit: ["cleanupObserver"], states: { idle: { on: { TAB_FOCUS: { target: "focused", actions: ["setFocusedValue"] }, TAB_CLICK: { target: "focused", actions: ["setFocusedValue", "setValue"] } } }, focused: { on: { TAB_CLICK: { actions: ["setFocusedValue", "setValue"] }, ARROW_PREV: [ { guard: "selectOnFocus", actions: ["focusPrevTab", "selectFocusedTab"] }, { actions: ["focusPrevTab"] } ], ARROW_NEXT: [ { guard: "selectOnFocus", actions: ["focusNextTab", "selectFocusedTab"] }, { actions: ["focusNextTab"] } ], HOME: [ { guard: "selectOnFocus", actions: ["focusFirstTab", "selectFocusedTab"] }, { actions: ["focusFirstTab"] } ], END: [ { guard: "selectOnFocus", actions: ["focusLastTab", "selectFocusedTab"] }, { actions: ["focusLastTab"] } ], TAB_FOCUS: { actions: ["setFocusedValue"] }, TAB_BLUR: { target: "idle", actions: ["clearFocusedValue"] } } } }, implementations: { guards: { selectOnFocus: ({ prop }) => prop("activationMode") === "automatic" }, actions: { selectFocusedTab({ context, prop }) { (0, import_dom_query.raf)(() => { const focusedValue = context.get("focusedValue"); if (!focusedValue) return; const nullable = prop("deselectable") && context.get("value") === focusedValue; const value = nullable ? null : focusedValue; context.set("value", value); }); }, setFocusedValue({ context, event, flush }) { if (event.value == null) return; flush(() => { context.set("focusedValue", event.value); }); }, clearFocusedValue({ context }) { context.set("focusedValue", null); }, setValue({ context, event, prop }) { const nullable = prop("deselectable") && context.get("value") === context.get("focusedValue"); context.set("value", nullable ? null : event.value); }, clearValue({ context }) { context.set("value", null); }, focusFirstTab({ scope }) { (0, import_dom_query.raf)(() => { dom.getFirstTriggerEl(scope)?.focus(); }); }, focusLastTab({ scope }) { (0, import_dom_query.raf)(() => { dom.getLastTriggerEl(scope)?.focus(); }); }, focusNextTab({ context, prop, scope, event }) { const focusedValue = event.value ?? context.get("focusedValue"); if (!focusedValue) return; const triggerEl = dom.getNextTriggerEl(scope, { value: focusedValue, loopFocus: prop("loopFocus") }); (0, import_dom_query.raf)(() => { if (prop("composite")) { triggerEl?.focus(); } else if (triggerEl?.dataset.value != null) { context.set("focusedValue", triggerEl.dataset.value); } }); }, focusPrevTab({ context, prop, scope, event }) { const focusedValue = event.value ?? context.get("focusedValue"); if (!focusedValue) return; const triggerEl = dom.getPrevTriggerEl(scope, { value: focusedValue, loopFocus: prop("loopFocus") }); (0, import_dom_query.raf)(() => { if (prop("composite")) { triggerEl?.focus(); } else if (triggerEl?.dataset.value != null) { context.set("focusedValue", triggerEl.dataset.value); } }); }, syncTabIndex({ context, scope }) { (0, import_dom_query.raf)(() => { const value = context.get("value"); if (!value) return; const contentEl = dom.getContentEl(scope, value); if (!contentEl) return; const focusables = (0, import_dom_query.getFocusables)(contentEl); if (focusables.length > 0) { contentEl.removeAttribute("tabindex"); } else { contentEl.setAttribute("tabindex", "0"); } }); }, cleanupObserver({ refs }) { const cleanup = refs.get("indicatorCleanup"); if (cleanup) cleanup(); }, setIndicatorRect({ context, event, scope }) { const value = event.id ?? context.get("value"); const indicatorEl = dom.getIndicatorEl(scope); if (!indicatorEl) return; if (!value) return; const triggerEl = dom.getTriggerEl(scope, value); if (!triggerEl) return; context.set("indicatorRect", dom.getRectByValue(scope, value)); }, syncSsr({ context }) { context.set("ssr", false); }, syncIndicatorRect({ context, refs, scope }) { const cleanup = refs.get("indicatorCleanup"); if (cleanup) cleanup(); const indicatorEl = dom.getIndicatorEl(scope); if (!indicatorEl) return; const exec = () => { const triggerEl = dom.getTriggerEl(scope, context.get("value")); if (!triggerEl) return; const rect = dom.getOffsetRect(triggerEl); context.set("indicatorRect", (prev) => (0, import_utils.isEqual)(prev, rect) ? prev : rect); }; exec(); const triggerEls = dom.getElements(scope); const indicatorCleanup = (0, import_utils.callAll)(...triggerEls.map((el) => import_dom_query.resizeObserverBorderBox.observe(el, exec))); refs.set("indicatorCleanup", indicatorCleanup); }, navigateIfNeeded({ context, prop, scope }) { const value = context.get("value"); if (!value) return; const triggerEl = dom.getTriggerEl(scope, value); if ((0, import_dom_query.isAnchorElement)(triggerEl)) { prop("navigate")?.({ value, node: triggerEl, href: triggerEl.href }); } } } } }); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { machine });