@ui-machines/toggle
Version:
Core logic for the toggle widget implemented as a state machine
4 lines • 122 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../utilities/dom/src/attrs.ts", "../../../utilities/core/src/array.ts", "../../../utilities/core/src/functions.ts", "../../../utilities/core/src/guard.ts", "../../../utilities/core/src/warning.ts", "../../../utilities/dom/src/next-tick.ts", "../../../utilities/dom/src/body-pointer-event.ts", "../../../../node_modules/compute-scroll-into-view/src/index.ts", "../../../../node_modules/scroll-into-view-if-needed/es/index.js", "../../../utilities/dom/src/listener.ts", "../../../utilities/dom/src/computed-style.ts", "../../../utilities/dom/src/query.ts", "../../../utilities/dom/src/scrollable.ts", "../../../utilities/dom/src/body-scroll-lock.ts", "../../../utilities/dom/src/dispatch-event.ts", "../../../utilities/dom/src/focus-event.ts", "../../../utilities/dom/src/focusable.ts", "../../../utilities/dom/src/keyboard-event.ts", "../../../utilities/dom/src/visually-hidden.ts", "../../../utilities/dom/src/live-region.ts", "../../../utilities/dom/src/mutation-observer.ts", "../../../utilities/dom/src/nodelist.ts", "../../../utilities/dom/src/text-selection.ts", "../../../utilities/dom/src/pointer-event.ts", "../../../utilities/dom/src/pointerlock.ts", "../../../../node_modules/@floating-ui/core/dist/floating-ui.core.esm.js", "../../../../node_modules/@floating-ui/dom/dist/floating-ui.dom.esm.js", "../../../utilities/dom/src/popper.middleware.ts", "../../../utilities/dom/src/rect-observer.ts", "../../../utilities/dom/src/popper.ts", "../../../types/src/prop-types.ts", "../src/toggle.dom.ts", "../src/toggle.connect.ts", "../src/toggle.machine.ts"],
"sourcesContent": ["type Booleanish = boolean | \"true\" | \"false\"\n\nexport const dataAttr = (guard: boolean | undefined) => {\n return (guard ? \"\" : undefined) as Booleanish\n}\n\nexport const ariaAttr = (guard: boolean | undefined) => {\n return guard ? true : undefined\n}\n", "export function toArray<T>(v: T | T[] | undefined | null): T[] {\n if (!v) return []\n return Array.isArray(v) ? v : [v]\n}\n\nexport const fromLength = (length: number) => Array.from(Array(length).keys())\n\nexport const first = <T>(v: T[]): T | undefined => v[0]\n\nexport const last = <T>(v: T[]): T | undefined => v[v.length - 1]\n\nexport const isEmpty = <T>(v: T[]): boolean => v.length === 0\n\nexport const has = <T>(v: T[], t: any): boolean => v.indexOf(t) !== -1\n\nexport const add = <T>(v: T[], ...items: T[]): T[] => v.concat(items)\n\nexport const remove = <T>(v: T[], item: T): T[] => removeAt(v, v.indexOf(item))\n\nexport const removeAt = <T>(v: T[], i: number): T[] => {\n if (i > -1) v.splice(i, 1)\n return v\n}\n\nexport function clear<T>(v: T[]): T[] {\n while (v.length > 0) v.pop()\n return v\n}\n\nexport type IndexOptions = {\n step?: number\n loop?: boolean\n}\n\nexport function nextIndex<T>(v: T[], idx: number, opts: IndexOptions = {}): number {\n const { step = 1, loop = true } = opts\n const next = idx + step\n const len = v.length\n const last = len - 1\n if (idx === -1) return step > 0 ? 0 : last\n if (next < 0) return loop ? last : 0\n if (next >= len) return loop ? 0 : idx > len ? len : idx\n return next\n}\n\nexport function next<T>(v: T[], idx: number, opts: IndexOptions = {}): T | undefined {\n return v[nextIndex(v, idx, opts)]\n}\n\nexport function prevIndex<T>(v: T[], idx: number, opts: IndexOptions = {}): number {\n const { step = 1, loop = true } = opts\n return nextIndex(v, idx, { step: -step, loop })\n}\n\nexport function prev<T>(v: T[], index: number, opts: IndexOptions = {}): T | undefined {\n return v[prevIndex(v, index, opts)]\n}\n\nexport const chunk = <T>(v: T[], size: number): T[][] => {\n const res: T[][] = []\n return v.reduce((rows, value, index) => {\n if (index % size === 0) rows.push([value])\n else last(rows)?.push(value)\n return rows\n }, res)\n}\n", "export const runIfFn = <T>(\n v: T,\n ...a: T extends (...a: any[]) => void ? Parameters<T> : never\n): T extends (...a: any[]) => void ? ReturnType<T> : T => {\n return typeof v === \"function\" ? v(...a) : v\n}\n\nexport const cast = <T>(v: unknown): T => v as T\n\nexport const noop = () => {}\n\nexport const pipe =\n <T>(...fns: Array<(a: T) => T>) =>\n (v: T) =>\n fns.reduce((a, b) => b(a), v)\n\nexport const callAll =\n <T extends (...a: any[]) => void>(...fns: (T | undefined)[]) =>\n (...a: Parameters<T>) => {\n fns.forEach(function (fn) {\n fn?.(...a)\n })\n }\n\nexport const uuid = (() => {\n let id = 0\n return () => {\n id++\n return id.toString(36)\n }\n})()\n", "const platform = (v: RegExp) => isDom() && v.test(navigator.platform)\nconst ua = (v: RegExp) => isDom() && v.test(navigator.userAgent)\n\nexport const isDev = () => process.env.NODE_ENV !== \"production\"\nexport const isDom = () => !!(typeof window !== \"undefined\")\nexport const isMac = () => platform(/^Mac/)\nexport const isIPhone = () => platform(/^iPhone/)\nexport const isIPad = () => platform(/^iPad/) || (isMac() && navigator.maxTouchPoints > 1)\nexport const isIos = () => isIPhone() || isIPad()\nexport const isSafari = () => ua(/^((?!chrome|android).)*safari/i)\nexport const isFirefox = () => ua(/^Firefox/)\nexport const isWebkit = () => ua(/^WebKit/) && !ua(/Chrome/)\nexport const isApple = () => isMac() || isIos()\n\nexport const isArray = (v: any): v is any[] => Array.isArray(v)\nexport const isBoolean = (v: any): v is boolean => v === true || v === false\nexport const isObject = (v: any): v is Record<string, any> => !(v == null || typeof v !== \"object\" || isArray(v))\nexport const isNumber = (v: any): v is number => typeof v === \"number\" && !Number.isNaN(v)\nexport const isString = (v: any): v is string => typeof v === \"string\"\nexport const isFunction = (v: any): v is Function => typeof v === \"function\"\n\nexport const supportsPointerEvent = () => isDom() && window.onpointerdown === null\nexport const supportsTouchEvent = () => isDom() && window.ontouchstart === null\nexport const supportsMouseEvent = () => isDom() && window.onmousedown === null\n\nexport const isMouseEvent = (v: any): v is MouseEvent => isObject(v) && \"button\" in v\nexport const isTouchEvent = (v: any): v is TouchEvent => isObject(v) && \"touches\" in v\nexport const isLeftClick = (v: MouseEvent | PointerEvent) => v.button === 0\nexport const isRightClick = (v: MouseEvent | PointerEvent) => v.button === 2\nexport const isModifiedEvent = (v: MouseEvent | PointerEvent | KeyboardEvent) =>\n v.ctrlKey || v.altKey || v.metaKey || v.shiftKey\nexport const isCtrlKey = (v: KeyboardEvent) => (isMac() ? v.metaKey && !v.ctrlKey : v.ctrlKey && !v.metaKey)\n", "export function warn(m: string): void\nexport function warn(c: boolean, m: string): void\nexport function warn(...a: any[]): void {\n const m = a.length === 1 ? a[0] : a[1]\n const c = a.length === 2 ? a[0] : true\n if (c && process.env.NODE_ENV !== \"production\") {\n console.warn(m)\n }\n}\n\nexport function invariant(m: string): void\nexport function invariant(c: boolean, m: string): void\nexport function invariant(...a: any[]): void {\n const m = a.length === 1 ? a[0] : a[1]\n const c = a.length === 2 ? a[0] : true\n if (c && process.env.NODE_ENV !== \"production\") {\n throw new Error(m)\n }\n}\n", "export function nextTick(fn: VoidFunction) {\n const set = new Set<VoidFunction>()\n function raf(fn: VoidFunction) {\n const id = globalThis.requestAnimationFrame(fn)\n set.add(() => globalThis.cancelAnimationFrame(id))\n }\n raf(() => raf(fn))\n return function cleanup() {\n set.forEach(function (fn) {\n fn()\n })\n }\n}\n\nexport function raf(fn: VoidFunction) {\n const id = globalThis.requestAnimationFrame(fn)\n return function cleanup() {\n globalThis.cancelAnimationFrame(id)\n }\n}\n\nexport function forceReflow() {\n return document.body.offsetHeight\n}\n", "import { isLeftClick } from \"@ui-machines/utils\"\nimport { nextTick } from \"./next-tick\"\n\nlet changeCount = 0\nlet originalBodyPointerEvents: string\n\ntype PointerEventOptions = {\n disabled: boolean\n document?: Document\n}\n\nexport function preventBodyPointerEvents(el: HTMLElement | null, opts: Partial<PointerEventOptions> = {}) {\n const { disabled = false, document: docProp } = opts\n const doc: Document = docProp || document\n\n let isTouchOrPenPressed = false\n let isLeftClickPressed = false\n\n function listen() {\n const onPointerDown = (event: PointerEvent) => {\n const isMouse = event.pointerType === \"mouse\"\n isTouchOrPenPressed = !isMouse\n isLeftClickPressed = isMouse && isLeftClick(event)\n }\n\n const onPointerUp = () => {\n isTouchOrPenPressed = false\n isLeftClickPressed = false\n }\n\n doc.addEventListener(\"pointerdown\", onPointerDown)\n doc.addEventListener(\"pointerup\", onPointerUp)\n\n return function () {\n doc.removeEventListener(\"pointerdown\", onPointerDown)\n doc.removeEventListener(\"pointerup\", onPointerUp)\n }\n }\n\n function reset() {\n changeCount--\n if (changeCount === 0) {\n doc.body.style.pointerEvents = originalBodyPointerEvents\n }\n if (el) {\n el.style.pointerEvents = \"\"\n }\n }\n\n function apply() {\n if (disabled) return\n\n if (changeCount === 0) {\n originalBodyPointerEvents = doc.body.style.pointerEvents\n }\n\n doc.body.style.pointerEvents = \"none\"\n if (el) {\n el.style.pointerEvents = \"auto\"\n }\n\n changeCount++\n\n return function () {\n if (isTouchOrPenPressed) {\n doc.addEventListener(\"click\", reset, { once: true })\n } else if (isLeftClickPressed) {\n doc.addEventListener(\"pointerup\", reset, { once: true })\n } else {\n reset()\n }\n }\n }\n\n const cleanups: Array<VoidFunction | undefined> = []\n cleanups.push(apply())\n nextTick(() => {\n cleanups.push(listen())\n })\n\n return function () {\n cleanups.forEach((cleanup) => cleanup?.())\n }\n}\n", "// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n// add support for visualViewport object currently implemented in chrome\ninterface visualViewport {\n height: number\n width: number\n}\n\ntype ScrollLogicalPosition = 'start' | 'center' | 'end' | 'nearest'\n// This new option is tracked in this PR, which is the most likely candidate at the time: https://github.com/w3c/csswg-drafts/pull/1805\ntype ScrollMode = 'always' | 'if-needed'\n// New option that skips auto-scrolling all nodes with overflow: hidden set\n// See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\ntype SkipOverflowHiddenElements = boolean\n\ninterface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n boundary?: CustomScrollBoundary\n skipOverflowHiddenElements?: SkipOverflowHiddenElements\n}\n\n// Custom behavior, not in any spec\ntype CustomScrollBoundaryCallback = (parent: Element) => boolean\ntype CustomScrollBoundary = Element | CustomScrollBoundaryCallback | null\ninterface CustomScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250F \u2501 \u2501 \u2501 \u2513\n * \u2502 target \u2502 frame\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2517 \u2501 \u2501 \u2501 \u251B\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * \u250C\u2500\u2500\u2510\n * \u250F\u2501\u2502\u2501\u2501\u2502\u2501\u2513\n * \u2502 \u2502\n * \u2503 \u2502 \u2502 \u2503 do nothing\n * \u2502 \u2502\n * \u2517\u2501\u2502\u2501\u2501\u2502\u2501\u251B\n * \u2514\u2500\u2500\u2518\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * \u250F \u2501 \u2501 \u2501 \u2501 \u2513\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502\u2503 \u2503\u2502 do nothing\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2517 \u2501 \u2501 \u2501 \u2501 \u251B\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * \u250C\u2500\u2500\u2510\n * \u250F\u2501\u2502\u2501\u2501\u2502\u2501\u2513 \u250F\u2501\u250C\u2501\u2501\u2510\u2501\u2513\n * \u2514\u2500\u2500\u2518 \u2502 \u2502\n * from \u2503 \u2503 to \u2503 \u2514\u2500\u2500\u2518 \u2503\n *\n * \u2517\u2501 \u2501\u2501 \u2501\u251B \u2517\u2501 \u2501\u2501 \u2501\u251B\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * \u250F\u2501 \u2501\u2501 \u2501\u2513 \u250F\u2501\u250C\u2501\u2501\u2510\u2501\u2513\n * \u2502 \u2502\n * from \u2503 \u250C\u2500\u2500\u2510 \u2503 to \u2503 \u2502 \u2502 \u2503\n * \u2502 \u2502 \u2502 \u2502\n * \u2517\u2501\u2502\u2501\u2501\u2502\u2501\u251B \u2517\u2501\u2502\u2501\u2501\u2502\u2501\u251B\n * \u2502 \u2502 \u2514\u2500\u2500\u2518\n * \u2502 \u2502\n * \u2514\u2500\u2500\u2518\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * \u250F \u2501 \u2501 \u2501 \u2501 \u2513 \u250F \u2501 \u2501 \u2501 \u2501 \u2513\n * \u250C\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2510\n * \u2502 \u2503 \u2502 \u2503 \u2503 \u2502 \u2503\n * \u2514\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2518\n * \u2517 \u2501 \u2501 \u2501 \u2501 \u251B \u2517 \u2501 \u2501 \u2501 \u2501 \u251B\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * \u250F \u2501 \u2501 \u2501 \u2501 \u2513 \u250F \u2501 \u2501 \u2501 \u2501 \u2513\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2503 \u2502 \u2503 \u2502 \u2503 \u2503 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2517 \u2501 \u2501 \u2501 \u2501 \u251B \u2517 \u2501 \u2501 \u2501 \u2501 \u251B\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * \u250F\u2501 \u2501\u2501 \u2501\u2513 \u250F\u2501 \u2501\u2501 \u2501\u2513\n *\n * from \u2503 \u2503 to \u2503 \u250C\u2500\u2500\u2510 \u2503\n * \u250C\u2500\u2500\u2510 \u2502 \u2502\n * \u2517\u2501\u2502\u2501\u2501\u2502\u2501\u251B \u2517\u2501\u2514\u2501\u2501\u2518\u2501\u251B\n * \u2514\u2500\u2500\u2518\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * \u250C\u2500\u2500\u2510\n * \u2502 \u2502\n * \u2502 \u2502 \u250C\u2500\u2500\u2510\n * \u250F\u2501\u2502\u2501\u2501\u2502\u2501\u2513 \u250F\u2501\u2502\u2501\u2501\u2502\u2501\u2513\n * \u2502 \u2502 \u2502 \u2502\n * from \u2503 \u2514\u2500\u2500\u2518 \u2503 to \u2503 \u2502 \u2502 \u2503\n * \u2502 \u2502\n * \u2517\u2501 \u2501\u2501 \u2501\u251B \u2517\u2501\u2514\u2501\u2501\u2518\u2501\u251B\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * \u250F \u2501 \u2501 \u2501 \u2501 \u2513 \u250F \u2501 \u2501 \u2501 \u2501 \u2513\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 \u2503 \u2502 \u2503 \u2502 \u2503 \u2503\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2517 \u2501 \u2501 \u2501 \u2501 \u251B \u2517 \u2501 \u2501 \u2501 \u2501 \u251B\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * \u250F \u2501 \u2501 \u2501 \u2501 \u2513 \u250F \u2501 \u2501 \u2501 \u2501 \u2513\n * \u250C\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2510\n * \u2503 \u2502 \u2503 \u2502 \u2503 \u2502 \u2503\n * \u2514\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2518\n * \u2517 \u2501 \u2501 \u2501 \u2501 \u251B \u2517 \u2501 \u2501 \u2501 \u2501 \u251B\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nexport default (target: Element, options: Options): CustomScrollAction[] => {\n //TODO: remove this hack when microbundle will support typescript >= 4.0\n const windowWithViewport = (window as unknown) as Window & {\n visualViewport: visualViewport\n }\n\n const {\n scrollMode,\n block,\n inline,\n boundary,\n skipOverflowHiddenElements,\n } = options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n const checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n const scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n const frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = cursor.parentElement\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n const viewportWidth = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.width\n : innerWidth\n const viewportHeight = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.height\n : innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX || pageXOffset\n const viewportY = window.scrollY || pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: CustomScrollAction[] = []\n // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const {\n height,\n width,\n top,\n right,\n bottom,\n left,\n } = frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll,\n frame.scrollHeight - height + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll,\n frame.scrollWidth - width + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n", "import compute from 'compute-scroll-into-view';\n\nfunction isOptionsObject(options) {\n return options === Object(options) && Object.keys(options).length !== 0;\n}\n\nfunction defaultBehavior(actions, behavior) {\n if (behavior === void 0) {\n behavior = 'auto';\n }\n\n var canSmoothScroll = ('scrollBehavior' in document.body.style);\n actions.forEach(function (_ref) {\n var el = _ref.el,\n top = _ref.top,\n left = _ref.left;\n\n if (el.scroll && canSmoothScroll) {\n el.scroll({\n top: top,\n left: left,\n behavior: behavior\n });\n } else {\n el.scrollTop = top;\n el.scrollLeft = left;\n }\n });\n}\n\nfunction getOptions(options) {\n if (options === false) {\n return {\n block: 'end',\n inline: 'nearest'\n };\n }\n\n if (isOptionsObject(options)) {\n return options;\n }\n\n return {\n block: 'start',\n inline: 'nearest'\n };\n}\n\nfunction scrollIntoView(target, options) {\n var targetIsDetached = !target.ownerDocument.documentElement.contains(target);\n\n if (isOptionsObject(options) && typeof options.behavior === 'function') {\n return options.behavior(targetIsDetached ? [] : compute(target, options));\n }\n\n if (targetIsDetached) {\n return;\n }\n\n var computeOptions = getOptions(options);\n return defaultBehavior(compute(target, computeOptions), computeOptions.behavior);\n}\n\nexport default scrollIntoView;", "export const t = (v: any) => Object.prototype.toString.call(v).slice(8, -1)\n\nexport const isRef = (v: any): v is RefTarget => {\n return t(v) === \"Object\" && \"current\" in v\n}\n\nexport const runIfFn = (fn: any): HTMLElement | null => {\n return t(fn) === \"Function\" ? fn() : fn\n}\n\nconst isTouchEvent = (v: Event): v is TouchEvent => {\n return t(v) === \"Object\" && !!(v as TouchEvent).touches\n}\n\nconst fallback = { pageX: 0, pageY: 0, clientX: 0, clientY: 0 }\n\nexport function extractInfo<T extends AnyPointerEvent = AnyPointerEvent>(event: T, type: \"page\" | \"client\" = \"page\") {\n const point = isTouchEvent(event) ? event.touches[0] || event.changedTouches[0] || fallback : event\n return {\n point: {\n x: point[`${type}X`],\n y: point[`${type}Y`],\n },\n }\n}\n\nexport function addDomEvent<K extends keyof EventMap>(\n target: DOMEventTarget,\n event: K,\n listener: (event: EventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n) {\n const node = isRef(target) ? target.current : runIfFn(target)\n node?.addEventListener(event, listener as any, options)\n return () => node?.removeEventListener(event, listener as any, options)\n}\n\nexport function addPointerEvent<K extends keyof EventMap>(\n target: DOMEventTarget,\n event: K,\n listener: (event: EventMap[K], info: PointerEventInfo) => void,\n options?: boolean | AddEventListenerOptions,\n) {\n return addDomEvent(target, getEventName(event), wrapHandler(listener, event === \"pointerdown\"), options)\n}\n\nfunction wrapHandler<E extends EventMap[keyof EventMap]>(\n fn: (event: E, info: PointerEventInfo) => void,\n filter = false,\n) {\n const listener: EventListener = (event: any) => {\n fn(event, extractInfo(event))\n }\n return filter ? filterPrimaryPointer(listener) : listener\n}\n\nfunction filterPrimaryPointer(fn: EventListener): EventListener {\n return (event: Event) => {\n const win = ((event as UIEvent).view ?? window) as typeof window\n const isMouseEvent = event instanceof win.MouseEvent\n const isPrimary = !isMouseEvent || (isMouseEvent && (event as MouseEvent).button === 0)\n if (isPrimary) {\n fn(event)\n }\n }\n}\n\nexport function extractClientInfo(event: AnyPointerEvent) {\n return extractInfo(event, \"client\")\n}\n\nconst supportsPointerEvent = () => typeof window !== \"undefined\" && window.onpointerdown === null\nconst supportsTouchEvent = () => typeof window !== \"undefined\" && window.ontouchstart === null\nconst supportsMouseEvent = () => typeof window !== \"undefined\" && window.onmousedown === null\n\nexport function getEventName(evt: keyof EventMap): keyof EventMap {\n if (supportsPointerEvent()) return evt\n if (supportsTouchEvent()) return touchEventNames[evt]\n if (supportsMouseEvent()) return mouseEventNames[evt]\n return evt\n}\n\nexport interface EventMap extends DocumentEventMap, WindowEventMap, HTMLElementEventMap {}\n\nexport type DOMTarget = Document | HTMLElement | EventTarget | null\nexport type AnyPointerEvent = MouseEvent | TouchEvent | PointerEvent\nexport type RefTarget = { current: HTMLElement | null }\nexport type DOMEventTarget = (() => DOMTarget) | DOMTarget | RefTarget\nexport type EventListenerOptions = boolean | AddEventListenerOptions\nexport interface PointerEventInfo {\n point: { x: number; y: number }\n}\nexport type EventListenerWithPointInfo<T extends AnyPointerEvent = AnyPointerEvent> = (\n event: T,\n info: PointerEventInfo,\n) => void\n\ninterface PointerNameMap {\n pointerdown: string\n pointermove: string\n pointerup: string\n pointercancel: string\n pointerover?: string\n pointerout?: string\n pointerenter?: string\n pointerleave?: string\n}\n\nconst mouseEventNames: PointerNameMap = {\n pointerdown: \"mousedown\",\n pointermove: \"mousemove\",\n pointerup: \"mouseup\",\n pointercancel: \"mousecancel\",\n pointerover: \"mouseover\",\n pointerout: \"mouseout\",\n pointerenter: \"mouseenter\",\n pointerleave: \"mouseleave\",\n}\n\nconst touchEventNames: PointerNameMap = {\n pointerdown: \"touchstart\",\n pointermove: \"touchmove\",\n pointerup: \"touchend\",\n pointercancel: \"touchcancel\",\n}\n", "type Key = keyof CSSStyleDeclaration | (string & {})\ntype Styles = Record<Key, any>\ntype El = HTMLElement | null | undefined\n\nconst styleCache: WeakMap<HTMLElement, Styles> = new WeakMap()\n\nexport function getComputedStyle(el: El): Styles {\n if (!el) return {} as Styles\n let style: Styles | undefined = styleCache.get(el)\n if (!style) {\n const win = el?.ownerDocument.defaultView ?? window\n style = win.getComputedStyle(el) as Styles\n styleCache.set(el, style)\n }\n return style\n}\n\nexport function setElementStyle(el: El, styles: Styles) {\n if (!el) return\n for (const key in styles) el.style.setProperty(key, styles[key])\n}\n\nexport function checkElementStyle(el: El, k: Key, v: string | string[]) {\n if (!el) return false\n const style = getComputedStyle(el)\n const val = Array.isArray(v) ? v : [v]\n const vv = style?.getPropertyValue(k)\n return vv !== null ? val.includes(vv) : false\n}\n", "export function getOwnerDocument(el: HTMLElement | Window) {\n if (isWindow(el)) return el.document\n return el?.ownerDocument ?? document\n}\n\nexport function getOwnerWindow(el: HTMLElement) {\n return el?.ownerDocument.defaultView ?? window\n}\n\nexport function getDocumentElement(el: HTMLElement | Window): HTMLElement {\n return getOwnerDocument(el).documentElement\n}\n\nexport function getNodeName(node: HTMLElement | Window): string {\n return isWindow(node) ? \"\" : node ? node.localName || \"\" : \"\"\n}\n\nexport function getEventWindow(event: UIEvent) {\n if (event.view) return event.view\n let target = event.currentTarget\n if (target != null) return getOwnerWindow(target as HTMLElement)\n return window\n}\n\nexport function getParent(el: HTMLElement): HTMLElement {\n const doc = getOwnerDocument(el)\n if (getNodeName(el) === \"html\") return el\n return el.assignedSlot || el.parentElement || doc.documentElement\n}\n\ntype Node = HTMLElement | EventTarget | null\n\nexport function contains(parent: Node | undefined, child: Node) {\n if (!parent) return false\n return parent === child || (isHTMLElement(parent) && isHTMLElement(child) && parent.contains(child))\n}\n\nexport function isHTMLElement(v: any): v is HTMLElement {\n return typeof v === \"object\" && v?.nodeType === Node.ELEMENT_NODE && typeof v?.nodeName === \"string\"\n}\n\nexport function isWindow(value: any): value is Window {\n return value?.toString() === \"[object Window]\"\n}\n\nexport const isDisabled = (el: HTMLElement | null): boolean => {\n return el?.getAttribute(\"disabled\") != null || !!el?.getAttribute(\"aria-disabled\") === true\n}\n\nexport function getNativeEvent<E>(\n event: E,\n): React.ChangeEvent<any> extends E ? InputEvent : E extends React.SyntheticEvent<any, infer T> ? T : never {\n return (event as any).nativeEvent ?? event\n}\n", "import { getComputedStyle } from \"./computed-style\"\nimport { getNodeName, getOwnerDocument, getOwnerWindow, getParent, isHTMLElement, isWindow } from \"./query\"\n\nexport function isScrollParent(el: HTMLElement): boolean {\n const { overflow, overflowX, overflowY } = getComputedStyle(el)\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX)\n}\n\nexport function getScrollParent(el: HTMLElement): HTMLElement {\n if ([\"html\", \"body\", \"#document\"].includes(getNodeName(el))) {\n return getOwnerDocument(el).body\n }\n\n if (isHTMLElement(el) && isScrollParent(el)) {\n return el\n }\n\n return getScrollParent(getParent(el))\n}\n\ntype Target = Array<VisualViewport | Window | HTMLElement>\n\nexport function getScrollParents(el: HTMLElement, list: Target = []): Target {\n const scrollParent = getScrollParent(el)\n const isBody = scrollParent === getOwnerDocument(el).body\n const win = getOwnerWindow(scrollParent)\n\n const target = isBody\n ? ([win] as Target).concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : [])\n : scrollParent\n\n const parents = list.concat(target)\n if (isBody) return parents\n\n return parents.concat(getScrollParents(getParent(<HTMLElement>target)))\n}\n\nexport function getScrollOffset(el: HTMLElement) {\n if (isWindow(el)) {\n return { scrollLeft: el.scrollX, scrollTop: el.scrollY }\n }\n\n return { scrollLeft: el.scrollLeft, scrollTop: el.scrollTop }\n}\n", "import { isDom, isIos, noop, pipe } from \"@ui-machines/utils\"\nimport scrollIntoView from \"scroll-into-view-if-needed\"\nimport { addDomEvent } from \"./listener\"\nimport { getScrollParent } from \"./scrollable\"\n\ninterface PreventScrollOptions {\n disabled?: boolean\n allowPinchZoom?: boolean\n document?: Document\n}\n\n// HTML input types that do not cause the software keyboard to appear.\nconst nonTextInputTypes = new Set([\"checkbox\", \"radio\", \"range\", \"color\", \"file\", \"image\", \"button\", \"submit\", \"reset\"])\n\nexport function preventBodyScroll(opts?: PreventScrollOptions) {\n const { document: docProp, disabled = false, allowPinchZoom } = opts ?? {}\n\n const doc = docProp ?? document\n const win = doc?.defaultView ?? window\n\n const viewport = isDom() ? win.visualViewport : null\n const docEl = doc.documentElement\n\n function preventScrollStandard() {\n const fn = pipe(\n setStyle(docEl, \"paddingRight\", `${win.innerWidth - docEl.clientWidth}px`),\n setStyle(docEl, \"overflow\", \"hidden\"),\n )\n return () => fn?.()\n }\n\n function preventScrollMobileSafari() {\n let scrollable: HTMLElement | undefined\n let lastY = 0\n\n let onTouchStart = (e: TouchEvent) => {\n scrollable = getScrollParent(e.target as HTMLElement)\n\n if (scrollable === docEl && scrollable === doc.body) {\n return\n }\n lastY = e.changedTouches[0].pageY\n }\n\n let onTouchMove = (e: TouchEvent) => {\n if (e.touches.length === 2 && allowPinchZoom) return\n\n if (!scrollable || scrollable === docEl || scrollable === doc.body) {\n e.preventDefault()\n return\n }\n\n let y = e.changedTouches[0].pageY\n let scrollTop = scrollable.scrollTop\n let bottom = scrollable.scrollHeight - scrollable.clientHeight\n\n if ((scrollTop <= 0 && y > lastY) || (scrollTop >= bottom && y < lastY)) {\n e.preventDefault()\n }\n\n lastY = y\n }\n\n let onTouchEnd = (e: TouchEvent) => {\n let target = e.target as HTMLElement\n if (target instanceof win.HTMLInputElement && !nonTextInputTypes.has(target.type)) {\n e.preventDefault()\n target.style.transform = \"translateY(-2000px)\"\n target.focus()\n win.requestAnimationFrame(() => {\n target.style.transform = \"\"\n })\n }\n }\n\n let onFocus = (e: FocusEvent) => {\n let target = e.target as HTMLElement\n if (target instanceof win.HTMLInputElement && !nonTextInputTypes.has(target.type)) {\n target.style.transform = \"translateY(-2000px)\"\n win.requestAnimationFrame(() => {\n target.style.transform = \"\"\n if (!viewport) return\n\n if (viewport.height < win.innerHeight) {\n win.requestAnimationFrame(function () {\n scrollIntoView(target, { scrollMode: \"if-needed\" })\n })\n } else {\n viewport.addEventListener(\n \"resize\",\n function () {\n scrollIntoView(target, { scrollMode: \"if-needed\" })\n },\n { once: true },\n )\n }\n })\n }\n }\n\n let onWindowScroll = () => {\n win.scrollTo(0, 0)\n }\n\n let scrollX = win.scrollX\n let scrollY = win.scrollY\n let restoreStyles = pipe(\n setStyle(docEl, \"paddingRight\", `${win.innerWidth - docEl.clientWidth}px`),\n setStyle(docEl, \"overflow\", \"hidden\"),\n setStyle(doc.body, \"marginTop\", `-${scrollY}px`),\n )\n\n win.scrollTo(0, 0)\n\n let removeEvents = pipe(\n addDomEvent(doc, \"touchstart\", onTouchStart, { passive: false, capture: true }),\n addDomEvent(doc, \"touchmove\", onTouchMove, { passive: false, capture: true }),\n addDomEvent(doc, \"touchend\", onTouchEnd, { passive: false, capture: true }),\n addDomEvent(doc, \"focus\", onFocus, true),\n addDomEvent(win, \"scroll\", onWindowScroll),\n )\n\n return () => {\n restoreStyles()\n removeEvents()\n win.scrollTo(scrollX, scrollY)\n }\n }\n\n if (disabled) return noop\n return isIos() ? preventScrollMobileSafari() : preventScrollStandard()\n}\n\nfunction setStyle(el: HTMLElement, key: string, value: string) {\n let cur = el.style[key]\n el.style[key] = value\n return () => {\n el.style[key] = cur\n }\n}\n\n// Backup lib: https://github.com/hanai/html-body-scroll-lock\n", "import { getOwnerWindow } from \"./query\"\n\ntype DispatchEventOptions = {\n type?: \"input\" | \"checked\"\n key?: string\n value: string | number | boolean\n hidden?: boolean\n}\n\nexport function dispatchInputEvent(el: HTMLElement, opts: DispatchEventOptions) {\n const { key = \"value\", value, type = \"input\", hidden = true } = opts\n const win = getOwnerWindow(el)\n if (!(el instanceof win.HTMLInputElement)) return\n\n if (hidden) {\n el.type = \"text\"\n el.hidden = true\n }\n\n const proto = win.HTMLInputElement.prototype\n const set = Object.getOwnPropertyDescriptor(proto, key)?.set\n\n set?.call(el, value)\n\n const evt = new win.Event(type, { bubbles: true })\n el.dispatchEvent(evt)\n\n if (hidden) {\n el.type = \"hidden\"\n el.hidden = false\n }\n}\n", "/**\n * Determine if the blur event within an element is valid\n */\nexport function validateBlur(event: Event, opts: Options) {\n const exclude = Array.isArray(opts.exclude) ? opts.exclude : [opts.exclude]\n const relatedTarget = (event.relatedTarget ?? opts.fallback) as HTMLElement\n return exclude.every((el) => !el?.contains(relatedTarget))\n}\n\ntype MaybeArray<T> = T | T[]\n\ntype Options = {\n exclude: MaybeArray<HTMLElement | null>\n fallback?: HTMLElement | null\n}\n\ntype Event = Pick<FocusEvent, \"relatedTarget\">\n", "import { getComputedStyle } from \"./computed-style\"\nimport { isDisabled, isHTMLElement } from \"./query\"\n\nexport const focusableSelector = [\n \"input:not([disabled]):not([type=hidden])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"button:not([disabled])\",\n \"embed\",\n \"iframe\",\n \"object\",\n \"a[href]\",\n \"area[href]\",\n \"[tabindex]\",\n \"audio[controls]\",\n \"video[controls]\",\n \"*[tabindex]:not([aria-disabled])\",\n \"[contenteditable]:not([contenteditable=false])\",\n \"details > summary:first-of-type\",\n].join(\",\")\n\nexport function isHidden(el: HTMLElement | null, until?: HTMLElement) {\n const style = getComputedStyle(el)\n if (!el || style.getPropertyValue(\"visibility\") === \"hidden\") return true\n while (el) {\n if (until != null && el === until) return false\n if (style.getPropertyValue(\"display\") === \"none\") return true\n el = el.parentElement\n }\n return false\n}\n\n/**\n * Returns the focusable elements within the element\n */\nexport const getFocusables = (el: HTMLElement | Document | null, includeContainer = false) => {\n if (!el) return []\n let els = Array.from(el.querySelectorAll<HTMLElement>(focusableSelector))\n if (includeContainer && isHTMLElement(el)) {\n els.unshift(el)\n }\n return els.filter((el) => isFocusable(el) && !isHidden(el))\n}\n\n/**\n * Whether this element is focusable\n */\nexport const isFocusable = (el: HTMLElement | null) => {\n if (!isHTMLElement(el) || isHidden(el) || isDisabled(el)) return false\n return el?.matches(focusableSelector)\n}\n\n/**\n * Returns the tabbable elements within the element\n */\nexport const getTabbables = (el: HTMLElement | Document, includeContainer = false) => {\n return getFocusables(el, includeContainer).filter(isTabbable)\n}\n\n/**\n * Whether this element is tabbable\n */\nexport const isTabbable = (el: HTMLElement | null) => {\n return isFocusable(el) && !isDisabled(el) && !isHidden(el)\n}\n", "import type { KeyboardEvent } from \"react\"\n\ntype EventKey =\n | \"ArrowDown\"\n | \"ArrowUp\"\n | \"ArrowLeft\"\n | \"ArrowRight\"\n | \"Space\"\n | \"Enter\"\n | \"Comma\"\n | \"Escape\"\n | \"Backspace\"\n | \"Delete\"\n | \"Home\"\n | \"End\"\n | \"Tab\"\n | \"PageUp\"\n | \"PageDown\"\n | (string & {})\n\nexport type EventKeyMap = Partial<Record<EventKey, (event: React.KeyboardEvent) => void>>\n\nconst rtlKeyMap = {\n ArrowLeft: \"ArrowRight\",\n ArrowRight: \"ArrowLeft\",\n Home: \"End\",\n End: \"Home\",\n}\n\nconst sameKeyMap = {\n Up: \"ArrowUp\",\n Down: \"ArrowDown\",\n Esc: \"Escape\",\n \" \": \"Space\",\n \",\": \"Comma\",\n Left: \"ArrowLeft\",\n Right: \"ArrowRight\",\n}\n\ntype EventKeyOptions = {\n dir?: \"ltr\" | \"rtl\"\n orientation?: \"horizontal\" | \"vertical\"\n}\n\n/**\n * Determine the event key based on text direction.\n */\nexport function getEventKey(event: KeyboardEvent, options: EventKeyOptions = {}) {\n const { dir = \"ltr\", orientation = \"horizontal\" } = options\n\n let { key } = event\n key = sameKeyMap[key] ?? key // normalize key\n\n const isRtl = dir === \"rtl\" && orientation === \"horizontal\"\n\n if (isRtl && key in rtlKeyMap) {\n key = rtlKeyMap[key]\n }\n\n return key\n}\n\nconst PAGE_KEYS = new Set([\"PageUp\", \"PageDown\"])\nconst ARROW_KEYS = new Set([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"])\n\n/**\n * Determine the step factor for keyboard events\n */\nexport function getEventStep(event: KeyboardEvent) {\n if (event.ctrlKey || event.metaKey) {\n return 0.1\n } else {\n const isPageKey = PAGE_KEYS.has(event.key)\n const isSkipKey = isPageKey || (event.shiftKey && ARROW_KEYS.has(event.key))\n return isSkipKey ? 10 : 1\n }\n}\n", "export const visuallyHiddenStyle = {\n border: \"0\",\n clip: \"rect(0 0 0 0)\",\n height: \"1px\",\n margin: \"-1px\",\n overflow: \"hidden\",\n padding: \"0\",\n position: \"absolute\",\n width: \"1px\",\n whiteSpace: \"nowrap\",\n wordWrap: \"normal\",\n} as const\n\nexport function setVisuallyHid