@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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/chunk-2S4C6FGA.js +380 -0
- package/dist/chunk-2S4C6FGA.js.map +1 -0
- package/dist/chunk-52J4Z3QD.cjs +45 -0
- package/dist/chunk-52J4Z3QD.cjs.map +1 -0
- package/dist/chunk-C7QK2I7H.js +373 -0
- package/dist/chunk-C7QK2I7H.js.map +1 -0
- package/dist/chunk-D2UMS62N.cjs +245 -0
- package/dist/chunk-D2UMS62N.cjs.map +1 -0
- package/dist/chunk-E265U2RK.js +234 -0
- package/dist/chunk-E265U2RK.js.map +1 -0
- package/dist/chunk-E4XJRXWM.js +215 -0
- package/dist/chunk-E4XJRXWM.js.map +1 -0
- package/dist/chunk-GDLOJH6K.cjs +110 -0
- package/dist/chunk-GDLOJH6K.cjs.map +1 -0
- package/dist/chunk-IR46CNNY.cjs +329 -0
- package/dist/chunk-IR46CNNY.cjs.map +1 -0
- package/dist/chunk-JXYOE7SH.js +103 -0
- package/dist/chunk-JXYOE7SH.js.map +1 -0
- package/dist/chunk-O3YYQZ5O.js +317 -0
- package/dist/chunk-O3YYQZ5O.js.map +1 -0
- package/dist/chunk-OIVTOU4Z.cjs +386 -0
- package/dist/chunk-OIVTOU4Z.cjs.map +1 -0
- package/dist/chunk-OND5B7UG.js +85 -0
- package/dist/chunk-OND5B7UG.js.map +1 -0
- package/dist/chunk-R4FR6M6I.cjs +383 -0
- package/dist/chunk-R4FR6M6I.cjs.map +1 -0
- package/dist/chunk-RBDQCIS7.cjs +89 -0
- package/dist/chunk-RBDQCIS7.cjs.map +1 -0
- package/dist/chunk-SOBS7MIH.cjs +220 -0
- package/dist/chunk-SOBS7MIH.cjs.map +1 -0
- package/dist/chunk-WURPAE3R.js +41 -0
- package/dist/chunk-WURPAE3R.js.map +1 -0
- package/dist/components/combobox/index.cjs +31 -0
- package/dist/components/combobox/index.cjs.map +1 -0
- package/dist/components/combobox/index.d.cts +55 -0
- package/dist/components/combobox/index.d.ts +55 -0
- package/dist/components/combobox/index.js +6 -0
- package/dist/components/combobox/index.js.map +1 -0
- package/dist/components/dialog/index.cjs +46 -0
- package/dist/components/dialog/index.cjs.map +1 -0
- package/dist/components/dialog/index.d.cts +84 -0
- package/dist/components/dialog/index.d.ts +84 -0
- package/dist/components/dialog/index.js +5 -0
- package/dist/components/dialog/index.js.map +1 -0
- package/dist/components/menu/index.cjs +46 -0
- package/dist/components/menu/index.cjs.map +1 -0
- package/dist/components/menu/index.d.cts +80 -0
- package/dist/components/menu/index.d.ts +80 -0
- package/dist/components/menu/index.js +5 -0
- package/dist/components/menu/index.js.map +1 -0
- package/dist/components/tabs/index.cjs +35 -0
- package/dist/components/tabs/index.cjs.map +1 -0
- package/dist/components/tabs/index.d.cts +65 -0
- package/dist/components/tabs/index.d.ts +65 -0
- package/dist/components/tabs/index.js +6 -0
- package/dist/components/tabs/index.js.map +1 -0
- package/dist/components/toast/index.cjs +24 -0
- package/dist/components/toast/index.cjs.map +1 -0
- package/dist/components/toast/index.d.cts +49 -0
- package/dist/components/toast/index.d.ts +49 -0
- package/dist/components/toast/index.js +3 -0
- package/dist/components/toast/index.js.map +1 -0
- package/dist/index.cjs +702 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +402 -0
- package/dist/index.d.ts +402 -0
- package/dist/index.js +430 -0
- package/dist/index.js.map +1 -0
- 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":"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","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"]}
|