@grail-ui/svelte
Version:
[](https://www.npmjs.com/package/@grail-ui/svelte) [](https://bundlephobia.com/package/@grail-ui/svelte) [ • 5.18 kB
JavaScript
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,
};
}