UNPKG

@grail-ui/svelte

Version:

[![NPM](https://img.shields.io/npm/v/@grail-ui/svelte)](https://www.npmjs.com/package/@grail-ui/svelte) [![minified](https://img.shields.io/bundlephobia/min/@grail-ui/svelte)](https://bundlephobia.com/package/@grail-ui/svelte) [![minified + zipped](https:

135 lines (134 loc) 5.18 kB
import { derived, get, readable, writable } from 'svelte/store'; import { uniqueId } from '../util/id.js'; import { listKeyManager } from '../keyManager/listKeyManager.js'; import { addEventListener } from '../eventListener/eventListener.js'; import { chain } from '../util/chain.js'; import { ENTER, SPACE } from '../util/keyboard.js'; import { toReadable, writableEffect } from '../util/store.js'; const TABS_ATTRIBUTES = { root: 'data-tabs-root', list: 'data-tabs-list', trigger: 'data-tabs-trigger', content: 'data-tabs-content', }; const getState = (active) => (active ? 'active' : 'inactive'); const getTriggerId = (baseId, value) => `${baseId}-trigger-${value}`; const getContentId = (baseId, value) => `${baseId}-content-${value}`; const getTriggers = (node) => Array.from(node.querySelectorAll(`[${TABS_ATTRIBUTES.trigger}]`)); export function createTabs(config) { const { orientation, value, activationMode, disabled, onValueChange } = { orientation: 'horizontal', activationMode: 'automatic', disabled: false, ...config, }; const disabled$ = writable(disabled); const baseId = uniqueId('tabs'); const active$ = writableEffect(value, onValueChange); const activate = (value) => { active$.set(value); }; const useTabs = (node) => { const items = writable(getTriggers(node)); const keyManager = listKeyManager({ items, homeAndEnd: true, wrap: true, horizontal: orientation === 'horizontal' ? 'ltr' : undefined, vertical: orientation === 'vertical', skipPredicate: (item) => 'disabled' in item.dataset, onActivate: (item) => item.focus(), }); function getTrigger(event, collection = getTriggers(node)) { const node = event.target; return node.hasAttribute(TABS_ATTRIBUTES.trigger) ? collection.find((item) => item === node) : undefined; } function activateTrigger(trigger) { if (trigger && !trigger.dataset.disabled && trigger.dataset.tabsTrigger) { activate(trigger.dataset.tabsTrigger); } } const handleKeyDown = (event) => { const triggerCollection = getTriggers(node); const trigger = getTrigger(event, triggerCollection); if (!trigger) return; if ([SPACE, ENTER].includes(event.key)) { event.preventDefault(); activateTrigger(trigger); return; } items.set(triggerCollection); keyManager.setActiveItem(triggerCollection.indexOf(trigger)); keyManager.currentActiveItem?.focus(); keyManager.onKeydown(event); }; const handleFocusIn = (event) => { if (activationMode === 'automatic') { activateTrigger(getTrigger(event)); } }; const handleClick = (event) => { activateTrigger(getTrigger(event)); }; const removeEvents = chain(addEventListener(node, 'keydown', handleKeyDown), addEventListener(node, 'focusin', handleFocusIn), addEventListener(node, 'click', handleClick)); return { destroy() { keyManager.destroy(); removeEvents(); }, }; }; const derivedState = function (fn) { return derived([active$, disabled$], () => { return function (key) { const _disabled = get(disabled$); const disabled = Array.isArray(_disabled) ? _disabled.includes(key) : typeof _disabled === 'boolean' ? _disabled : _disabled === key; const state = getState(get(active$) === key); return fn({ key, disabled, state }); }; }); }; const rootAttrs = readable({ [TABS_ATTRIBUTES.root]: '', }); const listAttrs = readable({ [TABS_ATTRIBUTES.list]: '', 'aria-orientation': orientation, role: 'tablist', }); const triggerAttrs = derivedState(({ key, disabled, state }) => ({ [TABS_ATTRIBUTES.trigger]: key, role: 'tab', id: getTriggerId(baseId, key), 'aria-controls': getContentId(baseId, key), 'aria-selected': `${state === 'active'}`, tabindex: state === 'active' ? '0' : '-1', ...(disabled ? { disabled: 'true', 'data-disabled': 'true' } : {}), })); const contentAttrs = derivedState(({ key, state }) => ({ [TABS_ATTRIBUTES.content]: key, 'data-orientation': orientation, role: 'tabpanel', id: getContentId(baseId, key), 'aria-labelledby': getTriggerId(baseId, key), tabindex: '0', ...(state === 'inactive' ? { hidden: 'true' } : {}), })); return { active: toReadable(active$), disabled: disabled$, activate, useTabs, rootAttrs, listAttrs, triggerAttrs, contentAttrs, }; }