noph-ui
Version:
Noph UI is a modern, powerful UI library for Svelte 5, fully aligned with the Material 3 guidelines. Build stunning, consistent user interfaces with the efficiency and flexibility of Svelte and Google’s Material Design framework.
70 lines (69 loc) • 2.56 kB
JavaScript
export const rovingTabindex = (itemSelector, options = {}) => {
const { currentAttr = 'aria-current', currentValue = 'page' } = options;
return (node) => {
const getItems = () => Array.from(node.querySelectorAll(itemSelector));
const setTabstop = (target) => {
for (const i of getItems()) {
const wanted = i === target ? 0 : -1;
if (i.tabIndex !== wanted)
i.tabIndex = wanted;
}
};
const sync = () => {
const items = getItems();
if (items.length === 0)
return;
const focused = node.querySelector(`${itemSelector}:focus`);
const current = items.find((i) => i.getAttribute(currentAttr) === currentValue);
setTabstop(focused ?? current ?? items[0]);
};
const onFocusIn = (event) => {
const target = event.target.closest(itemSelector);
if (!target || !node.contains(target) || target.tabIndex === 0)
return;
setTabstop(target);
};
sync();
node.addEventListener('focusin', onFocusIn);
const observer = new MutationObserver(sync);
observer.observe(node, {
attributes: true,
attributeFilter: [currentAttr],
subtree: true,
childList: true,
});
return () => {
node.removeEventListener('focusin', onFocusIn);
observer.disconnect();
};
};
};
export const arrowKeyNav = (itemSelector, orientation = 'vertical') => (event) => {
const [prev, next] = orientation === 'vertical'
? ['ArrowUp', 'ArrowDown']
: ['ArrowLeft', 'ArrowRight'];
const { key } = event;
if (key !== prev && key !== next && key !== 'Home' && key !== 'End')
return;
const items = Array.from(event.currentTarget.querySelectorAll(itemSelector));
if (items.length === 0)
return;
const focused = event.currentTarget.querySelector(`${itemSelector}:focus`);
if (!focused)
return;
const currentIndex = items.indexOf(focused);
let target;
if (key === 'Home') {
target = items[0];
}
else if (key === 'End') {
target = items[items.length - 1];
}
else {
const delta = key === next ? 1 : -1;
const index = currentIndex + delta;
target = index < 0 ? items[items.length - 1] : index >= items.length ? items[0] : items[index];
}
target.focus();
event.preventDefault();
};