se-design 1.0.83-dev.4 → 1.0.83-r-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/icons/tag.svg +2 -2
- package/dist/assets/style.css +1 -1
- package/dist/components/AccessibleDiv/index.d.ts +1 -23
- package/dist/components/Dropdown/index.d.ts +0 -2
- package/dist/components/DropdownWithInputTags/index.d.ts +0 -3
- package/dist/components/Link/index.d.ts +2 -6
- package/dist/components/NLSearch/ClearAiFiltersBar.d.ts +21 -0
- package/dist/components/NLSearch/NLDynamicChip.d.ts +26 -0
- package/dist/components/NLSearch/NLSearchInput.d.ts +46 -0
- package/dist/components/NLSearch/index.d.ts +6 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/index.js +139 -140
- package/dist/index.js.map +1 -1
- package/dist/index100.js.map +1 -1
- package/dist/index101.js.map +1 -1
- package/dist/index102.js.map +1 -1
- package/dist/index103.js.map +1 -1
- package/dist/index104.js.map +1 -1
- package/dist/index105.js.map +1 -1
- package/dist/index106.js.map +1 -1
- package/dist/index107.js.map +1 -1
- package/dist/index108.js.map +1 -1
- package/dist/index109.js.map +1 -1
- package/dist/index11.js +1 -1
- package/dist/index110.js.map +1 -1
- package/dist/index111.js.map +1 -1
- package/dist/index112.js.map +1 -1
- package/dist/index113.js.map +1 -1
- package/dist/index114.js.map +1 -1
- package/dist/index115.js.map +1 -1
- package/dist/index116.js.map +1 -1
- package/dist/index117.js.map +1 -1
- package/dist/index118.js.map +1 -1
- package/dist/index119.js.map +1 -1
- package/dist/index12.js +1 -1
- package/dist/index120.js.map +1 -1
- package/dist/index121.js.map +1 -1
- package/dist/index122.js.map +1 -1
- package/dist/index123.js.map +1 -1
- package/dist/index124.js.map +1 -1
- package/dist/index125.js.map +1 -1
- package/dist/index126.js.map +1 -1
- package/dist/index127.js.map +1 -1
- package/dist/index128.js.map +1 -1
- package/dist/index129.js.map +1 -1
- package/dist/index13.js +1 -1
- package/dist/index130.js.map +1 -1
- package/dist/index131.js.map +1 -1
- package/dist/index132.js.map +1 -1
- package/dist/index133.js.map +1 -1
- package/dist/index134.js.map +1 -1
- package/dist/index135.js.map +1 -1
- package/dist/index136.js.map +1 -1
- package/dist/index137.js.map +1 -1
- package/dist/index138.js.map +1 -1
- package/dist/index139.js.map +1 -1
- package/dist/index140.js.map +1 -1
- package/dist/index141.js.map +1 -1
- package/dist/index142.js.map +1 -1
- package/dist/index143.js.map +1 -1
- package/dist/index144.js.map +1 -1
- package/dist/index145.js.map +1 -1
- package/dist/index146.js.map +1 -1
- package/dist/index147.js.map +1 -1
- package/dist/index148.js.map +1 -1
- package/dist/index149.js.map +1 -1
- package/dist/index150.js.map +1 -1
- package/dist/index151.js.map +1 -1
- package/dist/index152.js.map +1 -1
- package/dist/index153.js.map +1 -1
- package/dist/index154.js.map +1 -1
- package/dist/index155.js.map +1 -1
- package/dist/index156.js.map +1 -1
- package/dist/index157.js.map +1 -1
- package/dist/index158.js.map +1 -1
- package/dist/index159.js.map +1 -1
- package/dist/index16.js +23 -25
- package/dist/index16.js.map +1 -1
- package/dist/index160.js.map +1 -1
- package/dist/index161.js.map +1 -1
- package/dist/index162.js.map +1 -1
- package/dist/index163.js.map +1 -1
- package/dist/index164.js.map +1 -1
- package/dist/index165.js.map +1 -1
- package/dist/index166.js.map +1 -1
- package/dist/index167.js.map +1 -1
- package/dist/index168.js.map +1 -1
- package/dist/index169.js.map +1 -1
- package/dist/index17.js +29 -29
- package/dist/index17.js.map +1 -1
- package/dist/index170.js.map +1 -1
- package/dist/index171.js.map +1 -1
- package/dist/index172.js.map +1 -1
- package/dist/index173.js.map +1 -1
- package/dist/index174.js.map +1 -1
- package/dist/index175.js.map +1 -1
- package/dist/index176.js.map +1 -1
- package/dist/index177.js.map +1 -1
- package/dist/index178.js.map +1 -1
- package/dist/index179.js.map +1 -1
- package/dist/index18.js +63 -85
- package/dist/index18.js.map +1 -1
- package/dist/index180.js.map +1 -1
- package/dist/index181.js.map +1 -1
- package/dist/index182.js.map +1 -1
- package/dist/index183.js.map +1 -1
- package/dist/index184.js.map +1 -1
- package/dist/index185.js.map +1 -1
- package/dist/index186.js.map +1 -1
- package/dist/index187.js.map +1 -1
- package/dist/index188.js.map +1 -1
- package/dist/index189.js.map +1 -1
- package/dist/index19.js +167 -182
- package/dist/index19.js.map +1 -1
- package/dist/index190.js.map +1 -1
- package/dist/index191.js.map +1 -1
- package/dist/index192.js.map +1 -1
- package/dist/index193.js +1 -1
- package/dist/index193.js.map +1 -1
- package/dist/index194.js.map +1 -1
- package/dist/index195.js.map +1 -1
- package/dist/index196.js.map +1 -1
- package/dist/index197.js.map +1 -1
- package/dist/index198.js.map +1 -1
- package/dist/index199.js.map +1 -1
- package/dist/index200.js.map +1 -1
- package/dist/index201.js.map +1 -1
- package/dist/index202.js.map +1 -1
- package/dist/index203.js.map +1 -1
- package/dist/index207.js +2 -2
- package/dist/index208.js +3 -3
- package/dist/index208.js.map +1 -1
- package/dist/index216.js +9 -13
- package/dist/index216.js.map +1 -1
- package/dist/index23.js +2 -2
- package/dist/index244.js +1 -1
- package/dist/index25.js +160 -174
- package/dist/index25.js.map +1 -1
- package/dist/index27.js +1 -1
- package/dist/index28.js +4 -4
- package/dist/index28.js.map +1 -1
- package/dist/index29.js +30 -34
- package/dist/index29.js.map +1 -1
- package/dist/index3.js +50 -60
- package/dist/index3.js.map +1 -1
- package/dist/index30.js +3 -3
- package/dist/index32.js +10 -11
- package/dist/index35.js +3 -3
- package/dist/index36.js +4 -4
- package/dist/index38.js +195 -210
- package/dist/index38.js.map +1 -1
- package/dist/index39.js +2 -2
- package/dist/index4.js +3 -3
- package/dist/index42.js +7 -8
- package/dist/index43.js +13 -14
- package/dist/index43.js.map +1 -1
- package/dist/index45.js +60 -61
- package/dist/index45.js.map +1 -1
- package/dist/index46.js +198 -194
- package/dist/index46.js.map +1 -1
- package/dist/index47.js +40 -38
- package/dist/index47.js.map +1 -1
- package/dist/index48.js +36 -33
- package/dist/index48.js.map +1 -1
- package/dist/index50.js +189 -78
- package/dist/index50.js.map +1 -1
- package/dist/index51.js +36 -459
- package/dist/index51.js.map +1 -1
- package/dist/index52.js +33 -98
- package/dist/index52.js.map +1 -1
- package/dist/index53.js +151 -36
- package/dist/index53.js.map +1 -1
- package/dist/index54.js +84 -36
- package/dist/index54.js.map +1 -1
- package/dist/index55.js +456 -88
- package/dist/index55.js.map +1 -1
- package/dist/index56.js +96 -161
- package/dist/index56.js.map +1 -1
- package/dist/index57.js +35 -146
- package/dist/index57.js.map +1 -1
- package/dist/index58.js +38 -33
- package/dist/index58.js.map +1 -1
- package/dist/index59.js +89 -83
- package/dist/index59.js.map +1 -1
- package/dist/index6.js +237 -239
- package/dist/index6.js.map +1 -1
- package/dist/index60.js +157 -113
- package/dist/index60.js.map +1 -1
- package/dist/index61.js +143 -62
- package/dist/index61.js.map +1 -1
- package/dist/index62.js +33 -152
- package/dist/index62.js.map +1 -1
- package/dist/index63.js +80 -312
- package/dist/index63.js.map +1 -1
- package/dist/index64.js +118 -46
- package/dist/index64.js.map +1 -1
- package/dist/index65.js +62 -140
- package/dist/index65.js.map +1 -1
- package/dist/index66.js +152 -12
- package/dist/index66.js.map +1 -1
- package/dist/index67.js +316 -44
- package/dist/index67.js.map +1 -1
- package/dist/index68.js +44 -104
- package/dist/index68.js.map +1 -1
- package/dist/index69.js +143 -18
- package/dist/index69.js.map +1 -1
- package/dist/index7.js +8 -9
- package/dist/index70.js +9 -63
- package/dist/index70.js.map +1 -1
- package/dist/index71.js +45 -26
- package/dist/index71.js.map +1 -1
- package/dist/index72.js +78 -102
- package/dist/index72.js.map +1 -1
- package/dist/index73.js +18 -61
- package/dist/index73.js.map +1 -1
- package/dist/index74.js +63 -18
- package/dist/index74.js.map +1 -1
- package/dist/index75.js +24 -105
- package/dist/index75.js.map +1 -1
- package/dist/index76.js +108 -18
- package/dist/index76.js.map +1 -1
- package/dist/index77.js +61 -10
- package/dist/index77.js.map +1 -1
- package/dist/index78.js +21 -5
- package/dist/index78.js.map +1 -1
- package/dist/index79.js +5 -56
- package/dist/index79.js.map +1 -1
- package/dist/index80.js +50 -23
- package/dist/index80.js.map +1 -1
- package/dist/index81.js +22 -7
- package/dist/index81.js.map +1 -1
- package/dist/index82.js +12 -0
- package/dist/index82.js.map +1 -0
- package/dist/index84.js +2 -2
- package/dist/index84.js.map +1 -1
- package/dist/index85.js +2 -2
- package/dist/index85.js.map +1 -1
- package/dist/index86.js +2 -2
- package/dist/index86.js.map +1 -1
- package/dist/index87.js +2 -2
- package/dist/index87.js.map +1 -1
- package/dist/index88.js +1 -1
- package/dist/index88.js.map +1 -1
- package/dist/index89.js +1 -1
- package/dist/index89.js.map +1 -1
- package/dist/index9.js +3 -3
- package/dist/index90.js +1 -1
- package/dist/index90.js.map +1 -1
- package/dist/index91.js +1 -1
- package/dist/index91.js.map +1 -1
- package/dist/index92.js +1 -1
- package/dist/index92.js.map +1 -1
- package/dist/index93.js +1 -1
- package/dist/index93.js.map +1 -1
- package/dist/index94.js +1 -1
- package/dist/index94.js.map +1 -1
- package/dist/index95.js +1 -1
- package/dist/index95.js.map +1 -1
- package/dist/index96.js +1 -1
- package/dist/index96.js.map +1 -1
- package/dist/index97.js.map +1 -1
- package/dist/index98.js.map +1 -1
- package/dist/index99.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/icons/chat.svg +0 -4
- package/dist/index49.js +0 -152
- package/dist/index49.js.map +0 -1
- package/dist/index83.js +0 -5
- package/dist/index83.js.map +0 -1
package/dist/index73.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index73.js","sources":["../src/utils/a11y/
|
|
1
|
+
{"version":3,"file":"index73.js","sources":["../src/utils/a11y/useFocusManagement.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport type { DependencyList } from 'react';\n\nexport interface UseFocusManagementOptions {\n /**\n * CSS selector to query the primary focus target.\n * Default: 'h1'\n */\n targetSelector?: string;\n /**\n * Fallback element id if selector target is not found.\n * Default: 'main-content'\n */\n fallbackId?: string;\n}\n\n/**\n * Generic focus management hook for route/state driven UI transitions.\n * Useful for both route changes and in-page multi-step wizards.\n * \n * Always skips first render and uses animation frame for safe DOM timing.\n */\nexport function useFocusManagement(\n dependencies: DependencyList,\n {\n targetSelector = '#main-content h1',\n fallbackId = 'main-content'\n }: UseFocusManagementOptions = {}\n): void {\n const isFirstRender = useRef(true);\n\n useEffect(() => {\n // Always skip first render\n if (isFirstRender.current) {\n isFirstRender.current = false;\n return;\n }\n\n const focusTarget = () => {\n const el = (document.querySelector(targetSelector) || document.getElementById(fallbackId)) as HTMLElement;\n if (el) {\n if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '-1');\n el.focus();\n }\n };\n\n // Always use animation frame for safe DOM timing\n const rafId = requestAnimationFrame(focusTarget);\n return () => cancelAnimationFrame(rafId);\n }, dependencies);\n}\n"],"names":["useFocusManagement","dependencies","targetSelector","fallbackId","isFirstRender","useRef","useEffect","current","rafId","requestAnimationFrame","focusTarget","el","document","querySelector","getElementById","hasAttribute","setAttribute","focus","cancelAnimationFrame"],"mappings":";AAsBO,SAASA,EACdC,GACA;AAAA,EACEC,gBAAAA,IAAiB;AAAA,EACjBC,YAAAA,IAAa;AACY,IAAI,IACzB;AACN,QAAMC,IAAgBC,EAAO,EAAI;AAEjCC,EAAAA,EAAU,MAAM;AAEd,QAAIF,EAAcG,SAAS;AACzBH,MAAAA,EAAcG,UAAU;AACxB;AAAA,IACF;AAWA,UAAMC,IAAQC,sBATMC,MAAM;AACxB,YAAMC,IAAMC,SAASC,cAAcX,CAAc,KAAKU,SAASE,eAAeX,CAAU;AACxF,MAAIQ,MACGA,EAAGI,aAAa,UAAU,KAAGJ,EAAGK,aAAa,YAAY,IAAI,GAClEL,EAAGM,MAAAA;AAAAA,IAEP,CAG+C;AAC/C,WAAO,MAAMC,qBAAqBV,CAAK;AAAA,EACzC,GAAGP,CAAY;AACjB;"}
|
package/dist/index74.js
CHANGED
|
@@ -1,24 +1,69 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import * as n from "react";
|
|
2
|
+
import { getA11yNameAttributes as C } from "./index82.js";
|
|
3
|
+
function M({
|
|
4
|
+
itemIds: r,
|
|
5
|
+
tabIndex: v = 0,
|
|
6
|
+
defaultFocusedId: u,
|
|
7
|
+
orientation: a = "horizontal",
|
|
8
|
+
cols: i,
|
|
9
|
+
loop: k = !0,
|
|
10
|
+
onFocusChange: f,
|
|
11
|
+
role: h
|
|
8
12
|
}) {
|
|
9
|
-
u
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
t?.current && f({
|
|
16
|
-
container: t.current
|
|
17
|
-
})?.focus();
|
|
13
|
+
const [c, b] = n.useState(u || r[0] || ""), g = n.useRef({});
|
|
14
|
+
n.useEffect(() => {
|
|
15
|
+
if (r.length > 0 && !r.includes(c)) {
|
|
16
|
+
const e = u || r[0] || "";
|
|
17
|
+
b(e);
|
|
18
18
|
}
|
|
19
|
-
}, [
|
|
19
|
+
}, [r, c, u]);
|
|
20
|
+
const x = n.useCallback((e) => {
|
|
21
|
+
b(e), f?.(e);
|
|
22
|
+
}, [f]), y = n.useCallback((e) => {
|
|
23
|
+
const t = g.current[e];
|
|
24
|
+
t ? t.focus() : (b(e), f?.(e));
|
|
25
|
+
}, [f]), p = n.useCallback((e, t = 1) => {
|
|
26
|
+
if (r.length === 0) return;
|
|
27
|
+
const o = r.indexOf(c), l = Math.max(0, o);
|
|
28
|
+
let s = l;
|
|
29
|
+
switch (e) {
|
|
30
|
+
case "first":
|
|
31
|
+
s = 0;
|
|
32
|
+
break;
|
|
33
|
+
case "last":
|
|
34
|
+
s = r.length - 1;
|
|
35
|
+
break;
|
|
36
|
+
case "prev":
|
|
37
|
+
k ? s = (l - t + r.length) % r.length : s = Math.max(0, l - t);
|
|
38
|
+
break;
|
|
39
|
+
case "next":
|
|
40
|
+
k ? s = (l + t) % r.length : s = Math.min(r.length - 1, l + t);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
const w = r[s];
|
|
44
|
+
w && g.current[w]?.focus();
|
|
45
|
+
}, [r, c, k]), A = n.useCallback((e) => ({
|
|
46
|
+
ref: (t) => {
|
|
47
|
+
g.current[e] = t;
|
|
48
|
+
},
|
|
49
|
+
tabIndex: c === e ? v : -1,
|
|
50
|
+
onFocus: () => x(e)
|
|
51
|
+
}), [c, v, x]), R = n.useCallback((e) => {
|
|
52
|
+
let t = null, o = 1;
|
|
53
|
+
e.key === "Home" ? t = "first" : e.key === "End" ? t = "last" : (a !== "vertical" && (e.key === "ArrowLeft" ? t = "prev" : e.key === "ArrowRight" && (t = "next")), !t && a !== "horizontal" && (e.key === "ArrowUp" ? t = "prev" : e.key === "ArrowDown" && (t = "next"), t && i && (o = i))), t && (e.preventDefault(), e.stopPropagation(), p(t, o));
|
|
54
|
+
}, [a, i, p]), z = n.useCallback((e) => {
|
|
55
|
+
const t = {};
|
|
56
|
+
return h && (t.role = h), a === "horizontal" ? t["aria-orientation"] = "horizontal" : a === "vertical" && (t["aria-orientation"] = "vertical"), e && Object.assign(t, C(e)), t;
|
|
57
|
+
}, [h, a]);
|
|
58
|
+
return {
|
|
59
|
+
setFocusedId: x,
|
|
60
|
+
focusItem: y,
|
|
61
|
+
handleKeyDown: R,
|
|
62
|
+
getRovingItemProps: A,
|
|
63
|
+
getContainerProps: z
|
|
64
|
+
};
|
|
20
65
|
}
|
|
21
66
|
export {
|
|
22
|
-
|
|
67
|
+
M as useRovingFocus
|
|
23
68
|
};
|
|
24
69
|
//# sourceMappingURL=index74.js.map
|
package/dist/index74.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index74.js","sources":["../src/utils/a11y/useFocusOnReady.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport type { RefObject } from 'react';\nimport { getFirstFocusableElement } from './focusableElements';\n\nexport interface UseFocusOnReadyOptions {\n /**\n * Specific element to focus. Takes priority over containerRef.\n */\n focusRef?: RefObject<HTMLElement | null>;\n /**\n * Container to search for first focusable element.\n * Used as fallback when focusRef is not provided or its current is null.\n */\n containerRef?: RefObject<HTMLElement | null>;\n /**\n * When this transitions to true, focus is triggered.\n */\n isReady: boolean;\n /**\n * Optional guard for async content — evaluated at the moment isReady becomes true.\n * Return false to skip focusing (e.g. if the user has tabbed away while content was loading).\n * Not needed for synchronous open/close like modals or dropdowns where focus is immediate.\n */\n shouldFocus?: () => boolean;\n}\n\n/**\n * Moves focus to an element when an async condition becomes ready.\n *\n * When `focusRef` is provided, focuses that element directly.\n * When only `containerRef` is provided, finds and focuses the first focusable child.\n * When both are provided, `focusRef` takes priority; `containerRef` is the fallback.\n *\n * @example Focus first focusable in container after loading\n * ```tsx\n * useFocusOnReady({\n * containerRef: panelRef,\n * isReady: !isLoading,\n * shouldFocus: () => document.activeElement === panelRef.current\n * });\n * ```\n *\n * @example Focus a specific element when modal opens\n * ```tsx\n * useFocusOnReady({\n * focusRef: closeButtonRef,\n * isReady: isOpen\n * });\n * ```\n *\n * @example Specific element with container fallback\n * ```tsx\n * useFocusOnReady({\n * focusRef: preferredRef,\n * containerRef: dialogRef,\n * isReady: isOpen\n * });\n * ```\n */\nexport function useFocusOnReady({\n focusRef,\n containerRef,\n isReady,\n shouldFocus\n}: UseFocusOnReadyOptions): void {\n useEffect(() => {\n if (!isReady) return;\n if (shouldFocus && !shouldFocus()) return;\n\n if (focusRef?.current) {\n focusRef.current.focus();\n return;\n }\n\n if (containerRef?.current) {\n const first = getFirstFocusableElement({ container: containerRef.current });\n first?.focus();\n }\n }, [isReady]);\n}\n"],"names":["useEffect","getFirstFocusableElement","useFocusOnReady","focusRef","containerRef","isReady","shouldFocus","current","focus","container"],"mappings":"AA2DO,SAAA,aAAAA,SAAA;AAAA,SAAA,4BAAAC,SAAA;AAAA,SAASC,EAAgB;AAAA,EAC9BC,UAAAA;AAAAA,EACAC,cAAAA;AAAAA,EACAC,SAAAA;AAAAA,EACAC,aAAAA;AACsB,GAAS;AAC/BN,EAAAA,EAAU,MAAM;AACd,QAAKK,KACDC,EAAAA,KAAe,CAACA,MAEpB;AAAA,UAAIH,GAAUI,SAAS;AACrBJ,QAAAA,EAASI,QAAQC,MAAAA;AACjB;AAAA,MACF;AAEA,MAAIJ,GAAcG,WACFN,EAAyB;AAAA,QAAEQ,WAAWL,EAAaG;AAAAA,MAAAA,CAAS,GACnEC,MAAAA;AAAAA;AAAAA,EAEX,GAAG,CAACH,CAAO,CAAC;AACd;"}
|
|
1
|
+
{"version":3,"file":"index74.js","sources":["../src/utils/a11y/useRovingFocus.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { getA11yNameAttributes, AccessibleNameInput } from './accessibleName';\nexport type { AccessibleNameInput };\n\nexport type RovingDirection = 'prev' | 'next' | 'first' | 'last';\n\nexport interface UseRovingFocusOptions {\n /**\n * Array of item IDs in order\n */\n itemIds: string[];\n\n /**\n * The tabIndex to apply to the currently focused item in the roving group.\n * Defaults to 0 \n */\n tabIndex?: number;\n\n /**\n * Initial focused item ID. Defaults to first item.\n */\n defaultFocusedId?: string;\n\n /**\n * Orientation for arrow key mapping.\n * - horizontal: ArrowLeft/ArrowRight\n * - vertical: ArrowUp/ArrowDown\n * - grid: all four arrow keys (Left/Right move by 1, Up/Down move by `cols`)\n */\n orientation?: 'horizontal' | 'vertical' | 'grid';\n\n /**\n * Number of columns in the grid. Required when orientation is 'grid'.\n * ArrowUp/ArrowDown navigate by this many items in the flat itemIds array.\n */\n cols?: number;\n\n /**\n * Whether navigation wraps around at ends. Defaults to true.\n */\n loop?: boolean;\n\n /**\n * Callback when focus changes\n */\n onFocusChange?: (id: string) => void;\n\n /**\n * ARIA role for the container element (e.g. 'grid', 'menu', 'tablist', 'toolbar').\n * Returned via getContainerProps().\n */\n role?: React.AriaRole;\n}\n\nexport interface RovingItemProps {\n ref: (el: HTMLElement | null) => void;\n tabIndex: number;\n onFocus: () => void;\n}\n\nexport interface RovingContainerProps {\n role?: React.AriaRole;\n 'aria-orientation'?: 'horizontal' | 'vertical';\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n}\n\nexport interface UseRovingFocusReturn {\n /**\n * Set focused item ID manually\n */\n setFocusedId: (id: string) => void;\n\n /**\n * Imperatively focus a DOM element by id.\n * If the element exists in refs, focus it immediately.\n * If not yet in DOM, update state so it gets focus once rendered.\n */\n focusItem: (id: string) => void;\n\n /**\n * Keyboard handler for arrow/Home/End navigation.\n * Attach to each item's onKeyDown.\n */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n\n /**\n * Get props for an item in the roving focus group (ref + tabIndex)\n */\n getRovingItemProps: (id: string) => RovingItemProps;\n\n /**\n * Props to spread on the container element.\n * Returns role (if provided), aria-orientation (derived from orientation; omitted for grid),\n * and any accessible name/description attributes passed in.\n */\n getContainerProps: (nameInput?: AccessibleNameInput) => RovingContainerProps;\n}\n\n/**\n * Hook for managing roving focus pattern (roving tabindex).\n * Reusable for composite widgets: tabs, toolbars, menus, listboxes, radio groups, grids.\n *\n * @example\n * // 1D (toolbar/tabs):\n * const { getRovingItemProps, handleKeyDown } = useRovingFocus({\n * itemIds: ['tab1', 'tab2', 'tab3'],\n * orientation: 'horizontal'\n * });\n *\n * // 2D (grid — flat itemIds, cols for row-jump math):\n * const { getRovingItemProps, handleKeyDown } = useRovingFocus({\n * itemIds: ['r0c0', 'r0c1', 'r0c2', 'r1c0', 'r1c1', 'r1c2'],\n * orientation: 'grid',\n * cols: 3\n * });\n *\n * // In render:\n * <button {...getRovingItemProps('tab1')} onKeyDown={handleKeyDown}>Tab 1</button>\n */\nexport function useRovingFocus({\n itemIds,\n tabIndex = 0,\n defaultFocusedId,\n orientation = 'horizontal',\n cols,\n loop = true,\n onFocusChange,\n role\n}: UseRovingFocusOptions): UseRovingFocusReturn {\n const [focusedId, setFocusedIdState] = React.useState<string>(\n defaultFocusedId || itemIds[0] || ''\n );\n\n const itemRefs = React.useRef<Record<string, HTMLElement | null>>({});\n\n // Sync focusedId if itemIds change and current focusedId is no longer valid\n React.useEffect(() => {\n if (itemIds.length > 0 && !itemIds.includes(focusedId)) {\n const newFocusedId = defaultFocusedId || itemIds[0] || '';\n setFocusedIdState(newFocusedId);\n }\n }, [itemIds, focusedId, defaultFocusedId]);\n\n const setFocusedId = React.useCallback(\n (id: string) => {\n setFocusedIdState(id);\n onFocusChange?.(id);\n },\n [onFocusChange]\n );\n\n const focusItem = React.useCallback(\n (id: string) => {\n const el = itemRefs.current[id];\n if (el) {\n el.focus();\n } else {\n // element not yet in DOM (e.g. month just changed) — update state so it gets focus once rendered\n setFocusedIdState(id);\n onFocusChange?.(id);\n }\n },\n [onFocusChange]\n );\n\n const moveFocus = React.useCallback(\n (direction: RovingDirection, step: number = 1) => {\n if (itemIds.length === 0) return;\n\n const currentIdx = itemIds.indexOf(focusedId);\n const safeIdx = Math.max(0, currentIdx);\n let nextIdx = safeIdx;\n\n switch (direction) {\n case 'first':\n nextIdx = 0;\n break;\n case 'last':\n nextIdx = itemIds.length - 1;\n break;\n case 'prev':\n if (loop) {\n nextIdx = (safeIdx - step + itemIds.length) % itemIds.length;\n } else {\n nextIdx = Math.max(0, safeIdx - step);\n }\n break;\n case 'next':\n if (loop) {\n nextIdx = (safeIdx + step) % itemIds.length;\n } else {\n nextIdx = Math.min(itemIds.length - 1, safeIdx + step);\n }\n break;\n }\n\n const nextId = itemIds[nextIdx];\n if (!nextId) return;\n\n itemRefs.current[nextId]?.focus();\n },\n [itemIds, focusedId, loop]\n );\n\n const getRovingItemProps = React.useCallback(\n (id: string): RovingItemProps => ({\n ref: (el: HTMLElement | null) => {\n itemRefs.current[id] = el;\n },\n tabIndex: focusedId === id ? tabIndex : -1,\n onFocus: () => setFocusedId(id)\n }),\n [focusedId, tabIndex, setFocusedId]\n );\n\n // Keyboard handler for arrow/Home/End navigation\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n let direction: RovingDirection | null = null;\n let step = 1;\n\n if (e.key === 'Home') {\n direction = 'first';\n } else if (e.key === 'End') {\n direction = 'last';\n } else {\n // Horizontal axis (Left/Right) — active for 'horizontal' and 'grid'\n if (orientation !== 'vertical') {\n if (e.key === 'ArrowLeft') direction = 'prev';\n else if (e.key === 'ArrowRight') direction = 'next';\n }\n // Vertical axis (Up/Down) — active for 'vertical' and 'grid'\n if (!direction && orientation !== 'horizontal') {\n if (e.key === 'ArrowUp') direction = 'prev';\n else if (e.key === 'ArrowDown') direction = 'next';\n if (direction && cols) step = cols;\n }\n }\n\n if (direction) {\n e.preventDefault();\n e.stopPropagation();\n moveFocus(direction, step);\n }\n },\n [orientation, cols, moveFocus]\n );\n\n const getContainerProps = React.useCallback((nameInput?: AccessibleNameInput): RovingContainerProps => {\n const props: RovingContainerProps = {};\n if (role) props.role = role;\n if (orientation === 'horizontal') props['aria-orientation'] = 'horizontal';\n else if (orientation === 'vertical') props['aria-orientation'] = 'vertical';\n // grid: aria-orientation omitted — not applicable for role=\"grid\"\n if (nameInput) Object.assign(props, getA11yNameAttributes(nameInput));\n return props;\n }, [role, orientation]);\n\n return {\n setFocusedId,\n focusItem,\n handleKeyDown,\n getRovingItemProps,\n getContainerProps\n };\n}\n"],"names":["React","getA11yNameAttributes","useRovingFocus","itemIds","tabIndex","defaultFocusedId","orientation","cols","loop","onFocusChange","role","focusedId","setFocusedIdState","useState","itemRefs","useRef","useEffect","length","includes","newFocusedId","setFocusedId","useCallback","id","focusItem","el","current","focus","moveFocus","direction","step","currentIdx","indexOf","safeIdx","Math","max","nextIdx","min","nextId","getRovingItemProps","ref","onFocus","handleKeyDown","e","key","preventDefault","stopPropagation","getContainerProps","nameInput","props","Object","assign"],"mappings":"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"}
|
package/dist/index75.js
CHANGED
|
@@ -1,111 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
const l = ['button:not([disabled]):not([tabindex="-1"])', '[href]:not([tabindex="-1"])', 'input:not([disabled]):not([tabindex="-1"])', 'select:not([disabled]):not([tabindex="-1"])', 'textarea:not([disabled]):not([tabindex="-1"])', '[tabindex]:not([tabindex="-1"]):not([disabled])', '[contenteditable="true"]:not([tabindex="-1"])'].join(", "), u = [l, '[role="menuitem"]', '[role="option"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]'].join(", ");
|
|
2
|
+
function s({
|
|
3
|
+
container: e,
|
|
4
|
+
includeRoles: t = !1,
|
|
5
|
+
additionalSelectors: r = [],
|
|
6
|
+
filterHidden: a = !1
|
|
7
|
+
}) {
|
|
8
|
+
if (!e) return [];
|
|
9
|
+
const d = [t ? u : l, ...r].filter(Boolean).join(", "), i = Array.from(e.querySelectorAll(d));
|
|
10
|
+
return a ? i.filter((n) => {
|
|
11
|
+
const o = window.getComputedStyle(n);
|
|
12
|
+
return o.display !== "none" && o.visibility !== "hidden" && o.opacity !== "0" && !n.hasAttribute("hidden") && n.offsetWidth > 0 && n.offsetHeight > 0;
|
|
13
|
+
}) : i;
|
|
8
14
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
r(this, "assertiveLog", null);
|
|
13
|
-
r(this, "politeLog", null);
|
|
14
|
-
r(this, "cleanupTimers", /* @__PURE__ */ new Map());
|
|
15
|
-
r(this, "ready", !1);
|
|
16
|
-
r(this, "pendingQueue", []);
|
|
17
|
-
typeof document > "u" || (this.container = document.createElement("div"), this.container.dataset.liveAnnouncer = "true", this.container.className = h, this.assertiveLog = this.createLog("assertive"), this.politeLog = this.createLog("polite"), this.container.appendChild(this.assertiveLog), this.container.appendChild(this.politeLog), document.body.prepend(this.container), this.isTestEnvironment() ? this.ready = !0 : setTimeout(() => {
|
|
18
|
-
this.ready = !0, this.flushPendingQueue();
|
|
19
|
-
}, 100));
|
|
20
|
-
}
|
|
21
|
-
// -----------------------------------------------------------------------
|
|
22
|
-
// Public
|
|
23
|
-
// -----------------------------------------------------------------------
|
|
24
|
-
announce(e, t = {}) {
|
|
25
|
-
const n = {
|
|
26
|
-
assertiveness: t.assertiveness ?? "polite",
|
|
27
|
-
timeout: t.timeout ?? 7e3,
|
|
28
|
-
batchId: t.batchId ?? ""
|
|
29
|
-
};
|
|
30
|
-
if (!this.ready) {
|
|
31
|
-
this.pendingQueue.push({
|
|
32
|
-
message: e,
|
|
33
|
-
options: n
|
|
34
|
-
});
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
this.doAnnounce(e, n);
|
|
38
|
-
}
|
|
39
|
-
clear(e) {
|
|
40
|
-
(!e || e === "assertive") && this.clearLog(this.assertiveLog), (!e || e === "polite") && this.clearLog(this.politeLog);
|
|
41
|
-
}
|
|
42
|
-
destroy() {
|
|
43
|
-
this.cleanupTimers.forEach((e) => clearTimeout(e)), this.cleanupTimers.clear(), this.container?.remove(), this.container = null, this.assertiveLog = null, this.politeLog = null, this.ready = !1, this.pendingQueue = [], s === this && (s = null);
|
|
44
|
-
}
|
|
45
|
-
isAttached() {
|
|
46
|
-
return this.container?.isConnected ?? !1;
|
|
47
|
-
}
|
|
48
|
-
// -----------------------------------------------------------------------
|
|
49
|
-
// Private
|
|
50
|
-
// -----------------------------------------------------------------------
|
|
51
|
-
createLog(e) {
|
|
52
|
-
const t = document.createElement("div");
|
|
53
|
-
return t.setAttribute("role", "log"), t.setAttribute("aria-live", e), t.setAttribute("aria-relevant", "additions"), t;
|
|
54
|
-
}
|
|
55
|
-
doAnnounce(e, t) {
|
|
56
|
-
const n = t.assertiveness === "assertive" ? this.assertiveLog : this.politeLog;
|
|
57
|
-
if (!n) return;
|
|
58
|
-
if (t.batchId) {
|
|
59
|
-
const c = n.querySelector(`[${a}="${CSS.escape(t.batchId)}"]`);
|
|
60
|
-
c && this.removeNode(c);
|
|
61
|
-
}
|
|
62
|
-
const o = document.createElement("div");
|
|
63
|
-
if (o.textContent = e, t.batchId && o.setAttribute(a, t.batchId), n.appendChild(o), e !== "") {
|
|
64
|
-
const c = setTimeout(() => {
|
|
65
|
-
this.removeNode(o);
|
|
66
|
-
}, t.timeout);
|
|
67
|
-
this.cleanupTimers.set(o, c);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
removeNode(e) {
|
|
71
|
-
const t = this.cleanupTimers.get(e);
|
|
72
|
-
t && (clearTimeout(t), this.cleanupTimers.delete(e)), e.remove();
|
|
73
|
-
}
|
|
74
|
-
clearLog(e) {
|
|
75
|
-
e && (Array.from(e.children).forEach((t) => {
|
|
76
|
-
const n = this.cleanupTimers.get(t);
|
|
77
|
-
n && (clearTimeout(n), this.cleanupTimers.delete(t));
|
|
78
|
-
}), e.innerHTML = "");
|
|
79
|
-
}
|
|
80
|
-
flushPendingQueue() {
|
|
81
|
-
this.pendingQueue.splice(0).forEach(({
|
|
82
|
-
message: t,
|
|
83
|
-
options: n
|
|
84
|
-
}) => this.doAnnounce(t, n));
|
|
85
|
-
}
|
|
86
|
-
isTestEnvironment() {
|
|
87
|
-
try {
|
|
88
|
-
if (typeof IS_REACT_ACT_ENVIRONMENT == "boolean" && IS_REACT_ACT_ENVIRONMENT || typeof jest < "u" || typeof vitest < "u") return !0;
|
|
89
|
-
} catch {
|
|
90
|
-
}
|
|
91
|
-
return !1;
|
|
92
|
-
}
|
|
15
|
+
function b(e) {
|
|
16
|
+
const t = s(e);
|
|
17
|
+
return t.length > 0 ? t[0] : null;
|
|
93
18
|
}
|
|
94
|
-
function
|
|
95
|
-
const t =
|
|
96
|
-
|
|
97
|
-
} : e ?? {};
|
|
98
|
-
d().announce(i, t);
|
|
99
|
-
}
|
|
100
|
-
function p(i) {
|
|
101
|
-
s?.clear(i);
|
|
102
|
-
}
|
|
103
|
-
function g() {
|
|
104
|
-
s?.destroy();
|
|
19
|
+
function c(e) {
|
|
20
|
+
const t = s(e);
|
|
21
|
+
return t.length > 0 ? t[t.length - 1] : null;
|
|
105
22
|
}
|
|
106
23
|
export {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
24
|
+
l as FOCUSABLE_SELECTOR,
|
|
25
|
+
u as FOCUSABLE_WITH_ROLES_SELECTOR,
|
|
26
|
+
b as getFirstFocusableElement,
|
|
27
|
+
s as getFocusableElements,
|
|
28
|
+
c as getLastFocusableElement
|
|
110
29
|
};
|
|
111
30
|
//# sourceMappingURL=index75.js.map
|
package/dist/index75.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index75.js","sources":["../src/utils/a11y/liveAnnouncer/LiveAnnouncer.ts"],"sourcesContent":["/**\n * LiveAnnouncer — singleton vanilla-DOM announcer for screen readers.\n *\n * Architecture:\n * - Prepends a visually-hidden container to document.body on first use.\n * - Two <div role=\"log\" aria-live=\"...\"> regions: one polite, one assertive.\n * - Each announce() appends a NEW child node (aria-relevant=\"additions\" makes\n * screen readers announce only additions, never re-read existing content).\n * - Child nodes auto-remove after a timeout (default 7 s) to keep the DOM clean.\n * - batchId option: if a node with the same batchId already exists in a log,\n * it is replaced — so only the final value is announced (e.g. \"N results\").\n *\n * No React dependency. No role=\"status\" or role=\"alert\" (avoids double-announce\n * bugs on iOS VoiceOver).\n */\n\nexport type Assertiveness = 'assertive' | 'polite';\n\nexport interface AnnounceOptions {\n /** Default: 'polite' */\n assertiveness?: Assertiveness;\n /** Auto-clear timeout in ms. Default: 7000 */\n timeout?: number;\n /**\n * When provided, any pending node with the same batchId is replaced rather\n * than appending a second node. Useful for rapidly-updating counts\n * (e.g. search results, selection count).\n */\n batchId?: string;\n}\n\nconst DEFAULT_TIMEOUT = 7000;\nconst SAFARI_INIT_DELAY = 100;\n\nconst SR_ONLY_CLASS = 'sr-only';\n\n/** Attribute used to find batchId nodes for replacement. */\nconst BATCH_ATTR = 'data-batch-id';\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\nlet instance: LiveAnnouncer | null = null;\n\n/**\n * Get or create the singleton LiveAnnouncer.\n * Safe to call multiple times — returns the same instance.\n */\nfunction getInstance(): LiveAnnouncer {\n if (!instance) {\n instance = new LiveAnnouncer();\n }\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// Class\n// ---------------------------------------------------------------------------\n\nclass LiveAnnouncer {\n private container: HTMLDivElement | null = null;\n private assertiveLog: HTMLDivElement | null = null;\n private politeLog: HTMLDivElement | null = null;\n private cleanupTimers: Map<HTMLElement, ReturnType<typeof setTimeout>> = new Map();\n private ready = false;\n private pendingQueue: Array<{ message: string; options: Required<AnnounceOptions> }> = [];\n\n constructor() {\n if (typeof document === 'undefined') return;\n\n this.container = document.createElement('div');\n this.container.dataset.liveAnnouncer = 'true';\n this.container.className = SR_ONLY_CLASS;\n\n this.assertiveLog = this.createLog('assertive');\n this.politeLog = this.createLog('polite');\n\n this.container.appendChild(this.assertiveLog);\n this.container.appendChild(this.politeLog);\n\n // Prepend so screen readers encounter it early in the DOM.\n document.body.prepend(this.container);\n\n // Safari/VoiceOver ignores content injected within ~100 ms of the live\n // region being added to the DOM. Delay readiness accordingly.\n // In test environments, skip the delay.\n if (this.isTestEnvironment()) {\n this.ready = true;\n } else {\n setTimeout(() => {\n this.ready = true;\n this.flushPendingQueue();\n }, SAFARI_INIT_DELAY);\n }\n }\n\n // -----------------------------------------------------------------------\n // Public\n // -----------------------------------------------------------------------\n\n announce(message: string, options: AnnounceOptions = {}): void {\n const resolved: Required<AnnounceOptions> = {\n assertiveness: options.assertiveness ?? 'polite',\n timeout: options.timeout ?? DEFAULT_TIMEOUT,\n batchId: options.batchId ?? '',\n };\n\n if (!this.ready) {\n this.pendingQueue.push({ message, options: resolved });\n return;\n }\n\n this.doAnnounce(message, resolved);\n }\n\n clear(assertiveness?: Assertiveness): void {\n if (!assertiveness || assertiveness === 'assertive') {\n this.clearLog(this.assertiveLog);\n }\n if (!assertiveness || assertiveness === 'polite') {\n this.clearLog(this.politeLog);\n }\n }\n\n destroy(): void {\n // Cancel all pending auto-clear timers.\n this.cleanupTimers.forEach((timer) => clearTimeout(timer));\n this.cleanupTimers.clear();\n\n this.container?.remove();\n this.container = null;\n this.assertiveLog = null;\n this.politeLog = null;\n this.ready = false;\n this.pendingQueue = [];\n\n if (instance === this) {\n instance = null;\n }\n }\n\n isAttached(): boolean {\n return this.container?.isConnected ?? false;\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n private createLog(ariaLive: Assertiveness): HTMLDivElement {\n const log = document.createElement('div');\n log.setAttribute('role', 'log');\n log.setAttribute('aria-live', ariaLive);\n log.setAttribute('aria-relevant', 'additions');\n return log;\n }\n\n private doAnnounce(message: string, options: Required<AnnounceOptions>): void {\n const log = options.assertiveness === 'assertive' ? this.assertiveLog : this.politeLog;\n if (!log) return;\n\n // batchId dedup: remove any existing node with the same batchId.\n if (options.batchId) {\n const existing = log.querySelector(`[${BATCH_ATTR}=\"${CSS.escape(options.batchId)}\"]`) as HTMLElement | null;\n if (existing) {\n this.removeNode(existing);\n }\n }\n\n const node = document.createElement('div');\n node.textContent = message;\n\n if (options.batchId) {\n node.setAttribute(BATCH_ATTR, options.batchId);\n }\n\n log.appendChild(node);\n\n // Auto-remove after timeout to keep the DOM clean.\n if (message !== '') {\n const timer = setTimeout(() => {\n this.removeNode(node);\n }, options.timeout);\n this.cleanupTimers.set(node, timer);\n }\n }\n\n private removeNode(node: HTMLElement): void {\n const timer = this.cleanupTimers.get(node);\n if (timer) {\n clearTimeout(timer);\n this.cleanupTimers.delete(node);\n }\n node.remove();\n }\n\n private clearLog(log: HTMLDivElement | null): void {\n if (!log) return;\n // Cancel timers for all children in this log.\n Array.from(log.children).forEach((child) => {\n const timer = this.cleanupTimers.get(child as HTMLElement);\n if (timer) {\n clearTimeout(timer);\n this.cleanupTimers.delete(child as HTMLElement);\n }\n });\n log.innerHTML = '';\n }\n\n private flushPendingQueue(): void {\n const queued = this.pendingQueue.splice(0);\n queued.forEach(({ message, options }) => this.doAnnounce(message, options));\n }\n\n private isTestEnvironment(): boolean {\n try {\n // React 18+ test flag\n // @ts-expect-error – global test flag\n if (typeof IS_REACT_ACT_ENVIRONMENT === 'boolean' && IS_REACT_ACT_ENVIRONMENT) return true;\n // Jest / Vitest\n // @ts-expect-error – jest global may not be typed\n if (typeof jest !== 'undefined') return true;\n // @ts-expect-error – Vitest global\n if (typeof vitest !== 'undefined') return true;\n } catch {\n // ignore\n }\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public imperative API (module-level)\n// ---------------------------------------------------------------------------\n\n/**\n * Announce a message to screen readers.\n *\n * Works anywhere — React components, event handlers, thunks, vanilla JS.\n * The singleton live region is lazily created on first call.\n *\n * @example\n * announce('5 results found');\n * announce('Error: upload failed', { assertiveness: 'assertive' });\n * announce('3 items selected', { batchId: 'selection-count' });\n */\nexport function announce(message: string, options?: AnnounceOptions): void;\nexport function announce(message: string, assertiveness?: Assertiveness): void;\nexport function announce(\n message: string,\n optionsOrAssertiveness?: AnnounceOptions | Assertiveness\n): void {\n const options: AnnounceOptions =\n typeof optionsOrAssertiveness === 'string'\n ? { assertiveness: optionsOrAssertiveness }\n : optionsOrAssertiveness ?? {};\n\n getInstance().announce(message, options);\n}\n\n/**\n * Clear all pending announcements.\n * @param assertiveness — if omitted, clears both polite and assertive logs.\n */\nexport function clearAnnouncer(assertiveness?: Assertiveness): void {\n instance?.clear(assertiveness);\n}\n\n/**\n * Remove the live-region container from the DOM and reset the singleton.\n * Typically called when the host app unmounts (via LiveAnnouncerProvider).\n */\nexport function destroyAnnouncer(): void {\n instance?.destroy();\n}\n\n/**\n * Check whether the live-region container is currently in the DOM.\n */\nexport function isAnnouncerAttached(): boolean {\n return instance?.isAttached() ?? false;\n}\n"],"names":["SR_ONLY_CLASS","BATCH_ATTR","instance","getInstance","LiveAnnouncer","constructor","container","assertiveLog","politeLog","cleanupTimers","Map","ready","pendingQueue","document","createElement","dataset","liveAnnouncer","className","createLog","appendChild","body","prepend","isTestEnvironment","setTimeout","flushPendingQueue","SAFARI_INIT_DELAY","announce","message","options","resolved","assertiveness","timeout","DEFAULT_TIMEOUT","batchId","push","doAnnounce","clear","clearLog","destroy","forEach","timer","clearTimeout","remove","isAttached","isConnected","ariaLive","log","setAttribute","existing","querySelector","CSS","escape","removeNode","node","textContent","set","get","delete","Array","from","children","child","innerHTML","queued","splice","IS_REACT_ACT_ENVIRONMENT","jest","vitest","optionsOrAssertiveness","clearAnnouncer","destroyAnnouncer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index75.js","sources":["../src/utils/a11y/focusableElements.ts"],"sourcesContent":["/**\n * Utilities for finding focusable elements within a container.\n * \n * Used for focus management in modals, popovers, and other interactive overlays.\n */\n\n/**\n * Base selector for standard focusable elements.\n * Matches: buttons, links, inputs, selects, textareas, elements with tabindex >= 0, contenteditable.\n */\nexport const FOCUSABLE_SELECTOR = [\n 'button:not([disabled]):not([tabindex=\"-1\"])',\n '[href]:not([tabindex=\"-1\"])',\n 'input:not([disabled]):not([tabindex=\"-1\"])',\n 'select:not([disabled]):not([tabindex=\"-1\"])',\n 'textarea:not([disabled]):not([tabindex=\"-1\"])',\n '[tabindex]:not([tabindex=\"-1\"]):not([disabled])',\n '[contenteditable=\"true\"]:not([tabindex=\"-1\"])'\n].join(', ');\n\n/**\n * Extended selector that includes ARIA role-based focusable elements.\n * Useful for composite widgets like menus, listboxes, etc.\n */\nexport const FOCUSABLE_WITH_ROLES_SELECTOR = [\n FOCUSABLE_SELECTOR,\n '[role=\"menuitem\"]',\n '[role=\"option\"]',\n '[role=\"menuitemcheckbox\"]',\n '[role=\"menuitemradio\"]'\n].join(', ');\n\n/**\n * Options for getFocusableElements\n */\nexport interface GetFocusableElementsOptions {\n /**\n * Container element to search within. If null/undefined, returns empty array.\n */\n container: HTMLElement | null;\n /**\n * Whether to include role-based focusable elements (menuitem, option, etc.).\n * Default: false (uses base selector only)\n */\n includeRoles?: boolean;\n /**\n * Additional custom selectors to include.\n */\n additionalSelectors?: string[];\n /**\n * Whether to filter out hidden/invisible elements.\n * Default: false (returns all matching elements regardless of visibility)\n */\n filterHidden?: boolean;\n}\n\n/**\n * Get all focusable elements within a container.\n * \n * @example Basic usage (standard focusable elements)\n * ```ts\n * const focusables = getFocusableElements({ container: dialogRef.current });\n * focusables[0]?.focus(); // Focus first element\n * ```\n * \n * @example With role-based elements (for menus/listboxes)\n * ```ts\n * const focusables = getFocusableElements({ \n * container: menuRef.current,\n * includeRoles: true \n * });\n * ```\n * \n * @example With custom selectors\n * ```ts\n * const focusables = getFocusableElements({ \n * container: customWidgetRef.current,\n * additionalSelectors: ['[data-focusable=\"true\"]']\n * });\n * ```\n */\nexport function getFocusableElements({\n container,\n includeRoles = false,\n additionalSelectors = [],\n filterHidden = false\n}: GetFocusableElementsOptions): HTMLElement[] {\n if (!container) return [];\n\n const selector = [\n includeRoles ? FOCUSABLE_WITH_ROLES_SELECTOR : FOCUSABLE_SELECTOR,\n ...additionalSelectors\n ].filter(Boolean).join(', ');\n\n const elements = Array.from(container.querySelectorAll<HTMLElement>(selector));\n\n if (!filterHidden) return elements;\n\n // Filter out hidden/invisible elements\n return elements.filter((el) => {\n const style = window.getComputedStyle(el);\n return (\n style.display !== 'none' &&\n style.visibility !== 'hidden' &&\n style.opacity !== '0' &&\n !el.hasAttribute('hidden') &&\n el.offsetWidth > 0 &&\n el.offsetHeight > 0\n );\n });\n}\n\n/**\n * Get the first focusable element in a container.\n * Returns null if none found.\n */\nexport function getFirstFocusableElement(\n options: GetFocusableElementsOptions\n): HTMLElement | null {\n const focusables = getFocusableElements(options);\n return focusables.length > 0 ? focusables[0] : null;\n}\n\n/**\n * Get the last focusable element in a container.\n * Returns null if none found.\n */\nexport function getLastFocusableElement(\n options: GetFocusableElementsOptions\n): HTMLElement | null {\n const focusables = getFocusableElements(options);\n return focusables.length > 0 ? focusables[focusables.length - 1] : null;\n}\n"],"names":["FOCUSABLE_SELECTOR","join","FOCUSABLE_WITH_ROLES_SELECTOR","getFocusableElements","container","includeRoles","additionalSelectors","filterHidden","selector","filter","Boolean","elements","Array","from","querySelectorAll","el","style","window","getComputedStyle","display","visibility","opacity","hasAttribute","offsetWidth","offsetHeight","getFirstFocusableElement","options","focusables","length","getLastFocusableElement"],"mappings":"AAUO,MAAMA,IAAqB,CAChC,+CACA,+BACA,8CACA,+CACA,iDACA,mDACA,+CAA+C,EAC/CC,KAAK,IAAI,GAMEC,IAAgC,CAC3CF,GACA,qBACA,mBACA,6BACA,wBAAwB,EACxBC,KAAK,IAAI;AAmDJ,SAASE,EAAqB;AAAA,EACnCC,WAAAA;AAAAA,EACAC,cAAAA,IAAe;AAAA,EACfC,qBAAAA,IAAsB,CAAA;AAAA,EACtBC,cAAAA,IAAe;AACY,GAAkB;AAC7C,MAAI,CAACH,EAAW,QAAO,CAAA;AAEvB,QAAMI,IAAW,CACfH,IAAeH,IAAgCF,GAC/C,GAAGM,CAAmB,EACtBG,OAAOC,OAAO,EAAET,KAAK,IAAI,GAErBU,IAAWC,MAAMC,KAAKT,EAAUU,iBAA8BN,CAAQ,CAAC;AAE7E,SAAKD,IAGEI,EAASF,OAAQM,CAAAA,MAAO;AAC7B,UAAMC,IAAQC,OAAOC,iBAAiBH,CAAE;AACxC,WACEC,EAAMG,YAAY,UAClBH,EAAMI,eAAe,YACrBJ,EAAMK,YAAY,OAClB,CAACN,EAAGO,aAAa,QAAQ,KACzBP,EAAGQ,cAAc,KACjBR,EAAGS,eAAe;AAAA,EAEtB,CAAC,IAbyBb;AAc5B;AAMO,SAASc,EACdC,GACoB;AACpB,QAAMC,IAAaxB,EAAqBuB,CAAO;AAC/C,SAAOC,EAAWC,SAAS,IAAID,EAAW,CAAC,IAAI;AACjD;AAMO,SAASE,EACdH,GACoB;AACpB,QAAMC,IAAaxB,EAAqBuB,CAAO;AAC/C,SAAOC,EAAWC,SAAS,IAAID,EAAWA,EAAWC,SAAS,CAAC,IAAI;AACrE;"}
|
package/dist/index76.js
CHANGED
|
@@ -1,21 +1,111 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { useRef as p, useLayoutEffect as a } from "react";
|
|
2
|
+
import { getFirstFocusableElement as A, getFocusableElements as L } from "./index75.js";
|
|
3
|
+
let f = null;
|
|
4
|
+
function F(e) {
|
|
5
|
+
const n = e.composedPath();
|
|
6
|
+
for (const s of n)
|
|
7
|
+
if (s instanceof HTMLElement && s.tabIndex >= 0) {
|
|
8
|
+
f = s;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function k(e) {
|
|
13
|
+
(e.key === "Enter" || e.key === " ") && (f = document.activeElement);
|
|
14
|
+
}
|
|
15
|
+
typeof document < "u" && (document.addEventListener("pointerdown", F, !0), document.addEventListener("keydown", k, !0));
|
|
16
|
+
function T() {
|
|
17
|
+
const e = f;
|
|
18
|
+
return f = null, e;
|
|
19
|
+
}
|
|
20
|
+
let m = null, l = null;
|
|
21
|
+
function _(e) {
|
|
22
|
+
l !== null && cancelAnimationFrame(l), m = e, l = requestAnimationFrame(() => {
|
|
23
|
+
m = null, l = null;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function I() {
|
|
27
|
+
l !== null && (cancelAnimationFrame(l), l = null);
|
|
28
|
+
const e = m;
|
|
29
|
+
return m = null, e;
|
|
30
|
+
}
|
|
31
|
+
function w(e, n) {
|
|
32
|
+
return n === "none" ? null : n === "first" ? A({
|
|
33
|
+
container: e
|
|
34
|
+
}) || e : n === "container" ? e : typeof n == "string" ? e.querySelector(n) : n instanceof HTMLElement ? n : null;
|
|
35
|
+
}
|
|
36
|
+
function C({
|
|
37
|
+
enabled: e,
|
|
38
|
+
containerRef: n,
|
|
39
|
+
restoreFocus: s = !0,
|
|
40
|
+
initialFocus: d = "first",
|
|
41
|
+
returnFocusRef: g,
|
|
42
|
+
portalContainerRefs: h
|
|
43
|
+
}) {
|
|
44
|
+
const u = p(null), E = p(null);
|
|
45
|
+
return a(() => {
|
|
46
|
+
if (!e) {
|
|
47
|
+
if (s && u.current) {
|
|
48
|
+
const t = u.current;
|
|
49
|
+
u.current = null, requestAnimationFrame(() => {
|
|
50
|
+
t.isConnected && t.focus();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const r = n.current;
|
|
56
|
+
if (!r) return;
|
|
57
|
+
const c = g?.current ?? I() ?? f ?? document.activeElement;
|
|
58
|
+
if (f = null, c?.tagName === "IFRAME") {
|
|
59
|
+
u.current = null;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
return u.current = c, r.contains(document.activeElement) || requestAnimationFrame(() => {
|
|
63
|
+
w(r, d)?.focus();
|
|
64
|
+
}), () => {
|
|
65
|
+
if (s && u.current) {
|
|
66
|
+
const t = u.current;
|
|
67
|
+
u.current = null, requestAnimationFrame(() => {
|
|
68
|
+
t.isConnected && t.focus();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, [e, n, s, d]), a(() => {
|
|
73
|
+
if (!e) return;
|
|
74
|
+
const r = n.current;
|
|
75
|
+
if (!r) return;
|
|
76
|
+
const c = (t) => {
|
|
77
|
+
if (t.key === "Tab") {
|
|
78
|
+
const o = L({
|
|
79
|
+
container: r
|
|
80
|
+
});
|
|
81
|
+
if (o.length === 0) {
|
|
82
|
+
t.preventDefault(), r.focus();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const i = o[0], v = o[o.length - 1], y = document.activeElement;
|
|
86
|
+
t.shiftKey && y === i ? (t.preventDefault(), v.focus()) : !t.shiftKey && y === v && (t.preventDefault(), i.focus());
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return document.addEventListener("keydown", c, !0), () => document.removeEventListener("keydown", c, !0);
|
|
90
|
+
}, [e, n]), a(() => {
|
|
91
|
+
if (!e) return;
|
|
92
|
+
const r = n.current;
|
|
93
|
+
if (!r) return;
|
|
94
|
+
const c = (t) => {
|
|
95
|
+
const o = t.target;
|
|
96
|
+
o?.tagName !== "IFRAME" && (r.contains(o) ? E.current = o : h?.current?.some((i) => i.current?.contains(o)) || (E.current || A({
|
|
97
|
+
container: r
|
|
98
|
+
}) || r).focus());
|
|
99
|
+
};
|
|
100
|
+
return document.addEventListener("focusin", c, !0), () => document.removeEventListener("focusin", c, !0);
|
|
101
|
+
}, [e, n]), {
|
|
102
|
+
triggerRef: u
|
|
103
|
+
};
|
|
104
|
+
}
|
|
17
105
|
export {
|
|
18
|
-
|
|
19
|
-
|
|
106
|
+
I as consumeFocusAnchor,
|
|
107
|
+
T as consumeLastInteractedElement,
|
|
108
|
+
_ as setFocusAnchor,
|
|
109
|
+
C as useFocusTrap
|
|
20
110
|
};
|
|
21
111
|
//# sourceMappingURL=index76.js.map
|
package/dist/index76.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index76.js","sources":["../src/utils/a11y/liveAnnouncer/LiveAnnouncerProvider.tsx"],"sourcesContent":["import React, { createContext, useEffect, useMemo } from 'react';\nimport {\n announce,\n clearAnnouncer,\n destroyAnnouncer,\n type AnnounceOptions,\n type Assertiveness,\n} from './LiveAnnouncer';\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nexport interface LiveAnnouncerContextValue {\n announce: (message: string, options?: AnnounceOptions) => void;\n clear: (assertiveness?: Assertiveness) => void;\n}\n\nexport const LiveAnnouncerContext = createContext<LiveAnnouncerContextValue | null>(null);\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface LiveAnnouncerProviderProps {\n children: React.ReactNode;\n}\n\n/**\n * Mounts the singleton live-region container on mount and tears it down on\n * unmount. Provides `{ announce, clear }` via React context so descendants\n * can use the `useLiveAnnouncer` hook.\n *\n * Optional — `announce()` works without a provider (the singleton auto-creates\n * on first call). The provider simply gives you explicit lifecycle control.\n *\n * @example\n * <LiveAnnouncerProvider>\n * <App />\n * </LiveAnnouncerProvider>\n */\nexport const LiveAnnouncerProvider: React.FC<LiveAnnouncerProviderProps> = ({ children }) => {\n // Trigger singleton creation on mount (if not already created).\n // The announce import already lazily creates, but calling it with an empty\n // string ensures the container is in the DOM before any child renders.\n useEffect(() => {\n // Force singleton init by importing — the constructor runs on getInstance().\n // We don't announce anything; just ensure the container exists.\n announce('');\n return () => {\n destroyAnnouncer();\n };\n }, []);\n\n const value = useMemo<LiveAnnouncerContextValue>(\n () => ({ announce, clear: clearAnnouncer }),\n []\n );\n\n return (\n <LiveAnnouncerContext.Provider value={value}>\n {children}\n </LiveAnnouncerContext.Provider>\n );\n};\n"],"names":["React__default","useEffect","useMemo","createContext","announce","destroyAnnouncer","clearAnnouncer","LiveAnnouncerContext","LiveAnnouncerProvider","children","value","clear","React","createElement","Provider"],"mappings":"AAkBO,OAAAA,KAAA,aAAAC,GAAA,WAAAC,GAAA,iBAAAC,SAAA;AAAA,SAAA,YAAAC,GAAA,oBAAAC,GAAA,kBAAAC,SAAA;AAAA,MAAMC,sBAAuE,IAAI,GAuB3EC,IAA8DA,CAAC;AAAA,EAAEC,UAAAA;AAAS,MAAM;AAI3FR,EAAAA,EAAU,OAGRG,EAAS,EAAE,GACJ,MAAM;AACXC,IAAAA,EAAAA;AAAAA,EACF,IACC,CAAA,CAAE;AAEL,QAAMK,IAAQR,EACZ,OAAO;AAAA,IAAEE,UAAAA;AAAAA,IAAUO,OAAOL;AAAAA,EAAAA,IAC1B,CAAA,CACF;AAEA,SACEM,gBAAAA,EAAAC,cAACN,EAAqBO,UAAQ;AAAA,IAACJ,OAAAA;AAAAA,EAAAA,GAC5BD,CAC4B;AAEnC;"}
|
|
1
|
+
{"version":3,"file":"index76.js","sources":["../src/utils/a11y/useFocusTrap.ts"],"sourcesContent":["import { useLayoutEffect, useRef } from 'react';\nimport type { MutableRefObject, RefObject } from 'react';\nimport { getFocusableElements, getFirstFocusableElement } from './focusableElements';\n\nexport interface UseFocusTrapOptions<T extends HTMLElement = HTMLElement> {\n /**\n * Whether the focus trap is active.\n */\n enabled: boolean;\n /**\n * Container element ref to trap focus within.\n */\n containerRef: React.RefObject<T | null>;\n /**\n * Whether to restore focus to the element that had focus before trap activated.\n * Default: true\n */\n restoreFocus?: boolean;\n /**\n * Initial focus target when trap activates.\n * - 'first': Focus first focusable element (default)\n * - 'container': Focus the container itself\n * - 'none': Skip initial focus — browser handles it (e.g. autofocus attribute)\n * - CSS selector: Focus element matching selector\n * - HTMLElement: Focus this specific element\n */\n initialFocus?: 'first' | 'container' | 'none' | string | HTMLElement;\n /**\n * Explicit element to restore focus to when the trap deactivates.\n * Overrides the automatic trigger capture (_lastInteractedElement / document.activeElement).\n * Use when the opener is known at call-site (e.g. a ref on the toggle button).\n */\n returnFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * Additional container refs that are logically part of this focus trap\n * (e.g., portal-rendered content from Popover/Dropdown). Focus moving to\n * these containers will NOT trigger the safety net redirect.\n */\n portalContainerRefs?: MutableRefObject<RefObject<HTMLElement | null>[]>;\n}\n\nexport interface UseFocusTrapReturn {\n /**\n * Ref to the element that had focus before trap activated.\n * Useful for manual focus restoration if needed.\n */\n triggerRef: React.MutableRefObject<HTMLElement | null>;\n}\n\n// Module-level trigger tracking: React's commit phase is bottom-up, so by the\n// time useFocusTrap's useLayoutEffect runs, document.activeElement may already\n// be the autoFocus element inside the modal rather than the opener button.\n// We capture the last interacted element via pointerdown/keydown listeners\n// (which fire before any React state update commits) to work around this.\nlet _lastInteractedElement: HTMLElement | null = null;\n\nfunction _onPointerDown(e: PointerEvent): void {\n // Walk composedPath to find the nearest focusable element being pressed.\n const path = e.composedPath();\n for (const node of path) {\n if (node instanceof HTMLElement && node.tabIndex >= 0) {\n _lastInteractedElement = node;\n return;\n }\n }\n}\n\nfunction _onKeyDown(e: KeyboardEvent): void {\n // Enter/Space on a focused button will synthesise a click — capture before that.\n if (e.key === 'Enter' || e.key === ' ') {\n _lastInteractedElement = document.activeElement as HTMLElement;\n }\n}\n\nif (typeof document !== 'undefined') {\n document.addEventListener('pointerdown', _onPointerDown, true);\n document.addEventListener('keydown', _onKeyDown, true);\n}\n\n/**\n * Returns (and clears) the last element interacted with before a React commit.\n * Used by SidebarOverlay complementary mode to seed its trigger ref without\n * relying on document.activeElement timing.\n */\nexport function consumeLastInteractedElement(): HTMLElement | null {\n const el = _lastInteractedElement;\n _lastInteractedElement = null;\n return el;\n}\n\n// Focus anchor — explicit stable return-focus target, higher priority than\n// _lastInteractedElement. Auto-clears via setTimeout(0) to prevent a stale\n// anchor leaking to an unrelated open.\nlet _focusAnchor: HTMLElement | null = null;\nlet _focusAnchorClearFrame: ReturnType<typeof requestAnimationFrame> | null = null;\n\n/**\n * Sets an explicit focus-return anchor element.\n * Call this before dispatching an action that will open a modal or sidebar,\n * when the natural last-interacted element is not the right return target\n * (e.g. a popover menu item that will be unmounted when the popover closes).\n */\nexport function setFocusAnchor(el: HTMLElement | null): void {\n if (_focusAnchorClearFrame !== null) cancelAnimationFrame(_focusAnchorClearFrame);\n _focusAnchor = el;\n // Auto-clear if not consumed — prevents stale anchor leaking to unrelated opens.\n // rAF fires after React has committed and painted, giving useFocusTrap time to\n // consume the anchor before it is discarded.\n _focusAnchorClearFrame = requestAnimationFrame(() => {\n _focusAnchor = null;\n _focusAnchorClearFrame = null;\n });\n}\n\n/**\n * Returns (and clears) the focus anchor if one was set, otherwise null.\n * Used internally by useFocusTrap and SidebarOverlay.\n */\nexport function consumeFocusAnchor(): HTMLElement | null {\n if (_focusAnchorClearFrame !== null) {\n cancelAnimationFrame(_focusAnchorClearFrame);\n _focusAnchorClearFrame = null;\n }\n const el = _focusAnchor;\n _focusAnchor = null;\n return el;\n}\n\n/**\n * Resolve the initial focus target based on the initialFocus option.\n */\nfunction resolveInitialFocusTarget(\n container: HTMLElement,\n initialFocus: 'first' | 'container' | 'none' | string | HTMLElement\n): HTMLElement | null {\n if (initialFocus === 'none') return null;\n if (initialFocus === 'first') {\n return getFirstFocusableElement({ container }) || container;\n }\n if (initialFocus === 'container') {\n return container;\n }\n if (typeof initialFocus === 'string') {\n return container.querySelector<HTMLElement>(initialFocus);\n }\n if (initialFocus instanceof HTMLElement) {\n return initialFocus;\n }\n return null;\n}\n\n/**\n * Hook to trap focus within a container (for modals, dialogs, drawers).\n *\n * Implements WCAG 2.1 focus trap pattern:\n * - Moves focus into container on activation\n * - Wraps Tab/Shift+Tab navigation within container\n * - Restores focus to trigger element on deactivation\n * - Safety net: catches focus escaping via other means\n * - Handles autoFocus content: captures trigger before autoFocus fires\n * - Handles {isOpen && <Modal>} pattern: restores focus on unmount\n *\n * Note: For Escape key handling, use `useDismissOnEscape` hook separately.\n * This keeps focus trap (accessibility) separate from Escape handling (UX).\n *\n * @example\n * ```tsx\n * const MyModal = ({ isOpen, onClose }) => {\n * const containerRef = useRef<HTMLDivElement>(null);\n *\n * // Escape handling (UX)\n * useDismissOnEscape({\n * containerRef,\n * onDismiss: onClose,\n * enabled: isOpen\n * });\n *\n * // Focus trap (accessibility)\n * const { triggerRef } = useFocusTrap({\n * enabled: isOpen,\n * containerRef,\n * restoreFocus: true\n * });\n *\n * return (\n * <div ref={containerRef}>\n * <button>First</button>\n * <button>Second</button>\n * </div>\n * );\n * };\n * ```\n */\nexport function useFocusTrap<T extends HTMLElement = HTMLElement>({\n enabled,\n containerRef,\n restoreFocus = true,\n initialFocus = 'first',\n returnFocusRef,\n portalContainerRefs,\n}: UseFocusTrapOptions<T>): UseFocusTrapReturn {\n const triggerRef = useRef<HTMLElement | null>(null);\n const lastFocusedInContainer = useRef<HTMLElement | null>(null);\n\n // Focus management: save trigger, move focus into container on activate, restore on deactivate\n useLayoutEffect(() => {\n if (!enabled) {\n // Restore focus to trigger when trap deactivates\n if (restoreFocus && triggerRef.current) {\n const el = triggerRef.current;\n triggerRef.current = null;\n requestAnimationFrame(() => {\n if (el.isConnected) el.focus();\n });\n }\n return;\n }\n\n const container = containerRef.current;\n if (!container) return;\n\n // Resolve trigger: explicit returnFocusRef wins, then focusAnchor (explicitly set\n // by caller for two-step flows like popover menu → modal), then the pre-commit\n // interaction record, then document.activeElement as final fallback.\n const previousActiveElement = returnFocusRef?.current ?? consumeFocusAnchor() ?? _lastInteractedElement ?? (document.activeElement as HTMLElement);\n _lastInteractedElement = null;\n\n // iframes manage their own internal focus. document.activeElement only ever\n // resolves to the <iframe> element from the parent doc — calling .focus()\n // on any parent-doc element forcibly blurs the iframe content (kills caret\n // in textareas, cancels native <select> dropdowns). Skip capture so the\n // auto-focus on open and focus-restore on close short-circuit on null trigger.\n if (previousActiveElement?.tagName === 'IFRAME') {\n triggerRef.current = null;\n return;\n }\n\n triggerRef.current = previousActiveElement;\n\n // Only move initial focus if autoFocus hasn't already placed it inside the container.\n if (!container.contains(document.activeElement)) {\n requestAnimationFrame(() => {\n resolveInitialFocusTarget(container, initialFocus)?.focus();\n });\n }\n\n // Restore focus on unmount while enabled (covers {isOpen && <Modal>} pattern\n // where the component unmounts before enabled can transition true → false)\n return () => {\n if (restoreFocus && triggerRef.current) {\n const el = triggerRef.current;\n triggerRef.current = null;\n requestAnimationFrame(() => {\n if (el.isConnected) el.focus();\n });\n }\n };\n }, [enabled, containerRef, restoreFocus, initialFocus]);\n\n // Focus trap: Tab wrapping (only when enabled)\n useLayoutEffect(() => {\n if (!enabled) return;\n\n const container = containerRef.current;\n if (!container) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n // Tab wrapping\n if (e.key === 'Tab') {\n const focusables = getFocusableElements({ container });\n\n if (focusables.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const activeElement = document.activeElement;\n\n if (e.shiftKey && activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n\n document.addEventListener('keydown', handleKeyDown, true);\n return () => document.removeEventListener('keydown', handleKeyDown, true);\n }, [enabled, containerRef]);\n\n // Focus trap safety net: catch focus escaping\n useLayoutEffect(() => {\n if (!enabled) return;\n\n const container = containerRef.current;\n if (!container) return;\n\n const handleFocusIn = (e: FocusEvent) => {\n const target = e.target as Node;\n\n // iframe focus is opaque from the parent doc — leave it alone, otherwise\n // a click into the iframe gets yanked back into the trap.\n if ((target as Element | null)?.tagName === 'IFRAME') return;\n\n if (container.contains(target)) {\n lastFocusedInContainer.current = target as HTMLElement;\n } else if (portalContainerRefs?.current?.some(ref => ref.current?.contains(target))) {\n // Focus is in a registered portal — allow it, but don't update\n // lastFocusedInContainer so safety net restores to the last main-container\n // element (e.g., the dropdown trigger) when the portal unmounts\n } else {\n // Focus escaped — redirect back\n const fallback = lastFocusedInContainer.current\n || getFirstFocusableElement({ container })\n || container;\n fallback.focus();\n }\n };\n\n document.addEventListener('focusin', handleFocusIn, true);\n return () => document.removeEventListener('focusin', handleFocusIn, true);\n }, [enabled, containerRef]);\n\n return { triggerRef };\n}\n"],"names":["_lastInteractedElement","_onPointerDown","e","path","composedPath","node","HTMLElement","tabIndex","_onKeyDown","key","document","activeElement","addEventListener","consumeLastInteractedElement","el","_focusAnchor","_focusAnchorClearFrame","setFocusAnchor","cancelAnimationFrame","requestAnimationFrame","consumeFocusAnchor","resolveInitialFocusTarget","container","initialFocus","getFirstFocusableElement","querySelector","useFocusTrap","enabled","containerRef","restoreFocus","returnFocusRef","portalContainerRefs","triggerRef","useRef","lastFocusedInContainer","useLayoutEffect","current","isConnected","focus","previousActiveElement","tagName","contains","handleKeyDown","focusables","getFocusableElements","length","preventDefault","first","last","shiftKey","removeEventListener","handleFocusIn","target","some","ref"],"mappings":"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"}
|
package/dist/index77.js
CHANGED
|
@@ -1,14 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { useCallback as c } from "react";
|
|
2
|
+
import { useRovingFocus as d } from "./index74.js";
|
|
3
|
+
const s = (r, t) => `tab${t ? `-${t}` : ""}-${r}`, l = (r, t) => `panel${t ? `-${t}` : ""}-${r}`, C = (r, t) => ({
|
|
4
|
+
id: l(r, t),
|
|
5
|
+
role: "tabpanel",
|
|
6
|
+
"aria-labelledby": s(r, t)
|
|
7
|
+
});
|
|
8
|
+
function R({
|
|
9
|
+
itemIds: r,
|
|
10
|
+
orientation: t = "horizontal",
|
|
11
|
+
activeItem: n,
|
|
12
|
+
idBase: e = "",
|
|
13
|
+
includePanelLinks: a = !0,
|
|
14
|
+
externalPanelId: u
|
|
15
|
+
}) {
|
|
16
|
+
const {
|
|
17
|
+
setFocusedId: f,
|
|
18
|
+
handleKeyDown: p,
|
|
19
|
+
getRovingItemProps: b,
|
|
20
|
+
getContainerProps: g
|
|
21
|
+
} = d({
|
|
22
|
+
itemIds: r,
|
|
23
|
+
defaultFocusedId: n,
|
|
24
|
+
orientation: t,
|
|
25
|
+
loop: !0,
|
|
26
|
+
role: "tablist"
|
|
27
|
+
}), P = c((o) => {
|
|
28
|
+
const {
|
|
29
|
+
ref: $,
|
|
30
|
+
tabIndex: x,
|
|
31
|
+
onFocus: F
|
|
32
|
+
} = b(o), y = n === o;
|
|
33
|
+
return {
|
|
34
|
+
ref: $,
|
|
35
|
+
tabIndex: x,
|
|
36
|
+
onFocus: F,
|
|
37
|
+
id: s(o, e),
|
|
38
|
+
...a && {
|
|
39
|
+
"aria-controls": u ?? l(o, e)
|
|
40
|
+
},
|
|
41
|
+
"aria-selected": y,
|
|
42
|
+
role: "tab"
|
|
43
|
+
};
|
|
44
|
+
}, [n, b, e, a, u]), T = c((o) => ({
|
|
45
|
+
id: l(o, e),
|
|
46
|
+
role: "tabpanel",
|
|
47
|
+
"aria-labelledby": s(o, e)
|
|
48
|
+
}), [e]);
|
|
49
|
+
return {
|
|
50
|
+
getTabProps: P,
|
|
51
|
+
getPanelProps: T,
|
|
52
|
+
getTabListProps: g,
|
|
53
|
+
setFocusedTabId: f,
|
|
54
|
+
handleKeyDown: p,
|
|
55
|
+
getPanelId: (o) => l(o, e),
|
|
56
|
+
getTabId: (o) => s(o, e)
|
|
57
|
+
};
|
|
10
58
|
}
|
|
11
59
|
export {
|
|
12
|
-
l as
|
|
60
|
+
l as getPanelId,
|
|
61
|
+
s as getTabId,
|
|
62
|
+
C as getTabPanelProps,
|
|
63
|
+
R as useTabsA11y
|
|
13
64
|
};
|
|
14
65
|
//# sourceMappingURL=index77.js.map
|
package/dist/index77.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index77.js","sources":["../src/utils/a11y/
|
|
1
|
+
{"version":3,"file":"index77.js","sources":["../src/utils/a11y/useTabsA11y.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useRovingFocus } from './useRovingFocus';\n\nexport const getTabId = (itemId: string, idBase: string) => {\n const suffix = idBase ? `-${idBase}` : '';\n return `tab${suffix}-${itemId}`;\n};\n\nexport const getPanelId = (itemId: string, idBase: string) => {\n const suffix = idBase ? `-${idBase}` : '';\n return `panel${suffix}-${itemId}`;\n};\n\nexport const getTabPanelProps = (tabId: string, idBase: string) => ({\n id: getPanelId(tabId, idBase),\n role: 'tabpanel' as const,\n 'aria-labelledby': getTabId(tabId, idBase)\n});\n\ninterface UseTabsA11yProps {\n itemIds: string[];\n orientation?: 'horizontal' | 'vertical';\n activeItem: string;\n idBase?: string;\n includePanelLinks?: boolean; // Set to false when consumer manages their own panels externally\n externalPanelId?: string; // When set, all tabs point aria-controls to this single external panel id\n}\n\ninterface TabA11yProps {\n ref: (el: HTMLElement | null) => void;\n tabIndex: number;\n onFocus: () => void;\n id: string;\n 'aria-controls'?: string;\n 'aria-selected': boolean;\n role: 'tab';\n}\n\ninterface PanelA11yProps {\n id: string;\n role: 'tabpanel';\n 'aria-labelledby': string;\n}\n\n/**\n * Hook for managing Tabs (NavigationBar) focus and ARIA props.\n * Handles roving tabindex pattern and generates tab-specific ARIA attributes.\n * \n * @example\n * const itemIds = useMemo(() => navigationItems.map(item => item.id), [navigationItems]);\n * const { getTabProps, setFocusedTabId, handleKeyDown } = useTabsA11y({\n * itemIds,\n * activeNavigationItem: 'tab-1',\n * orientation: 'horizontal',\n * idBase: 'settings'\n * });\n */\nexport function useTabsA11y({\n itemIds,\n orientation = 'horizontal',\n activeItem,\n idBase = '',\n includePanelLinks = true,\n externalPanelId\n}: UseTabsA11yProps) {\n const {\n setFocusedId: setFocusedTabId,\n handleKeyDown,\n getRovingItemProps,\n getContainerProps\n } = useRovingFocus({\n itemIds,\n defaultFocusedId: activeItem,\n orientation,\n loop: true,\n role: 'tablist'\n });\n\n /**\n * Get all props needed for a tab button.\n * Combines roving focus props (ref, tabIndex) with tab-specific ARIA attributes.\n */\n const getTabProps = useCallback((itemId: string): TabA11yProps => {\n const { ref, tabIndex, onFocus } = getRovingItemProps(itemId);\n const isSelected = activeItem === itemId;\n\n return {\n ref,\n tabIndex,\n onFocus,\n id: getTabId(itemId, idBase),\n ...(includePanelLinks && { 'aria-controls': externalPanelId ?? getPanelId(itemId, idBase) }),\n 'aria-selected': isSelected,\n role: 'tab'\n };\n }, [activeItem, getRovingItemProps, idBase, includePanelLinks, externalPanelId]);\n\n /**\n * Get all props needed for a tab panel.\n * Provides ARIA attributes to associate the panel with its tab.\n */\n const getPanelProps = useCallback((itemId: string): PanelA11yProps => {\n return {\n id: getPanelId(itemId, idBase),\n role: 'tabpanel',\n 'aria-labelledby': getTabId(itemId, idBase)\n };\n }, [idBase]);\n\n return {\n getTabProps,\n getPanelProps,\n getTabListProps: getContainerProps,\n setFocusedTabId,\n handleKeyDown,\n getPanelId: (itemId: string) => getPanelId(itemId, idBase),\n getTabId: (itemId: string) => getTabId(itemId, idBase)\n };\n}\n\n"],"names":["useCallback","useRovingFocus","getTabId","itemId","idBase","getPanelId","getTabPanelProps","tabId","id","role","useTabsA11y","itemIds","orientation","activeItem","includePanelLinks","externalPanelId","setFocusedId","setFocusedTabId","handleKeyDown","getRovingItemProps","getContainerProps","defaultFocusedId","loop","getTabProps","ref","tabIndex","onFocus","isSelected","getPanelProps","getTabListProps"],"mappings":"AAGO,SAAA,eAAAA,SAAA;AAAA,SAAA,kBAAAC,SAAA;AAAA,MAAMC,IAAWA,CAACC,GAAgBC,MAEhC,MADQA,IAAS,IAAIA,CAAM,KAAK,EACpB,IAAID,CAAM,IAGlBE,IAAaA,CAACF,GAAgBC,MAElC,QADQA,IAAS,IAAIA,CAAM,KAAK,EAClB,IAAID,CAAM,IAGpBG,IAAmBA,CAACC,GAAeH,OAAoB;AAAA,EAClEI,IAAIH,EAAWE,GAAOH,CAAM;AAAA,EAC5BK,MAAM;AAAA,EACN,mBAAmBP,EAASK,GAAOH,CAAM;AAC3C;AAwCO,SAASM,EAAY;AAAA,EAC1BC,SAAAA;AAAAA,EACAC,aAAAA,IAAc;AAAA,EACdC,YAAAA;AAAAA,EACAT,QAAAA,IAAS;AAAA,EACTU,mBAAAA,IAAoB;AAAA,EACpBC,iBAAAA;AACgB,GAAG;AACnB,QAAM;AAAA,IACJC,cAAcC;AAAAA,IACdC,eAAAA;AAAAA,IACAC,oBAAAA;AAAAA,IACAC,mBAAAA;AAAAA,EAAAA,IACEnB,EAAe;AAAA,IACjBU,SAAAA;AAAAA,IACAU,kBAAkBR;AAAAA,IAClBD,aAAAA;AAAAA,IACAU,MAAM;AAAA,IACNb,MAAM;AAAA,EAAA,CACP,GAMKc,IAAcvB,EAAY,CAACG,MAAiC;AAChE,UAAM;AAAA,MAAEqB,KAAAA;AAAAA,MAAKC,UAAAA;AAAAA,MAAUC,SAAAA;AAAAA,IAAAA,IAAYP,EAAmBhB,CAAM,GACtDwB,IAAad,MAAeV;AAElC,WAAO;AAAA,MACLqB,KAAAA;AAAAA,MACAC,UAAAA;AAAAA,MACAC,SAAAA;AAAAA,MACAlB,IAAIN,EAASC,GAAQC,CAAM;AAAA,MAC3B,GAAIU,KAAqB;AAAA,QAAE,iBAAiBC,KAAmBV,EAAWF,GAAQC,CAAM;AAAA,MAAA;AAAA,MACxF,iBAAiBuB;AAAAA,MACjBlB,MAAM;AAAA,IAAA;AAAA,EAEV,GAAG,CAACI,GAAYM,GAAoBf,GAAQU,GAAmBC,CAAe,CAAC,GAMzEa,IAAgB5B,EAAY,CAACG,OAC1B;AAAA,IACLK,IAAIH,EAAWF,GAAQC,CAAM;AAAA,IAC7BK,MAAM;AAAA,IACN,mBAAmBP,EAASC,GAAQC,CAAM;AAAA,EAAA,IAE3C,CAACA,CAAM,CAAC;AAEX,SAAO;AAAA,IACLmB,aAAAA;AAAAA,IACAK,eAAAA;AAAAA,IACAC,iBAAiBT;AAAAA,IACjBH,iBAAAA;AAAAA,IACAC,eAAAA;AAAAA,IACAb,YAAYA,CAACF,MAAmBE,EAAWF,GAAQC,CAAM;AAAA,IACzDF,UAAUA,CAACC,MAAmBD,EAASC,GAAQC,CAAM;AAAA,EAAA;AAEzD;"}
|