@a11y-core/react 0.1.0-alpha.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/README.md +252 -0
- package/dist/chunk-3WBTHTVK.js +41 -0
- package/dist/chunk-3WBTHTVK.js.map +1 -0
- package/dist/chunk-47MFBHV6.js +85 -0
- package/dist/chunk-47MFBHV6.js.map +1 -0
- package/dist/chunk-7A3IDIUB.cjs +89 -0
- package/dist/chunk-7A3IDIUB.cjs.map +1 -0
- package/dist/chunk-AZFZWGI6.cjs +383 -0
- package/dist/chunk-AZFZWGI6.cjs.map +1 -0
- package/dist/chunk-CTW5D77X.cjs +220 -0
- package/dist/chunk-CTW5D77X.cjs.map +1 -0
- package/dist/chunk-EY73HQNR.js +380 -0
- package/dist/chunk-EY73HQNR.js.map +1 -0
- package/dist/chunk-FJABCNFE.js +215 -0
- package/dist/chunk-FJABCNFE.js.map +1 -0
- package/dist/chunk-FTZ5KCOO.js +326 -0
- package/dist/chunk-FTZ5KCOO.js.map +1 -0
- package/dist/chunk-GS3H4T2O.cjs +106 -0
- package/dist/chunk-GS3H4T2O.cjs.map +1 -0
- package/dist/chunk-N6L4GLFC.cjs +45 -0
- package/dist/chunk-N6L4GLFC.cjs.map +1 -0
- package/dist/chunk-N774QCHE.js +234 -0
- package/dist/chunk-N774QCHE.js.map +1 -0
- package/dist/chunk-OSHIYZCZ.cjs +386 -0
- package/dist/chunk-OSHIYZCZ.cjs.map +1 -0
- package/dist/chunk-SRJSGDIA.js +373 -0
- package/dist/chunk-SRJSGDIA.js.map +1 -0
- package/dist/chunk-THB5U7YC.cjs +338 -0
- package/dist/chunk-THB5U7YC.cjs.map +1 -0
- package/dist/chunk-U6DUSMEA.js +99 -0
- package/dist/chunk-U6DUSMEA.js.map +1 -0
- package/dist/chunk-WDCYEMBO.cjs +245 -0
- package/dist/chunk-WDCYEMBO.cjs.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 +698 -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 +426 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk7A3IDIUB_cjs = require('./chunk-7A3IDIUB.cjs');
|
|
4
|
+
var chunkN6L4GLFC_cjs = require('./chunk-N6L4GLFC.cjs');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var reactDom = require('react-dom');
|
|
7
|
+
var core = require('@a11y-core/core');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
|
|
10
|
+
function useFocusTrap(options = {}) {
|
|
11
|
+
const { active = true, ...trapOptions } = options;
|
|
12
|
+
const containerRef = react.useRef(null);
|
|
13
|
+
const trapRef = react.useRef(null);
|
|
14
|
+
react.useEffect(() => {
|
|
15
|
+
const container = containerRef.current;
|
|
16
|
+
if (!container) return;
|
|
17
|
+
trapRef.current = core.createFocusTrap(container, trapOptions);
|
|
18
|
+
if (active) {
|
|
19
|
+
trapRef.current.activate();
|
|
20
|
+
}
|
|
21
|
+
return () => {
|
|
22
|
+
trapRef.current?.destroy();
|
|
23
|
+
trapRef.current = null;
|
|
24
|
+
};
|
|
25
|
+
}, [
|
|
26
|
+
trapOptions.initialFocus,
|
|
27
|
+
trapOptions.returnFocus,
|
|
28
|
+
trapOptions.clickOutsideDeactivates,
|
|
29
|
+
trapOptions.escapeDeactivates,
|
|
30
|
+
trapOptions.onDeactivate
|
|
31
|
+
]);
|
|
32
|
+
react.useEffect(() => {
|
|
33
|
+
if (!trapRef.current) return;
|
|
34
|
+
if (active) {
|
|
35
|
+
trapRef.current.activate();
|
|
36
|
+
} else {
|
|
37
|
+
trapRef.current.deactivate();
|
|
38
|
+
}
|
|
39
|
+
}, [active]);
|
|
40
|
+
return containerRef;
|
|
41
|
+
}
|
|
42
|
+
function useFocusTrapControls(options = {}) {
|
|
43
|
+
const containerRef = react.useRef(null);
|
|
44
|
+
const trapRef = react.useRef(null);
|
|
45
|
+
const activate = react.useCallback(() => {
|
|
46
|
+
const container = containerRef.current;
|
|
47
|
+
if (!container) return;
|
|
48
|
+
if (!trapRef.current) {
|
|
49
|
+
trapRef.current = core.createFocusTrap(container, options);
|
|
50
|
+
}
|
|
51
|
+
trapRef.current.activate();
|
|
52
|
+
}, [options]);
|
|
53
|
+
const deactivate = react.useCallback(() => {
|
|
54
|
+
trapRef.current?.deactivate();
|
|
55
|
+
}, []);
|
|
56
|
+
const pause = react.useCallback(() => {
|
|
57
|
+
trapRef.current?.pause();
|
|
58
|
+
}, []);
|
|
59
|
+
const unpause = react.useCallback(() => {
|
|
60
|
+
trapRef.current?.unpause();
|
|
61
|
+
}, []);
|
|
62
|
+
react.useEffect(() => {
|
|
63
|
+
return () => {
|
|
64
|
+
trapRef.current?.destroy();
|
|
65
|
+
trapRef.current = null;
|
|
66
|
+
};
|
|
67
|
+
}, []);
|
|
68
|
+
return {
|
|
69
|
+
ref: containerRef,
|
|
70
|
+
activate,
|
|
71
|
+
deactivate,
|
|
72
|
+
pause,
|
|
73
|
+
unpause,
|
|
74
|
+
isActive: () => trapRef.current?.isActive() ?? false,
|
|
75
|
+
isPaused: () => trapRef.current?.isPaused() ?? false
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var DialogContext = react.createContext(null);
|
|
79
|
+
function useDialogContext() {
|
|
80
|
+
const context = react.useContext(DialogContext);
|
|
81
|
+
if (!context) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"Dialog compound components must be used within a Dialog component"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return context;
|
|
87
|
+
}
|
|
88
|
+
var DialogProvider = DialogContext.Provider;
|
|
89
|
+
var warnings = core.createComponentWarnings("Dialog");
|
|
90
|
+
function Dialog({
|
|
91
|
+
open,
|
|
92
|
+
onOpenChange,
|
|
93
|
+
children,
|
|
94
|
+
className,
|
|
95
|
+
initialFocus,
|
|
96
|
+
closeOnOutsideClick = true,
|
|
97
|
+
closeOnEscape = true,
|
|
98
|
+
container,
|
|
99
|
+
"aria-label": ariaLabel,
|
|
100
|
+
"aria-labelledby": ariaLabelledBy,
|
|
101
|
+
unstyled = false
|
|
102
|
+
}) {
|
|
103
|
+
const dialogId = chunkN6L4GLFC_cjs.useId("dialog");
|
|
104
|
+
const titleId = chunkN6L4GLFC_cjs.useId("dialog-title");
|
|
105
|
+
const descriptionId = chunkN6L4GLFC_cjs.useId("dialog-desc");
|
|
106
|
+
const [hasTitle, setHasTitle] = react.useState(false);
|
|
107
|
+
const [hasDescription, setHasDescription] = react.useState(false);
|
|
108
|
+
const close = react.useCallback(() => {
|
|
109
|
+
onOpenChange(false);
|
|
110
|
+
}, [onOpenChange]);
|
|
111
|
+
react.useEffect(() => {
|
|
112
|
+
if (open && !hasTitle && !ariaLabel && !ariaLabelledBy) {
|
|
113
|
+
warnings.warning(
|
|
114
|
+
"Dialog has no accessible title. Add a DialogTitle or aria-label prop.",
|
|
115
|
+
'Use <Dialog.Title> or provide aria-label="..."'
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}, [open, hasTitle, ariaLabel, ariaLabelledBy]);
|
|
119
|
+
const contextValue = {
|
|
120
|
+
isOpen: open,
|
|
121
|
+
close,
|
|
122
|
+
dialogId,
|
|
123
|
+
titleId,
|
|
124
|
+
descriptionId,
|
|
125
|
+
hasTitle,
|
|
126
|
+
hasDescription,
|
|
127
|
+
setHasTitle,
|
|
128
|
+
setHasDescription
|
|
129
|
+
};
|
|
130
|
+
if (!open) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const dialogContent = /* @__PURE__ */ jsxRuntime.jsx(DialogProvider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
134
|
+
DialogOverlay,
|
|
135
|
+
{
|
|
136
|
+
className,
|
|
137
|
+
closeOnOutsideClick,
|
|
138
|
+
closeOnEscape,
|
|
139
|
+
initialFocus,
|
|
140
|
+
ariaLabel,
|
|
141
|
+
ariaLabelledBy,
|
|
142
|
+
unstyled,
|
|
143
|
+
children
|
|
144
|
+
}
|
|
145
|
+
) });
|
|
146
|
+
const portalContainer = container ?? document.body;
|
|
147
|
+
return reactDom.createPortal(dialogContent, portalContainer);
|
|
148
|
+
}
|
|
149
|
+
function DialogOverlay({
|
|
150
|
+
children,
|
|
151
|
+
className,
|
|
152
|
+
closeOnOutsideClick,
|
|
153
|
+
closeOnEscape,
|
|
154
|
+
initialFocus,
|
|
155
|
+
ariaLabel,
|
|
156
|
+
ariaLabelledBy,
|
|
157
|
+
unstyled
|
|
158
|
+
}) {
|
|
159
|
+
const { close, dialogId, titleId, descriptionId, hasTitle, hasDescription } = useDialogContext();
|
|
160
|
+
const { announce } = chunk7A3IDIUB_cjs.useAnnouncer();
|
|
161
|
+
const trapRef = useFocusTrap({
|
|
162
|
+
active: true,
|
|
163
|
+
initialFocus: initialFocus?.current ?? void 0,
|
|
164
|
+
escapeDeactivates: closeOnEscape,
|
|
165
|
+
// Don't use clickOutsideDeactivates - we handle this in handleOverlayClick
|
|
166
|
+
clickOutsideDeactivates: false,
|
|
167
|
+
onDeactivate: close
|
|
168
|
+
});
|
|
169
|
+
react.useEffect(() => {
|
|
170
|
+
announce("Dialog opened", { politeness: "polite" });
|
|
171
|
+
return () => {
|
|
172
|
+
announce("Dialog closed", { politeness: "polite" });
|
|
173
|
+
};
|
|
174
|
+
}, [announce]);
|
|
175
|
+
react.useEffect(() => {
|
|
176
|
+
const originalOverflow = document.body.style.overflow;
|
|
177
|
+
document.body.style.overflow = "hidden";
|
|
178
|
+
return () => {
|
|
179
|
+
document.body.style.overflow = originalOverflow;
|
|
180
|
+
};
|
|
181
|
+
}, []);
|
|
182
|
+
const handleOverlayClick = (event) => {
|
|
183
|
+
if (closeOnOutsideClick && event.target === event.currentTarget) {
|
|
184
|
+
close();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const labelledBy = ariaLabelledBy ?? (hasTitle ? titleId : void 0);
|
|
188
|
+
const describedBy = hasDescription ? descriptionId : void 0;
|
|
189
|
+
const handleDialogClick = (event) => {
|
|
190
|
+
event.stopPropagation();
|
|
191
|
+
};
|
|
192
|
+
const overlayStructuralStyles = {
|
|
193
|
+
position: "fixed",
|
|
194
|
+
inset: 0,
|
|
195
|
+
display: "flex",
|
|
196
|
+
alignItems: "center",
|
|
197
|
+
justifyContent: "center",
|
|
198
|
+
zIndex: 9999
|
|
199
|
+
};
|
|
200
|
+
const overlayVisualStyles = unstyled ? {} : {
|
|
201
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)"
|
|
202
|
+
};
|
|
203
|
+
const overlayStyles = {
|
|
204
|
+
...overlayStructuralStyles,
|
|
205
|
+
...overlayVisualStyles
|
|
206
|
+
};
|
|
207
|
+
const dialogStyles = unstyled ? {} : {
|
|
208
|
+
backgroundColor: "white",
|
|
209
|
+
borderRadius: "8px",
|
|
210
|
+
padding: "1.5rem",
|
|
211
|
+
minWidth: "300px",
|
|
212
|
+
maxWidth: "500px",
|
|
213
|
+
boxShadow: "0 10px 25px rgba(0, 0, 0, 0.2)"
|
|
214
|
+
};
|
|
215
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
216
|
+
"div",
|
|
217
|
+
{
|
|
218
|
+
className,
|
|
219
|
+
style: overlayStyles,
|
|
220
|
+
onClick: handleOverlayClick,
|
|
221
|
+
"data-a11y-core-dialog-overlay": true,
|
|
222
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
223
|
+
"div",
|
|
224
|
+
{
|
|
225
|
+
ref: trapRef,
|
|
226
|
+
id: dialogId,
|
|
227
|
+
role: "dialog",
|
|
228
|
+
"aria-modal": "true",
|
|
229
|
+
"aria-label": ariaLabel,
|
|
230
|
+
"aria-labelledby": labelledBy,
|
|
231
|
+
"aria-describedby": describedBy,
|
|
232
|
+
onClick: handleDialogClick,
|
|
233
|
+
style: dialogStyles,
|
|
234
|
+
"data-a11y-core-dialog": true,
|
|
235
|
+
children
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
var DialogTrigger = react.forwardRef(
|
|
242
|
+
function DialogTrigger2({ children, ...props }, ref) {
|
|
243
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
244
|
+
"button",
|
|
245
|
+
{
|
|
246
|
+
ref,
|
|
247
|
+
type: "button",
|
|
248
|
+
tabIndex: 0,
|
|
249
|
+
"data-a11y-core-dialog-trigger": true,
|
|
250
|
+
...props,
|
|
251
|
+
children
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
var DialogTitle = react.forwardRef(
|
|
257
|
+
function DialogTitle2({ as: Component = "h2", children, ...props }, ref) {
|
|
258
|
+
const { titleId, setHasTitle } = useDialogContext();
|
|
259
|
+
react.useEffect(() => {
|
|
260
|
+
setHasTitle(true);
|
|
261
|
+
return () => setHasTitle(false);
|
|
262
|
+
}, [setHasTitle]);
|
|
263
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Component, { ref, id: titleId, "data-a11y-core-dialog-title": true, ...props, children });
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
var DialogDescription = react.forwardRef(function DialogDescription2({ children, ...props }, ref) {
|
|
267
|
+
const { descriptionId, setHasDescription } = useDialogContext();
|
|
268
|
+
react.useEffect(() => {
|
|
269
|
+
setHasDescription(true);
|
|
270
|
+
return () => setHasDescription(false);
|
|
271
|
+
}, [setHasDescription]);
|
|
272
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
273
|
+
"p",
|
|
274
|
+
{
|
|
275
|
+
ref,
|
|
276
|
+
id: descriptionId,
|
|
277
|
+
"data-a11y-core-dialog-description": true,
|
|
278
|
+
...props,
|
|
279
|
+
children
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
var DialogClose = react.forwardRef(
|
|
284
|
+
function DialogClose2({ children, onClick, ...props }, ref) {
|
|
285
|
+
const { close } = useDialogContext();
|
|
286
|
+
const handleClick = (event) => {
|
|
287
|
+
onClick?.(event);
|
|
288
|
+
if (!event.defaultPrevented) {
|
|
289
|
+
close();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
293
|
+
"button",
|
|
294
|
+
{
|
|
295
|
+
ref,
|
|
296
|
+
type: "button",
|
|
297
|
+
tabIndex: 0,
|
|
298
|
+
onClick: handleClick,
|
|
299
|
+
"aria-label": children ? void 0 : "Close dialog",
|
|
300
|
+
"data-a11y-core-dialog-close": true,
|
|
301
|
+
...props,
|
|
302
|
+
children: children ?? "\xD7"
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
var DialogContent = react.forwardRef(
|
|
308
|
+
function DialogContent2({ children, ...props }, ref) {
|
|
309
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-a11y-core-dialog-content": true, ...props, children });
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
var DialogActions = react.forwardRef(
|
|
313
|
+
function DialogActions2({ children, ...props }, ref) {
|
|
314
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-a11y-core-dialog-actions": true, ...props, children });
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
var DialogCompound = Object.assign(Dialog, {
|
|
318
|
+
Trigger: DialogTrigger,
|
|
319
|
+
Title: DialogTitle,
|
|
320
|
+
Description: DialogDescription,
|
|
321
|
+
Close: DialogClose,
|
|
322
|
+
Content: DialogContent,
|
|
323
|
+
Actions: DialogActions
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
exports.Dialog = Dialog;
|
|
327
|
+
exports.DialogActions = DialogActions;
|
|
328
|
+
exports.DialogClose = DialogClose;
|
|
329
|
+
exports.DialogCompound = DialogCompound;
|
|
330
|
+
exports.DialogContent = DialogContent;
|
|
331
|
+
exports.DialogDescription = DialogDescription;
|
|
332
|
+
exports.DialogTitle = DialogTitle;
|
|
333
|
+
exports.DialogTrigger = DialogTrigger;
|
|
334
|
+
exports.useDialogContext = useDialogContext;
|
|
335
|
+
exports.useFocusTrap = useFocusTrap;
|
|
336
|
+
exports.useFocusTrapControls = useFocusTrapControls;
|
|
337
|
+
//# sourceMappingURL=chunk-THB5U7YC.cjs.map
|
|
338
|
+
//# sourceMappingURL=chunk-THB5U7YC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-focus-trap.ts","../src/components/dialog/dialog-context.ts","../src/components/dialog/dialog.tsx"],"names":["useRef","useEffect","createFocusTrap","useCallback","createContext","useContext","createComponentWarnings","useId","useState","jsx","createPortal","useAnnouncer","forwardRef","DialogTrigger","DialogTitle","DialogDescription","DialogClose","DialogContent","DialogActions"],"mappings":"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","file":"chunk-THB5U7YC.cjs","sourcesContent":["import { useEffect, useRef, useCallback } from 'react';\nimport { createFocusTrap, type FocusTrapOptions } from '@a11y-core/core';\n\nexport interface UseFocusTrapOptions extends FocusTrapOptions {\n /** Whether the focus trap is active */\n active?: boolean;\n}\n\n/**\n * Hook to create a focus trap for modals, dialogs, etc.\n *\n * @example\n * ```tsx\n * function Modal({ isOpen, onClose }) {\n * const trapRef = useFocusTrap({\n * active: isOpen,\n * onDeactivate: onClose,\n * });\n *\n * return (\n * <div ref={trapRef} role=\"dialog\">\n * <button>Close</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useFocusTrap<T extends HTMLElement = HTMLDivElement>(\n options: UseFocusTrapOptions = {}\n): React.RefObject<T> {\n const { active = true, ...trapOptions } = options;\n const containerRef = useRef<T>(null);\n const trapRef = useRef<ReturnType<typeof createFocusTrap> | null>(null);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n trapRef.current = createFocusTrap(container, trapOptions);\n\n if (active) {\n trapRef.current.activate();\n }\n\n return () => {\n // Use destroy() instead of deactivate() to avoid calling onDeactivate\n // during cleanup (which would cause issues with React Strict Mode)\n trapRef.current?.destroy();\n trapRef.current = null;\n };\n }, [\n trapOptions.initialFocus,\n trapOptions.returnFocus,\n trapOptions.clickOutsideDeactivates,\n trapOptions.escapeDeactivates,\n trapOptions.onDeactivate,\n ]);\n\n useEffect(() => {\n if (!trapRef.current) return;\n\n if (active) {\n trapRef.current.activate();\n } else {\n trapRef.current.deactivate();\n }\n }, [active]);\n\n return containerRef;\n}\n\n/**\n * Imperative focus trap controls\n */\nexport function useFocusTrapControls<T extends HTMLElement = HTMLDivElement>(\n options: Omit<FocusTrapOptions, 'onDeactivate'> = {}\n) {\n const containerRef = useRef<T | null>(null);\n const trapRef = useRef<ReturnType<typeof createFocusTrap> | null>(null);\n\n const activate = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n if (!trapRef.current) {\n trapRef.current = createFocusTrap(container, options);\n }\n trapRef.current.activate();\n }, [options]);\n\n const deactivate = useCallback(() => {\n trapRef.current?.deactivate();\n }, []);\n\n const pause = useCallback(() => {\n trapRef.current?.pause();\n }, []);\n\n const unpause = useCallback(() => {\n trapRef.current?.unpause();\n }, []);\n\n useEffect(() => {\n return () => {\n // Use destroy() for cleanup to avoid calling onDeactivate during unmount\n trapRef.current?.destroy();\n trapRef.current = null;\n };\n }, []);\n\n return {\n ref: containerRef,\n activate,\n deactivate,\n pause,\n unpause,\n isActive: () => trapRef.current?.isActive() ?? false,\n isPaused: () => trapRef.current?.isPaused() ?? false,\n };\n}\n","import { createContext, useContext } from 'react';\n\nexport interface DialogContextValue {\n /** Whether the dialog is open */\n isOpen: boolean;\n /** Close the dialog */\n close: () => void;\n /** ID for the dialog element */\n dialogId: string;\n /** ID for the title element */\n titleId: string;\n /** ID for the description element */\n descriptionId: string;\n /** Whether the dialog has a visible title */\n hasTitle: boolean;\n /** Whether the dialog has a visible description */\n hasDescription: boolean;\n /** Set whether title is rendered */\n setHasTitle: (value: boolean) => void;\n /** Set whether description is rendered */\n setHasDescription: (value: boolean) => void;\n}\n\nconst DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(): DialogContextValue {\n const context = useContext(DialogContext);\n if (!context) {\n throw new Error(\n 'Dialog compound components must be used within a Dialog component'\n );\n }\n return context;\n}\n\nexport const DialogProvider = DialogContext.Provider;\n","import React, { forwardRef, useCallback, useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useId } from '../../hooks/use-id';\nimport { useFocusTrap } from '../../hooks/use-focus-trap';\nimport { useAnnouncer } from '../../hooks/use-announcer';\nimport { DialogProvider, useDialogContext } from './dialog-context';\nimport { createComponentWarnings } from '@a11y-core/core';\n\nconst warnings = createComponentWarnings('Dialog');\n\nexport interface DialogProps {\n /** Whether the dialog is open */\n open: boolean;\n /** Called when the dialog should close */\n onOpenChange: (open: boolean) => void;\n /** The dialog content */\n children: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Element to focus when dialog opens */\n initialFocus?: React.RefObject<HTMLElement>;\n /** Whether clicking outside closes the dialog */\n closeOnOutsideClick?: boolean;\n /** Whether pressing Escape closes the dialog */\n closeOnEscape?: boolean;\n /** Portal container (defaults to document.body) */\n container?: HTMLElement;\n /** Accessible label (required if no DialogTitle) */\n 'aria-label'?: string;\n /** ID of element that labels the dialog */\n 'aria-labelledby'?: string;\n /** Remove default styles to allow full customization via className */\n unstyled?: boolean;\n}\n\nexport function Dialog({\n open,\n onOpenChange,\n children,\n className,\n initialFocus,\n closeOnOutsideClick = true,\n closeOnEscape = true,\n container,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n unstyled = false,\n}: DialogProps) {\n const dialogId = useId('dialog');\n const titleId = useId('dialog-title');\n const descriptionId = useId('dialog-desc');\n\n const [hasTitle, setHasTitle] = useState(false);\n const [hasDescription, setHasDescription] = useState(false);\n\n const close = useCallback(() => {\n onOpenChange(false);\n }, [onOpenChange]);\n\n // Warn if no accessible label\n useEffect(() => {\n if (open && !hasTitle && !ariaLabel && !ariaLabelledBy) {\n warnings.warning(\n 'Dialog has no accessible title. Add a DialogTitle or aria-label prop.',\n 'Use <Dialog.Title> or provide aria-label=\"...\"'\n );\n }\n }, [open, hasTitle, ariaLabel, ariaLabelledBy]);\n\n const contextValue = {\n isOpen: open,\n close,\n dialogId,\n titleId,\n descriptionId,\n hasTitle,\n hasDescription,\n setHasTitle,\n setHasDescription,\n };\n\n if (!open) {\n return null;\n }\n\n const dialogContent = (\n <DialogProvider value={contextValue}>\n <DialogOverlay\n className={className}\n closeOnOutsideClick={closeOnOutsideClick}\n closeOnEscape={closeOnEscape}\n initialFocus={initialFocus}\n ariaLabel={ariaLabel}\n ariaLabelledBy={ariaLabelledBy}\n unstyled={unstyled}\n >\n {children}\n </DialogOverlay>\n </DialogProvider>\n );\n\n const portalContainer = container ?? document.body;\n return createPortal(dialogContent, portalContainer);\n}\n\ninterface DialogOverlayProps {\n children: React.ReactNode;\n className?: string;\n closeOnOutsideClick: boolean;\n closeOnEscape: boolean;\n initialFocus?: React.RefObject<HTMLElement>;\n ariaLabel?: string;\n ariaLabelledBy?: string;\n unstyled: boolean;\n}\n\nfunction DialogOverlay({\n children,\n className,\n closeOnOutsideClick,\n closeOnEscape,\n initialFocus,\n ariaLabel,\n ariaLabelledBy,\n unstyled,\n}: DialogOverlayProps) {\n const { close, dialogId, titleId, descriptionId, hasTitle, hasDescription } =\n useDialogContext();\n const { announce } = useAnnouncer();\n\n const trapRef = useFocusTrap<HTMLDivElement>({\n active: true,\n initialFocus: initialFocus?.current ?? undefined,\n escapeDeactivates: closeOnEscape,\n // Don't use clickOutsideDeactivates - we handle this in handleOverlayClick\n clickOutsideDeactivates: false,\n onDeactivate: close,\n });\n\n useEffect(() => {\n announce('Dialog opened', { politeness: 'polite' });\n return () => {\n announce('Dialog closed', { politeness: 'polite' });\n };\n }, [announce]);\n\n useEffect(() => {\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = originalOverflow;\n };\n }, []);\n\n const handleOverlayClick = (event: React.MouseEvent) => {\n if (closeOnOutsideClick && event.target === event.currentTarget) {\n close();\n }\n };\n\n const labelledBy = ariaLabelledBy ?? (hasTitle ? titleId : undefined);\n const describedBy = hasDescription ? descriptionId : undefined;\n\n const handleDialogClick = (event: React.MouseEvent) => {\n // Prevent clicks inside dialog from bubbling to overlay\n event.stopPropagation();\n };\n\n const overlayStructuralStyles: React.CSSProperties = {\n position: 'fixed',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 9999,\n };\n\n // Visual styles - only applied when not unstyled\n const overlayVisualStyles: React.CSSProperties = unstyled\n ? {}\n : {\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n };\n\n const overlayStyles: React.CSSProperties = {\n ...overlayStructuralStyles,\n ...overlayVisualStyles,\n };\n\n // Visual styles for dialog panel - only applied when not unstyled\n const dialogStyles: React.CSSProperties = unstyled\n ? {}\n : {\n backgroundColor: 'white',\n borderRadius: '8px',\n padding: '1.5rem',\n minWidth: '300px',\n maxWidth: '500px',\n boxShadow: '0 10px 25px rgba(0, 0, 0, 0.2)',\n };\n\n return (\n <div\n className={className}\n style={overlayStyles}\n onClick={handleOverlayClick}\n data-a11y-core-dialog-overlay\n >\n <div\n ref={trapRef}\n id={dialogId}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={ariaLabel}\n aria-labelledby={labelledBy}\n aria-describedby={describedBy}\n onClick={handleDialogClick}\n style={dialogStyles}\n data-a11y-core-dialog\n >\n {children}\n </div>\n </div>\n );\n}\n\nexport interface DialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n children: React.ReactNode;\n}\n\nexport const DialogTrigger = forwardRef<HTMLButtonElement, DialogTriggerProps>(\n function DialogTrigger({ children, ...props }, ref) {\n return (\n <button\n ref={ref}\n type=\"button\"\n // Safari fix: Ensure button is in tab order (Safari skips buttons by default)\n tabIndex={0}\n data-a11y-core-dialog-trigger\n {...props}\n >\n {children}\n </button>\n );\n }\n);\n\nexport interface DialogTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {\n as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n children: React.ReactNode;\n}\n\nexport const DialogTitle = forwardRef<HTMLHeadingElement, DialogTitleProps>(\n function DialogTitle({ as: Component = 'h2', children, ...props }, ref) {\n const { titleId, setHasTitle } = useDialogContext();\n\n useEffect(() => {\n setHasTitle(true);\n return () => setHasTitle(false);\n }, [setHasTitle]);\n\n return (\n <Component ref={ref} id={titleId} data-a11y-core-dialog-title {...props}>\n {children}\n </Component>\n );\n }\n);\n\nexport interface DialogDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {\n children: React.ReactNode;\n}\n\nexport const DialogDescription = forwardRef<\n HTMLParagraphElement,\n DialogDescriptionProps\n>(function DialogDescription({ children, ...props }, ref) {\n const { descriptionId, setHasDescription } = useDialogContext();\n\n useEffect(() => {\n setHasDescription(true);\n return () => setHasDescription(false);\n }, [setHasDescription]);\n\n return (\n <p\n ref={ref}\n id={descriptionId}\n data-a11y-core-dialog-description\n {...props}\n >\n {children}\n </p>\n );\n});\n\nexport interface DialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n children?: React.ReactNode;\n}\n\nexport const DialogClose = forwardRef<HTMLButtonElement, DialogCloseProps>(\n function DialogClose({ children, onClick, ...props }, ref) {\n const { close } = useDialogContext();\n\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event);\n if (!event.defaultPrevented) {\n close();\n }\n };\n\n return (\n <button\n ref={ref}\n type=\"button\"\n // Safari fix: Ensure button is in tab order (Safari skips buttons by default)\n tabIndex={0}\n onClick={handleClick}\n aria-label={children ? undefined : 'Close dialog'}\n data-a11y-core-dialog-close\n {...props}\n >\n {children ?? '×'}\n </button>\n );\n }\n);\n\nexport interface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nexport const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(\n function DialogContent({ children, ...props }, ref) {\n return (\n <div ref={ref} data-a11y-core-dialog-content {...props}>\n {children}\n </div>\n );\n }\n);\n\nexport interface DialogActionsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nexport const DialogActions = forwardRef<HTMLDivElement, DialogActionsProps>(\n function DialogActions({ children, ...props }, ref) {\n return (\n <div ref={ref} data-a11y-core-dialog-actions {...props}>\n {children}\n </div>\n );\n }\n);\n\nexport const DialogCompound = Object.assign(Dialog, {\n Trigger: DialogTrigger,\n Title: DialogTitle,\n Description: DialogDescription,\n Close: DialogClose,\n Content: DialogContent,\n Actions: DialogActions,\n});\n"]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useRef, useCallback, useMemo, useState, useEffect } from 'react';
|
|
2
|
+
import { getKeyCombo, normalizeKey, KeyboardPatterns, createTypeAhead } from '@a11y-core/core';
|
|
3
|
+
|
|
4
|
+
// src/hooks/use-keyboard.ts
|
|
5
|
+
function useKeyboard(handlers, options = {}) {
|
|
6
|
+
const { preventDefault = true, stopPropagation = true, disabled = false } = options;
|
|
7
|
+
const handlersRef = useRef(handlers);
|
|
8
|
+
handlersRef.current = handlers;
|
|
9
|
+
const handleKeyDown = useCallback(
|
|
10
|
+
(event) => {
|
|
11
|
+
if (disabled) return;
|
|
12
|
+
const combo = getKeyCombo(event.nativeEvent);
|
|
13
|
+
let handler = handlersRef.current[combo];
|
|
14
|
+
if (!handler) {
|
|
15
|
+
const key = normalizeKey(event.nativeEvent);
|
|
16
|
+
handler = handlersRef.current[key];
|
|
17
|
+
}
|
|
18
|
+
if (handler) {
|
|
19
|
+
const result = handler(event.nativeEvent);
|
|
20
|
+
if (result !== false) {
|
|
21
|
+
if (preventDefault) {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
}
|
|
24
|
+
if (stopPropagation) {
|
|
25
|
+
event.stopPropagation();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
[disabled, preventDefault, stopPropagation]
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
onKeyDown: handleKeyDown
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function useMenuKeyboard(options) {
|
|
37
|
+
const { disabled, ...handlers } = options;
|
|
38
|
+
return useKeyboard(KeyboardPatterns.menu(handlers), { disabled });
|
|
39
|
+
}
|
|
40
|
+
function useTabsKeyboard(options) {
|
|
41
|
+
const { disabled, ...handlers } = options;
|
|
42
|
+
return useKeyboard(KeyboardPatterns.tabs(handlers), { disabled });
|
|
43
|
+
}
|
|
44
|
+
function useGridKeyboard(options) {
|
|
45
|
+
const { disabled, ...handlers } = options;
|
|
46
|
+
return useKeyboard(KeyboardPatterns.grid(handlers), { disabled });
|
|
47
|
+
}
|
|
48
|
+
function useTypeAhead(items, onMatch, options = {}) {
|
|
49
|
+
const { timeout = 500, disabled = false } = options;
|
|
50
|
+
const typeAhead = useMemo(
|
|
51
|
+
() => createTypeAhead(items, { timeout }),
|
|
52
|
+
[items, timeout]
|
|
53
|
+
);
|
|
54
|
+
const onMatchRef = useRef(onMatch);
|
|
55
|
+
onMatchRef.current = onMatch;
|
|
56
|
+
const handleKeyDown = useCallback(
|
|
57
|
+
(event) => {
|
|
58
|
+
if (disabled) return;
|
|
59
|
+
if (event.key.length !== 1) return;
|
|
60
|
+
if (event.ctrlKey || event.altKey || event.metaKey) return;
|
|
61
|
+
const match = typeAhead.type(event.key);
|
|
62
|
+
if (match) {
|
|
63
|
+
onMatchRef.current(match);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[disabled, typeAhead]
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
onKeyDown: handleKeyDown,
|
|
70
|
+
reset: typeAhead.reset,
|
|
71
|
+
getSearch: typeAhead.getSearch
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function useKeyPressed(targetKey) {
|
|
75
|
+
const [pressed, setPressed] = useState(false);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const handleKeyDown = (event) => {
|
|
78
|
+
if (normalizeKey(event) === targetKey) {
|
|
79
|
+
setPressed(true);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const handleKeyUp = (event) => {
|
|
83
|
+
if (normalizeKey(event) === targetKey) {
|
|
84
|
+
setPressed(false);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
88
|
+
document.addEventListener("keyup", handleKeyUp);
|
|
89
|
+
return () => {
|
|
90
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
91
|
+
document.removeEventListener("keyup", handleKeyUp);
|
|
92
|
+
};
|
|
93
|
+
}, [targetKey]);
|
|
94
|
+
return pressed;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { useGridKeyboard, useKeyPressed, useKeyboard, useMenuKeyboard, useTabsKeyboard, useTypeAhead };
|
|
98
|
+
//# sourceMappingURL=chunk-U6DUSMEA.js.map
|
|
99
|
+
//# sourceMappingURL=chunk-U6DUSMEA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-keyboard.ts"],"names":[],"mappings":"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","file":"chunk-U6DUSMEA.js","sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n KeyboardPatterns,\n createTypeAhead,\n normalizeKey,\n getKeyCombo,\n type KeyboardHandlers,\n} from '@a11y-core/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 { preventDefault = true, stopPropagation = true, disabled = false } = 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(\n targetKey: string\n): 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"]}
|