@compa11y/react 0.1.0

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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/dist/chunk-2S4C6FGA.js +380 -0
  4. package/dist/chunk-2S4C6FGA.js.map +1 -0
  5. package/dist/chunk-52J4Z3QD.cjs +45 -0
  6. package/dist/chunk-52J4Z3QD.cjs.map +1 -0
  7. package/dist/chunk-C7QK2I7H.js +373 -0
  8. package/dist/chunk-C7QK2I7H.js.map +1 -0
  9. package/dist/chunk-D2UMS62N.cjs +245 -0
  10. package/dist/chunk-D2UMS62N.cjs.map +1 -0
  11. package/dist/chunk-E265U2RK.js +234 -0
  12. package/dist/chunk-E265U2RK.js.map +1 -0
  13. package/dist/chunk-E4XJRXWM.js +215 -0
  14. package/dist/chunk-E4XJRXWM.js.map +1 -0
  15. package/dist/chunk-GDLOJH6K.cjs +110 -0
  16. package/dist/chunk-GDLOJH6K.cjs.map +1 -0
  17. package/dist/chunk-IR46CNNY.cjs +329 -0
  18. package/dist/chunk-IR46CNNY.cjs.map +1 -0
  19. package/dist/chunk-JXYOE7SH.js +103 -0
  20. package/dist/chunk-JXYOE7SH.js.map +1 -0
  21. package/dist/chunk-O3YYQZ5O.js +317 -0
  22. package/dist/chunk-O3YYQZ5O.js.map +1 -0
  23. package/dist/chunk-OIVTOU4Z.cjs +386 -0
  24. package/dist/chunk-OIVTOU4Z.cjs.map +1 -0
  25. package/dist/chunk-OND5B7UG.js +85 -0
  26. package/dist/chunk-OND5B7UG.js.map +1 -0
  27. package/dist/chunk-R4FR6M6I.cjs +383 -0
  28. package/dist/chunk-R4FR6M6I.cjs.map +1 -0
  29. package/dist/chunk-RBDQCIS7.cjs +89 -0
  30. package/dist/chunk-RBDQCIS7.cjs.map +1 -0
  31. package/dist/chunk-SOBS7MIH.cjs +220 -0
  32. package/dist/chunk-SOBS7MIH.cjs.map +1 -0
  33. package/dist/chunk-WURPAE3R.js +41 -0
  34. package/dist/chunk-WURPAE3R.js.map +1 -0
  35. package/dist/components/combobox/index.cjs +31 -0
  36. package/dist/components/combobox/index.cjs.map +1 -0
  37. package/dist/components/combobox/index.d.cts +55 -0
  38. package/dist/components/combobox/index.d.ts +55 -0
  39. package/dist/components/combobox/index.js +6 -0
  40. package/dist/components/combobox/index.js.map +1 -0
  41. package/dist/components/dialog/index.cjs +46 -0
  42. package/dist/components/dialog/index.cjs.map +1 -0
  43. package/dist/components/dialog/index.d.cts +84 -0
  44. package/dist/components/dialog/index.d.ts +84 -0
  45. package/dist/components/dialog/index.js +5 -0
  46. package/dist/components/dialog/index.js.map +1 -0
  47. package/dist/components/menu/index.cjs +46 -0
  48. package/dist/components/menu/index.cjs.map +1 -0
  49. package/dist/components/menu/index.d.cts +80 -0
  50. package/dist/components/menu/index.d.ts +80 -0
  51. package/dist/components/menu/index.js +5 -0
  52. package/dist/components/menu/index.js.map +1 -0
  53. package/dist/components/tabs/index.cjs +35 -0
  54. package/dist/components/tabs/index.cjs.map +1 -0
  55. package/dist/components/tabs/index.d.cts +65 -0
  56. package/dist/components/tabs/index.d.ts +65 -0
  57. package/dist/components/tabs/index.js +6 -0
  58. package/dist/components/tabs/index.js.map +1 -0
  59. package/dist/components/toast/index.cjs +24 -0
  60. package/dist/components/toast/index.cjs.map +1 -0
  61. package/dist/components/toast/index.d.cts +49 -0
  62. package/dist/components/toast/index.d.ts +49 -0
  63. package/dist/components/toast/index.js +3 -0
  64. package/dist/components/toast/index.js.map +1 -0
  65. package/dist/index.cjs +702 -0
  66. package/dist/index.cjs.map +1 -0
  67. package/dist/index.d.cts +402 -0
  68. package/dist/index.d.ts +402 -0
  69. package/dist/index.js +430 -0
  70. package/dist/index.js.map +1 -0
  71. package/package.json +99 -0
