UNPKG

@agnos-ui/core

Version:

Framework-agnostic headless component library.

197 lines (196 loc) 7.08 kB
import { writable, computed } from "@amadeus-it-group/tansu"; import { r as registrationArray, y as computeCommonAncestor, b as browserDirective } from "../dom-gfxqXJpK.js"; const isInertOrInvisible = (element) => { let curElement = element; while (curElement) { const style = getComputedStyle(curElement); if (curElement.inert || curElement.hidden || style.display === "none" || style.visibility === "hidden") { return true; } curElement = curElement.parentElement; } return false; }; const checkNotDisabled = (element) => { var _a; if (element.disabled) { return false; } const parentFieldset = (_a = element.parentElement) == null ? void 0 : _a.closest("fieldset"); return parentFieldset ? checkNotDisabled(parentFieldset) : true; }; const isFocusableOtherTags = (element) => element.isContentEditable || !!element.hasAttribute("tabindex"); const isFocusableByTagName = { INPUT: (element) => element.type !== "hidden" && checkNotDisabled(element), SELECT: checkNotDisabled, TEXTAREA: checkNotDisabled, BUTTON: checkNotDisabled, A: (element) => !!element.href || isFocusableOtherTags(element) }; const isFocusable = (element) => { return document.contains(element) && !isInertOrInvisible(element) && (isFocusableByTagName[element.tagName] ?? isFocusableOtherTags)(element); }; const compareDomOrder = (element1, element2) => { if (element1 === element2) { return 0; } const result = element1.compareDocumentPosition(element2); if (result & Node.DOCUMENT_POSITION_FOLLOWING) { return -1; } else if (result & Node.DOCUMENT_POSITION_PRECEDING) { return 1; } throw new Error("failed to compare elements"); }; const getTextDirection = (element) => getComputedStyle(element).direction; const textInputTypes = /* @__PURE__ */ new Set(["text", "search", "url", "tel", "password"]); const isTextInput = (element) => element instanceof HTMLInputElement && textInputTypes.has(element.type); const getKeyName = (event) => { let key = event.key; if (event.shiftKey) { key = `Shift+${key}`; } if (event.altKey) { key = `Alt+${key}`; } if (event.ctrlKey) { key = `Ctrl+${key}`; } if (event.metaKey) { key = `Meta+${key}`; } return key; }; const isInternalInputNavigation = (event) => { const { target, key } = event; if (isTextInput(target) && (key === "ArrowLeft" || key === "ArrowRight" || key === "Home" || key === "End")) { let startPosition; if (key === "ArrowLeft" || key === "ArrowRight") { const direction = getTextDirection(target); startPosition = key === (direction === "ltr" ? "ArrowLeft" : "ArrowRight"); } else { startPosition = key === "Home"; } const cursorPosition = target.selectionStart === target.selectionEnd ? target.selectionStart : null; if (startPosition && cursorPosition !== 0 || !startPosition && cursorPosition !== target.value.length) { return true; } } return false; }; const defaultSelector = (directiveElement) => [directiveElement]; const createNavManager = () => { const directiveInstances$ = registrationArray(); const elementsRefresh$ = writable({}); const refreshElements = (now = true) => { elementsRefresh$.set({}); if (now) { commonAncestor$(); elementsInDomOrder$(); } }; const elements$ = computed(() => { elementsRefresh$(); const res = []; for (const item of directiveInstances$()) { res.push(...item()); } return res; }); const commonAncestor$ = computed(() => computeCommonAncestor(elements$()), { equal: Object.is }); const elementsInDomOrder$ = computed(() => [...elements$()].sort(compareDomOrder)); const ancestorDirection = () => { const commonAncestor = commonAncestor$(); return commonAncestor ? getTextDirection(commonAncestor) : "ltr"; }; const preventDefaultIfRelevant = (value, event) => { if (value) { event == null ? void 0 : event.preventDefault(); } return value; }; const focusIndex = (index, moveDirection = 0) => { const array = elementsInDomOrder$(); while (index >= 0 && index < array.length) { const newItem = array[index]; if (isFocusable(newItem)) { newItem.focus(); if (moveDirection != 0 && isTextInput(newItem)) { const changeDirection = ancestorDirection() !== getTextDirection(newItem); const position = moveDirection > 0 !== changeDirection ? 0 : newItem.value.length; newItem.setSelectionRange(position, position, position === 0 ? "forward" : "backward"); } return newItem; } if (moveDirection === 0) { break; } else { index += moveDirection; } } return null; }; const createFocusNeighbour = (moveDirection) => ({ event, referenceElement = (event == null ? void 0 : event.target) ?? document.activeElement } = {}) => { const curIndex = referenceElement ? elementsInDomOrder$().indexOf(referenceElement) : -1; if (curIndex > -1) { return preventDefaultIfRelevant(focusIndex(curIndex + moveDirection, moveDirection), event); } return null; }; const directive = browserDirective((directiveElement, config) => { const onKeyDown = (event) => { var _a; if (isInternalInputNavigation(event)) { return; } const keyName = getKeyName(event); const handler = (_a = config.keys) == null ? void 0 : _a[keyName]; if (handler) { refreshElements(false); handler({ event, directiveElement, navManager, context: config.context }); } }; directiveElement.addEventListener("keydown", onKeyDown); const unregister = directiveInstances$.register(() => ((config == null ? void 0 : config.selector) ?? defaultSelector)(directiveElement)); return { update(newConfig) { config = newConfig; }, destroy() { directiveElement.removeEventListener("keydown", onKeyDown); unregister(); } }; }); const focusPrevious = createFocusNeighbour(-1); const focusNext = createFocusNeighbour(1); const focusFirst = ({ event } = {}) => preventDefaultIfRelevant(focusIndex(0, 1), event); const focusLast = ({ event } = {}) => preventDefaultIfRelevant(focusIndex(elementsInDomOrder$().length - 1, -1), event); const focusLeft = (...args) => (ancestorDirection() === "rtl" ? focusNext : focusPrevious)(...args); const focusRight = (...args) => (ancestorDirection() === "rtl" ? focusPrevious : focusNext)(...args); const focusFirstLeft = (...args) => (ancestorDirection() === "rtl" ? focusLast : focusFirst)(...args); const focusFirstRight = (...args) => (ancestorDirection() === "rtl" ? focusFirst : focusLast)(...args); const navManager = { elementsInDomOrder$, directive, focusIndex, focusPrevious, focusNext, focusFirst, focusFirstLeft, focusFirstRight, focusLast, focusLeft, focusRight, refreshElements }; return navManager; }; export { createNavManager, getKeyName, isInternalInputNavigation };