@@ -0,0 +1,215 @@
1
+ import { createContext, forwardRef, useState, useRef, useCallback, useEffect, useContext } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { announceAssertive, announce } from '@compa11y/core';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ // src/components/toast/toast.tsx
7
+ var ToastContext = createContext(null);
8
+ function useToast() {
9
+ const context = useContext(ToastContext);
10
+ if (!context) {
11
+ throw new Error("useToast must be used within a ToastProvider");
12
+ }
13
+ return context;
14
+ }
15
+ function ToastProvider({
16
+ children,
17
+ duration = 5e3,
18
+ maxToasts = 5
19
+ }) {
20
+ const [toasts, setToasts] = useState([]);
21
+ const toastIdCounter = useRef(0);
22
+ const addToast = useCallback(
23
+ (toast) => {
24
+ const id = `toast-${++toastIdCounter.current}`;
25
+ const newToast = {
26
+ ...toast,
27
+ id,
28
+ duration: toast.duration ?? duration
29
+ };
30
+ setToasts((prev) => {
31
+ const updated = [...prev, newToast];
32
+ return updated.slice(-maxToasts);
33
+ });
34
+ const message = toast.title ? `${toast.title}. ${toast.description || ""}` : toast.description || "";
35
+ if (toast.type === "error") {
36
+ announceAssertive(message);
37
+ } else {
38
+ announce(message, { politeness: "polite" });
39
+ }
40
+ return id;
41
+ },
42
+ [duration, maxToasts]
43
+ );
44
+ const removeToast = useCallback((id) => {
45
+ setToasts((prev) => prev.filter((t) => t.id !== id));
46
+ }, []);
47
+ const updateToast = useCallback(
48
+ (id, updates) => {
49
+ setToasts(
50
+ (prev) => prev.map((t) => t.id === id ? { ...t, ...updates } : t)
51
+ );
52
+ },
53
+ []
54
+ );
55
+ return /* @__PURE__ */ jsx(
56
+ ToastContext.Provider,
57
+ {
58
+ value: { toasts, addToast, removeToast, updateToast },
59
+ children
60
+ }
61
+ );
62
+ }
63
+ var positionStyles = {
64
+ "top-left": { top: 0, left: 0 },
65
+ "top-center": { top: 0, left: "50%", transform: "translateX(-50%)" },
66
+ "top-right": { top: 0, right: 0 },
67
+ "bottom-left": { bottom: 0, left: 0 },
68
+ "bottom-center": { bottom: 0, left: "50%", transform: "translateX(-50%)" },
69
+ "bottom-right": { bottom: 0, right: 0 }
70
+ };
71
+ var ToastViewport = forwardRef(
72
+ function ToastViewport2({
73
+ position = "bottom-right",
74
+ label = "Notifications",
75
+ style,
76
+ children,
77
+ ...props
78
+ }, ref) {
79
+ const { toasts, removeToast } = useToast();
80
+ const viewport = /* @__PURE__ */ jsxs(
81
+ "div",
82
+ {
83
+ ref,
84
+ role: "region",
85
+ "aria-label": label,
86
+ "aria-live": "polite",
87
+ "aria-relevant": "additions removals",
88
+ tabIndex: -1,
89
+ style: {
90
+ position: "fixed",
91
+ zIndex: 9999,
92
+ padding: "1rem",
93
+ display: "flex",
94
+ flexDirection: "column",
95
+ gap: "0.5rem",
96
+ ...positionStyles[position],
97
+ ...style
98
+ },
99
+ "data-compa11y-toast-viewport": true,
100
+ "data-position": position,
101
+ ...props,
102
+ children: [
103
+ toasts.map((toast) => /* @__PURE__ */ jsx(
104
+ ToastItem,
105
+ {
106
+ toast,
107
+ onClose: () => removeToast(toast.id)
108
+ },
109
+ toast.id
110
+ )),
111
+ children
112
+ ]
113
+ }
114
+ );
115
+ return createPortal(viewport, document.body);
116
+ }
117
+ );
118
+ function ToastItem({ toast, onClose }) {
119
+ const [isVisible, setIsVisible] = useState(true);
120
+ const [, setIsPaused] = useState(false);
121
+ const timerRef = useRef(null);
122
+ const remainingRef = useRef(toast.duration || 5e3);
123
+ const startTimeRef = useRef(Date.now());
124
+ const startTimer = useCallback(() => {
125
+ if (toast.duration === 0) return;
126
+ startTimeRef.current = Date.now();
127
+ timerRef.current = setTimeout(() => {
128
+ setIsVisible(false);
129
+ setTimeout(onClose, 200);
130
+ }, remainingRef.current);
131
+ }, [toast.duration, onClose]);
132
+ const pauseTimer = useCallback(() => {
133
+ if (timerRef.current) {
134
+ clearTimeout(timerRef.current);
135
+ remainingRef.current -= Date.now() - startTimeRef.current;
136
+ }
137
+ }, []);
138
+ useEffect(() => {
139
+ startTimer();
140
+ return () => {
141
+ if (timerRef.current) {
142
+ clearTimeout(timerRef.current);
143
+ }
144
+ };
145
+ }, [startTimer]);
146
+ const handleMouseEnter = () => {
147
+ setIsPaused(true);
148
+ pauseTimer();
149
+ };
150
+ const handleMouseLeave = () => {
151
+ setIsPaused(false);
152
+ startTimer();
153
+ };
154
+ const handleKeyDown = (event) => {
155
+ if (event.key === "Escape") {
156
+ onClose();
157
+ }
158
+ };
159
+ return /* @__PURE__ */ jsxs(
160
+ "div",
161
+ {
162
+ role: "alert",
163
+ "aria-atomic": "true",
164
+ tabIndex: 0,
165
+ onMouseEnter: handleMouseEnter,
166
+ onMouseLeave: handleMouseLeave,
167
+ onKeyDown: handleKeyDown,
168
+ "data-type": toast.type,
169
+ "data-visible": isVisible,
170
+ "data-compa11y-toast": true,
171
+ children: [
172
+ toast.title && /* @__PURE__ */ jsx("div", { "data-compa11y-toast-title": true, children: toast.title }),
173
+ toast.description && /* @__PURE__ */ jsx("div", { "data-compa11y-toast-description": true, children: toast.description }),
174
+ toast.action && /* @__PURE__ */ jsx(
175
+ "button",
176
+ {
177
+ type: "button",
178
+ tabIndex: 0,
179
+ onClick: () => {
180
+ toast.action?.onClick();
181
+ onClose();
182
+ },
183
+ "data-compa11y-toast-action": true,
184
+ children: toast.action.label
185
+ }
186
+ ),
187
+ /* @__PURE__ */ jsx(
188
+ "button",
189
+ {
190
+ type: "button",
191
+ tabIndex: 0,
192
+ "aria-label": "Dismiss",
193
+ onClick: onClose,
194
+ "data-compa11y-toast-close": true,
195
+ children: "\xD7"
196
+ }
197
+ )
198
+ ]
199
+ }
200
+ );
201
+ }
202
+ function useToastHelpers() {
203
+ const { addToast } = useToast();
204
+ return {
205
+ toast: addToast,
206
+ success: (title, description) => addToast({ type: "success", title, description }),
207
+ error: (title, description) => addToast({ type: "error", title, description }),
208
+ warning: (title, description) => addToast({ type: "warning", title, description }),
209
+ info: (title, description) => addToast({ type: "info", title, description })
210
+ };
211
+ }
212
+
213
+ export { ToastProvider, ToastViewport, useToast, useToastHelpers };
214
+ //# sourceMappingURL=chunk-E4XJRXWM.js.map
215
+ //# sourceMappingURL=chunk-E4XJRXWM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/toast/toast.tsx"],"names":["ToastViewport"],"mappings":"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","file":"chunk-E4XJRXWM.js","sourcesContent":["import React, {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { announce, announceAssertive } from '@compa11y/core';\nexport type ToastType = 'info' | 'success' | 'warning' | 'error';\n\nexport interface Toast {\n id: string;\n title?: string;\n description?: string;\n type: ToastType;\n duration?: number;\n action?: {\n label: string;\n onClick: () => void;\n };\n}\n\ninterface ToastContextValue {\n toasts: Toast[];\n addToast: (toast: Omit<Toast, 'id'>) => string;\n removeToast: (id: string) => void;\n updateToast: (id: string, toast: Partial<Omit<Toast, 'id'>>) => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast() {\n const context = useContext(ToastContext);\n if (!context) {\n throw new Error('useToast must be used within a ToastProvider');\n }\n return context;\n}\n\nexport interface ToastProviderProps {\n children: React.ReactNode;\n /** Default duration for toasts in ms */\n duration?: number;\n /** Maximum number of visible toasts */\n maxToasts?: number;\n}\n\nexport function ToastProvider({\n children,\n duration = 5000,\n maxToasts = 5,\n}: ToastProviderProps) {\n const [toasts, setToasts] = useState<Toast[]>([]);\n const toastIdCounter = useRef(0);\n\n const addToast = useCallback(\n (toast: Omit<Toast, 'id'>): string => {\n const id = `toast-${++toastIdCounter.current}`;\n const newToast: Toast = {\n ...toast,\n id,\n duration: toast.duration ?? duration,\n };\n\n setToasts((prev) => {\n const updated = [...prev, newToast];\n // Limit visible toasts\n return updated.slice(-maxToasts);\n });\n\n // Announce to screen readers\n const message = toast.title\n ? `${toast.title}. ${toast.description || ''}`\n : toast.description || '';\n\n if (toast.type === 'error') {\n announceAssertive(message);\n } else {\n announce(message, { politeness: 'polite' });\n }\n\n return id;\n },\n [duration, maxToasts]\n );\n\n const removeToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const updateToast = useCallback(\n (id: string, updates: Partial<Omit<Toast, 'id'>>) => {\n setToasts((prev) =>\n prev.map((t) => (t.id === id ? { ...t, ...updates } : t))\n );\n },\n []\n );\n\n return (\n <ToastContext.Provider\n value={{ toasts, addToast, removeToast, updateToast }}\n >\n {children}\n </ToastContext.Provider>\n );\n}\n\nexport interface ToastViewportProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Position of the toast container */\n position?:\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n /** Label for screen readers */\n label?: string;\n}\n\nconst positionStyles: Record<string, React.CSSProperties> = {\n 'top-left': { top: 0, left: 0 },\n 'top-center': { top: 0, left: '50%', transform: 'translateX(-50%)' },\n 'top-right': { top: 0, right: 0 },\n 'bottom-left': { bottom: 0, left: 0 },\n 'bottom-center': { bottom: 0, left: '50%', transform: 'translateX(-50%)' },\n 'bottom-right': { bottom: 0, right: 0 },\n};\n\nexport const ToastViewport = forwardRef<HTMLDivElement, ToastViewportProps>(\n function ToastViewport(\n {\n position = 'bottom-right',\n label = 'Notifications',\n style,\n children,\n ...props\n },\n ref\n ) {\n const { toasts, removeToast } = useToast();\n\n const viewport = (\n <div\n ref={ref}\n role=\"region\"\n aria-label={label}\n aria-live=\"polite\"\n aria-relevant=\"additions removals\"\n tabIndex={-1}\n style={{\n position: 'fixed',\n zIndex: 9999,\n padding: '1rem',\n display: 'flex',\n flexDirection: 'column',\n gap: '0.5rem',\n ...positionStyles[position],\n ...style,\n }}\n data-compa11y-toast-viewport\n data-position={position}\n {...props}\n >\n {toasts.map((toast) => (\n <ToastItem\n key={toast.id}\n toast={toast}\n onClose={() => removeToast(toast.id)}\n />\n ))}\n {children}\n </div>\n );\n\n return createPortal(viewport, document.body);\n }\n);\n\ninterface ToastItemProps {\n toast: Toast;\n onClose: () => void;\n}\n\nfunction ToastItem({ toast, onClose }: ToastItemProps) {\n const [isVisible, setIsVisible] = useState(true);\n const [, setIsPaused] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const remainingRef = useRef(toast.duration || 5000);\n const startTimeRef = useRef(Date.now());\n\n const startTimer = useCallback(() => {\n if (toast.duration === 0) return; // Infinite duration\n\n startTimeRef.current = Date.now();\n timerRef.current = setTimeout(() => {\n setIsVisible(false);\n setTimeout(onClose, 200); // Allow exit animation\n }, remainingRef.current);\n }, [toast.duration, onClose]);\n\n const pauseTimer = useCallback(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n remainingRef.current -= Date.now() - startTimeRef.current;\n }\n }, []);\n\n useEffect(() => {\n startTimer();\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n };\n }, [startTimer]);\n\n const handleMouseEnter = () => {\n setIsPaused(true);\n pauseTimer();\n };\n\n const handleMouseLeave = () => {\n setIsPaused(false);\n startTimer();\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose();\n }\n };\n\n return (\n <div\n role=\"alert\"\n aria-atomic=\"true\"\n tabIndex={0}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onKeyDown={handleKeyDown}\n data-type={toast.type}\n data-visible={isVisible}\n data-compa11y-toast\n >\n {toast.title && <div data-compa11y-toast-title>{toast.title}</div>}\n {toast.description && (\n <div data-compa11y-toast-description>{toast.description}</div>\n )}\n {toast.action && (\n <button\n type=\"button\"\n // Safari fix: Ensure button is in tab order\n tabIndex={0}\n onClick={() => {\n toast.action?.onClick();\n onClose();\n }}\n data-compa11y-toast-action\n >\n {toast.action.label}\n </button>\n )}\n <button\n type=\"button\"\n // Safari fix: Ensure button is in tab order\n tabIndex={0}\n aria-label=\"Dismiss\"\n onClick={onClose}\n data-compa11y-toast-close\n >\n ×\n </button>\n </div>\n );\n}\n\n/**\n * Hook for common toast patterns\n */\nexport function useToastHelpers() {\n const { addToast } = useToast();\n\n return {\n toast: addToast,\n success: (title: string, description?: string) =>\n addToast({ type: 'success', title, description }),\n error: (title: string, description?: string) =>\n addToast({ type: 'error', title, description }),\n warning: (title: string, description?: string) =>\n addToast({ type: 'warning', title, description }),\n info: (title: string, description?: string) =>\n addToast({ type: 'info', title, description }),\n };\n}\n"]}
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var core = require('@compa11y/core');
5
+
6
+ // src/hooks/use-keyboard.ts
7
+ function useKeyboard(handlers, options = {}) {
8
+ const {
9
+ preventDefault = true,
10
+ stopPropagation = true,
11
+ disabled = false
12
+ } = options;
13
+ const handlersRef = react.useRef(handlers);
14
+ handlersRef.current = handlers;
15
+ const handleKeyDown = react.useCallback(
16
+ (event) => {
17
+ if (disabled) return;
18
+ const combo = core.getKeyCombo(event.nativeEvent);
19
+ let handler = handlersRef.current[combo];
20
+ if (!handler) {
21
+ const key = core.normalizeKey(event.nativeEvent);
22
+ handler = handlersRef.current[key];
23
+ }
24
+ if (handler) {
25
+ const result = handler(event.nativeEvent);
26
+ if (result !== false) {
27
+ if (preventDefault) {
28
+ event.preventDefault();
29
+ }
30
+ if (stopPropagation) {
31
+ event.stopPropagation();
32
+ }
33
+ }
34
+ }
35
+ },
36
+ [disabled, preventDefault, stopPropagation]
37
+ );
38
+ return {
39
+ onKeyDown: handleKeyDown
40
+ };
41
+ }
42
+ function useMenuKeyboard(options) {
43
+ const { disabled, ...handlers } = options;
44
+ return useKeyboard(core.KeyboardPatterns.menu(handlers), { disabled });
45
+ }
46
+ function useTabsKeyboard(options) {
47
+ const { disabled, ...handlers } = options;
48
+ return useKeyboard(core.KeyboardPatterns.tabs(handlers), { disabled });
49
+ }
50
+ function useGridKeyboard(options) {
51
+ const { disabled, ...handlers } = options;
52
+ return useKeyboard(core.KeyboardPatterns.grid(handlers), { disabled });
53
+ }
54
+ function useTypeAhead(items, onMatch, options = {}) {
55
+ const { timeout = 500, disabled = false } = options;
56
+ const typeAhead = react.useMemo(
57
+ () => core.createTypeAhead(items, { timeout }),
58
+ [items, timeout]
59
+ );
60
+ const onMatchRef = react.useRef(onMatch);
61
+ onMatchRef.current = onMatch;
62
+ const handleKeyDown = react.useCallback(
63
+ (event) => {
64
+ if (disabled) return;
65
+ if (event.key.length !== 1) return;
66
+ if (event.ctrlKey || event.altKey || event.metaKey) return;
67
+ const match = typeAhead.type(event.key);
68
+ if (match) {
69
+ onMatchRef.current(match);
70
+ }
71
+ },
72
+ [disabled, typeAhead]
73
+ );
74
+ return {
75
+ onKeyDown: handleKeyDown,
76
+ reset: typeAhead.reset,
77
+ getSearch: typeAhead.getSearch
78
+ };
79
+ }
80
+ function useKeyPressed(targetKey) {
81
+ const [pressed, setPressed] = react.useState(false);
82
+ react.useEffect(() => {
83
+ const handleKeyDown = (event) => {
84
+ if (core.normalizeKey(event) === targetKey) {
85
+ setPressed(true);
86
+ }
87
+ };
88
+ const handleKeyUp = (event) => {
89
+ if (core.normalizeKey(event) === targetKey) {
90
+ setPressed(false);
91
+ }
92
+ };
93
+ document.addEventListener("keydown", handleKeyDown);
94
+ document.addEventListener("keyup", handleKeyUp);
95
+ return () => {
96
+ document.removeEventListener("keydown", handleKeyDown);
97
+ document.removeEventListener("keyup", handleKeyUp);
98
+ };
99
+ }, [targetKey]);
100
+ return pressed;
101
+ }
102
+
103
+ exports.useGridKeyboard = useGridKeyboard;
104
+ exports.useKeyPressed = useKeyPressed;
105
+ exports.useKeyboard = useKeyboard;
106
+ exports.useMenuKeyboard = useMenuKeyboard;
107
+ exports.useTabsKeyboard = useTabsKeyboard;
108
+ exports.useTypeAhead = useTypeAhead;
109
+ //# sourceMappingURL=chunk-GDLOJH6K.cjs.map
110
+ //# sourceMappingURL=chunk-GDLOJH6K.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-keyboard.ts"],"names":["useRef","useCallback","getKeyCombo","normalizeKey","KeyboardPatterns","useMemo","createTypeAhead","useState","useEffect"],"mappings":";;;;;;AA0BO,SAAS,WAAA,CACd,QAAA,EACA,OAAA,GAII,EAAC,EACL;AACA,EAAA,MAAM;AAAA,IACJ,cAAA,GAAiB,IAAA;AAAA,IACjB,eAAA,GAAkB,IAAA;AAAA,IAClB,QAAA,GAAW;AAAA,GACb,GAAI,OAAA;AAGJ,EAAA,MAAM,WAAA,GAAcA,aAAO,QAAQ,CAAA;AACnC,EAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAEtB,EAAA,MAAM,aAAA,GAAgBC,iBAAA;AAAA,IACpB,CAAC,KAAA,KAA+B;AAC9B,MAAA,IAAI,QAAA,EAAU;AAGd,MAAA,MAAM,KAAA,GAAQC,gBAAA,CAAY,KAAA,CAAM,WAAW,CAAA;AAC3C,MAAA,IAAI,OAAA,GAAU,WAAA,CAAY,OAAA,CAAQ,KAAK,CAAA;AAGvC,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,MAAM,GAAA,GAAMC,iBAAA,CAAa,KAAA,CAAM,WAAW,CAAA;AAC1C,QAAA,OAAA,GAAU,WAAA,CAAY,QAAQ,GAAG,CAAA;AAAA,MACnC;AAEA,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,KAAA,CAAM,WAAW,CAAA;AACxC,QAAA,IAAI,WAAW,KAAA,EAAO;AACpB,UAAA,IAAI,cAAA,EAAgB;AAClB,YAAA,KAAA,CAAM,cAAA,EAAe;AAAA,UACvB;AACA,UAAA,IAAI,eAAA,EAAiB;AACnB,YAAA,KAAA,CAAM,eAAA,EAAgB;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA,CAAC,QAAA,EAAU,cAAA,EAAgB,eAAe;AAAA,GAC5C;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW;AAAA,GACb;AACF;AAKO,SAAS,gBAAgB,OAAA,EAQ7B;AACD,EAAA,MAAM,EAAE,QAAA,EAAU,GAAG,QAAA,EAAS,GAAI,OAAA;AAClC,EAAA,OAAO,YAAYC,qBAAA,CAAiB,IAAA,CAAK,QAAQ,CAAA,EAAG,EAAE,UAAU,CAAA;AAClE;AAEO,SAAS,gBAAgB,OAAA,EAM7B;AACD,EAAA,MAAM,EAAE,QAAA,EAAU,GAAG,QAAA,EAAS,GAAI,OAAA;AAClC,EAAA,OAAO,YAAYA,qBAAA,CAAiB,IAAA,CAAK,QAAQ,CAAA,EAAG,EAAE,UAAU,CAAA;AAClE;AAEO,SAAS,gBAAgB,OAAA,EAU7B;AACD,EAAA,MAAM,EAAE,QAAA,EAAU,GAAG,QAAA,EAAS,GAAI,OAAA;AAClC,EAAA,OAAO,YAAYA,qBAAA,CAAiB,IAAA,CAAK,QAAQ,CAAA,EAAG,EAAE,UAAU,CAAA;AAClE;AAiBO,SAAS,YAAA,CACd,KAAA,EACA,OAAA,EACA,OAAA,GAAoD,EAAC,EACrD;AACA,EAAA,MAAM,EAAE,OAAA,GAAU,GAAA,EAAK,QAAA,GAAW,OAAM,GAAI,OAAA;AAE5C,EAAA,MAAM,SAAA,GAAYC,aAAA;AAAA,IAChB,MAAMC,oBAAA,CAAgB,KAAA,EAAO,EAAE,SAAS,CAAA;AAAA,IACxC,CAAC,OAAO,OAAO;AAAA,GACjB;AAEA,EAAA,MAAM,UAAA,GAAaN,aAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,aAAA,GAAgBC,iBAAA;AAAA,IACpB,CAAC,KAAA,KAA+B;AAC9B,MAAA,IAAI,QAAA,EAAU;AAGd,MAAA,IAAI,KAAA,CAAM,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG;AAG5B,MAAA,IAAI,KAAA,CAAM,OAAA,IAAW,KAAA,CAAM,MAAA,IAAU,MAAM,OAAA,EAAS;AAEpD,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACtC,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,UAAA,CAAW,QAAQ,KAAK,CAAA;AAAA,MAC1B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,SAAS;AAAA,GACtB;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,aAAA;AAAA,IACX,OAAO,SAAA,CAAU,KAAA;AAAA,IACjB,WAAW,SAAA,CAAU;AAAA,GACvB;AACF;AAMO,SAAS,cAAc,SAAA,EAA4B;AACxD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIM,eAAS,KAAK,CAAA;AAE5C,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,aAAA,GAAgB,CAAC,KAAA,KAAyB;AAC9C,MAAA,IAAIL,iBAAA,CAAa,KAAK,CAAA,KAAM,SAAA,EAAW;AACrC,QAAA,UAAA,CAAW,IAAI,CAAA;AAAA,MACjB;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAAyB;AAC5C,MAAA,IAAIA,iBAAA,CAAa,KAAK,CAAA,KAAM,SAAA,EAAW;AACrC,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,WAAW,aAAa,CAAA;AAClD,IAAA,QAAA,CAAS,gBAAA,CAAiB,SAAS,WAAW,CAAA;AAE9C,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,WAAW,aAAa,CAAA;AACrD,MAAA,QAAA,CAAS,mBAAA,CAAoB,SAAS,WAAW,CAAA;AAAA,IACnD,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAEd,EAAA,OAAO,OAAA;AACT","file":"chunk-GDLOJH6K.cjs","sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n KeyboardPatterns,\n createTypeAhead,\n normalizeKey,\n getKeyCombo,\n type KeyboardHandlers,\n} from '@compa11y/core';\n\n/**\n * Hook for keyboard event handling\n *\n * @example\n * ```tsx\n * function Menu({ items, onSelect }) {\n * const keyboardProps = useKeyboard({\n * ArrowDown: () => focusNext(),\n * ArrowUp: () => focusPrevious(),\n * Enter: () => onSelect(focused),\n * Escape: () => close(),\n * });\n *\n * return <ul {...keyboardProps}>...</ul>;\n * }\n * ```\n */\nexport function useKeyboard(\n handlers: KeyboardHandlers,\n options: {\n preventDefault?: boolean;\n stopPropagation?: boolean;\n disabled?: boolean;\n } = {}\n) {\n const {\n preventDefault = true,\n stopPropagation = true,\n disabled = false,\n } = options;\n\n // Memoize handlers to prevent unnecessary re-renders\n const handlersRef = useRef(handlers);\n handlersRef.current = handlers;\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (disabled) return;\n\n // Try key combo first\n const combo = getKeyCombo(event.nativeEvent);\n let handler = handlersRef.current[combo];\n\n // Fall back to simple key\n if (!handler) {\n const key = normalizeKey(event.nativeEvent);\n handler = handlersRef.current[key];\n }\n\n if (handler) {\n const result = handler(event.nativeEvent);\n if (result !== false) {\n if (preventDefault) {\n event.preventDefault();\n }\n if (stopPropagation) {\n event.stopPropagation();\n }\n }\n }\n },\n [disabled, preventDefault, stopPropagation]\n );\n\n return {\n onKeyDown: handleKeyDown,\n };\n}\n\n/**\n * Pre-built keyboard patterns for common widgets\n */\nexport function useMenuKeyboard(options: {\n onUp?: () => void;\n onDown?: () => void;\n onEnter?: () => void;\n onEscape?: () => void;\n onHome?: () => void;\n onEnd?: () => void;\n disabled?: boolean;\n}) {\n const { disabled, ...handlers } = options;\n return useKeyboard(KeyboardPatterns.menu(handlers), { disabled });\n}\n\nexport function useTabsKeyboard(options: {\n onLeft?: () => void;\n onRight?: () => void;\n onHome?: () => void;\n onEnd?: () => void;\n disabled?: boolean;\n}) {\n const { disabled, ...handlers } = options;\n return useKeyboard(KeyboardPatterns.tabs(handlers), { disabled });\n}\n\nexport function useGridKeyboard(options: {\n onUp?: () => void;\n onDown?: () => void;\n onLeft?: () => void;\n onRight?: () => void;\n onHome?: () => void;\n onEnd?: () => void;\n onCtrlHome?: () => void;\n onCtrlEnd?: () => void;\n disabled?: boolean;\n}) {\n const { disabled, ...handlers } = options;\n return useKeyboard(KeyboardPatterns.grid(handlers), { disabled });\n}\n\n/**\n * Hook for type-ahead search in menus/listboxes\n *\n * @example\n * ```tsx\n * function Listbox({ items }) {\n * const { onKeyDown, reset } = useTypeAhead(\n * items.map(i => i.label),\n * (match) => focusItem(match)\n * );\n *\n * return <ul onKeyDown={onKeyDown}>...</ul>;\n * }\n * ```\n */\nexport function useTypeAhead(\n items: string[],\n onMatch: (match: string) => void,\n options: { timeout?: number; disabled?: boolean } = {}\n) {\n const { timeout = 500, disabled = false } = options;\n\n const typeAhead = useMemo(\n () => createTypeAhead(items, { timeout }),\n [items, timeout]\n );\n\n const onMatchRef = useRef(onMatch);\n onMatchRef.current = onMatch;\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (disabled) return;\n\n // Only handle single character keys\n if (event.key.length !== 1) return;\n\n // Ignore if modifier keys are pressed\n if (event.ctrlKey || event.altKey || event.metaKey) return;\n\n const match = typeAhead.type(event.key);\n if (match) {\n onMatchRef.current(match);\n }\n },\n [disabled, typeAhead]\n );\n\n return {\n onKeyDown: handleKeyDown,\n reset: typeAhead.reset,\n getSearch: typeAhead.getSearch,\n };\n}\n\n/**\n * Hook for tracking which key is currently pressed\n * Useful for showing keyboard shortcuts or modifier states\n */\nexport function useKeyPressed(targetKey: string): boolean {\n const [pressed, setPressed] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (normalizeKey(event) === targetKey) {\n setPressed(true);\n }\n };\n\n const handleKeyUp = (event: KeyboardEvent) => {\n if (normalizeKey(event) === targetKey) {\n setPressed(false);\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('keyup', handleKeyUp);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('keyup', handleKeyUp);\n };\n }, [targetKey]);\n\n return pressed;\n}\n\n// Need to import useState\nimport { useState } from 'react';\n"]}