@compa11y/react 0.1.0 → 0.1.3
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 +487 -5
- package/dist/chunk-36S2JYVF.cjs +1 -0
- package/dist/chunk-AJ7JSWUT.cjs +1 -0
- package/dist/chunk-DDFEION3.cjs +1 -0
- package/dist/chunk-DWU3PTJO.cjs +1 -0
- package/dist/chunk-FD4F6ONU.cjs +1 -0
- package/dist/chunk-FOVHQAY5.cjs +1 -0
- package/dist/chunk-GITBIGD4.js +1 -0
- package/dist/chunk-HEA4NAOM.js +1 -0
- package/dist/chunk-IZ7LLPPV.js +1 -0
- package/dist/chunk-JS3UD7KS.cjs +1 -0
- package/dist/chunk-MAR6RBHF.cjs +1 -0
- package/dist/chunk-MD4AVTLT.js +1 -0
- package/dist/chunk-SB6ASQ36.js +1 -0
- package/dist/chunk-VMM4K2K4.js +1 -0
- package/dist/chunk-XEJXACWE.js +1 -0
- package/dist/chunk-ZB3SYGHE.js +1 -0
- package/dist/components/combobox/index.cjs +1 -31
- package/dist/components/combobox/index.js +1 -6
- package/dist/components/dialog/index.cjs +1 -46
- package/dist/components/dialog/index.js +1 -5
- package/dist/components/menu/index.cjs +1 -46
- package/dist/components/menu/index.js +1 -5
- package/dist/components/tabs/index.cjs +1 -35
- package/dist/components/tabs/index.js +1 -6
- package/dist/components/toast/index.cjs +1 -24
- package/dist/components/toast/index.js +1 -3
- package/dist/index.cjs +1 -702
- package/dist/index.d.cts +760 -3
- package/dist/index.d.ts +760 -3
- package/dist/index.js +1 -430
- package/package.json +44 -3
- package/dist/chunk-2S4C6FGA.js +0 -380
- package/dist/chunk-2S4C6FGA.js.map +0 -1
- package/dist/chunk-52J4Z3QD.cjs +0 -45
- package/dist/chunk-52J4Z3QD.cjs.map +0 -1
- package/dist/chunk-C7QK2I7H.js +0 -373
- package/dist/chunk-C7QK2I7H.js.map +0 -1
- package/dist/chunk-D2UMS62N.cjs +0 -245
- package/dist/chunk-D2UMS62N.cjs.map +0 -1
- package/dist/chunk-E265U2RK.js +0 -234
- package/dist/chunk-E265U2RK.js.map +0 -1
- package/dist/chunk-E4XJRXWM.js +0 -215
- package/dist/chunk-E4XJRXWM.js.map +0 -1
- package/dist/chunk-GDLOJH6K.cjs +0 -110
- package/dist/chunk-GDLOJH6K.cjs.map +0 -1
- package/dist/chunk-IR46CNNY.cjs +0 -329
- package/dist/chunk-IR46CNNY.cjs.map +0 -1
- package/dist/chunk-JXYOE7SH.js +0 -103
- package/dist/chunk-JXYOE7SH.js.map +0 -1
- package/dist/chunk-O3YYQZ5O.js +0 -317
- package/dist/chunk-O3YYQZ5O.js.map +0 -1
- package/dist/chunk-OIVTOU4Z.cjs +0 -386
- package/dist/chunk-OIVTOU4Z.cjs.map +0 -1
- package/dist/chunk-OND5B7UG.js +0 -85
- package/dist/chunk-OND5B7UG.js.map +0 -1
- package/dist/chunk-R4FR6M6I.cjs +0 -383
- package/dist/chunk-R4FR6M6I.cjs.map +0 -1
- package/dist/chunk-RBDQCIS7.cjs +0 -89
- package/dist/chunk-RBDQCIS7.cjs.map +0 -1
- package/dist/chunk-SOBS7MIH.cjs +0 -220
- package/dist/chunk-SOBS7MIH.cjs.map +0 -1
- package/dist/chunk-WURPAE3R.js +0 -41
- package/dist/chunk-WURPAE3R.js.map +0 -1
- package/dist/components/combobox/index.cjs.map +0 -1
- package/dist/components/combobox/index.js.map +0 -1
- package/dist/components/dialog/index.cjs.map +0 -1
- package/dist/components/dialog/index.js.map +0 -1
- package/dist/components/menu/index.cjs.map +0 -1
- package/dist/components/menu/index.js.map +0 -1
- package/dist/components/tabs/index.cjs.map +0 -1
- package/dist/components/tabs/index.js.map +0 -1
- package/dist/components/toast/index.cjs.map +0 -1
- package/dist/components/toast/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/chunk-O3YYQZ5O.js
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import { useAnnouncer } from './chunk-OND5B7UG.js';
|
|
2
|
-
import { useId } from './chunk-WURPAE3R.js';
|
|
3
|
-
import { createContext, forwardRef, useEffect, useContext, useState, useCallback, useRef } from 'react';
|
|
4
|
-
import { createPortal } from 'react-dom';
|
|
5
|
-
import { createComponentWarnings, createFocusTrap } from '@compa11y/core';
|
|
6
|
-
import { jsx } from 'react/jsx-runtime';
|
|
7
|
-
|
|
8
|
-
function useFocusTrap(options = {}) {
|
|
9
|
-
const { active = true, ...trapOptions } = options;
|
|
10
|
-
const containerRef = useRef(null);
|
|
11
|
-
const trapRef = useRef(null);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const container = containerRef.current;
|
|
14
|
-
if (!container) return;
|
|
15
|
-
trapRef.current = createFocusTrap(container, trapOptions);
|
|
16
|
-
if (active) {
|
|
17
|
-
trapRef.current.activate();
|
|
18
|
-
}
|
|
19
|
-
return () => {
|
|
20
|
-
trapRef.current?.destroy();
|
|
21
|
-
trapRef.current = null;
|
|
22
|
-
};
|
|
23
|
-
}, [
|
|
24
|
-
trapOptions.initialFocus,
|
|
25
|
-
trapOptions.returnFocus,
|
|
26
|
-
trapOptions.clickOutsideDeactivates,
|
|
27
|
-
trapOptions.escapeDeactivates,
|
|
28
|
-
trapOptions.onDeactivate
|
|
29
|
-
]);
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (!trapRef.current) return;
|
|
32
|
-
if (active) {
|
|
33
|
-
trapRef.current.activate();
|
|
34
|
-
} else {
|
|
35
|
-
trapRef.current.deactivate();
|
|
36
|
-
}
|
|
37
|
-
}, [active]);
|
|
38
|
-
return containerRef;
|
|
39
|
-
}
|
|
40
|
-
function useFocusTrapControls(options = {}) {
|
|
41
|
-
const containerRef = useRef(null);
|
|
42
|
-
const trapRef = useRef(null);
|
|
43
|
-
const activate = useCallback(() => {
|
|
44
|
-
const container = containerRef.current;
|
|
45
|
-
if (!container) return;
|
|
46
|
-
if (!trapRef.current) {
|
|
47
|
-
trapRef.current = createFocusTrap(container, options);
|
|
48
|
-
}
|
|
49
|
-
trapRef.current.activate();
|
|
50
|
-
}, [options]);
|
|
51
|
-
const deactivate = useCallback(() => {
|
|
52
|
-
trapRef.current?.deactivate();
|
|
53
|
-
}, []);
|
|
54
|
-
const pause = useCallback(() => {
|
|
55
|
-
trapRef.current?.pause();
|
|
56
|
-
}, []);
|
|
57
|
-
const unpause = useCallback(() => {
|
|
58
|
-
trapRef.current?.unpause();
|
|
59
|
-
}, []);
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
return () => {
|
|
62
|
-
trapRef.current?.destroy();
|
|
63
|
-
trapRef.current = null;
|
|
64
|
-
};
|
|
65
|
-
}, []);
|
|
66
|
-
return {
|
|
67
|
-
ref: containerRef,
|
|
68
|
-
activate,
|
|
69
|
-
deactivate,
|
|
70
|
-
pause,
|
|
71
|
-
unpause,
|
|
72
|
-
isActive: () => trapRef.current?.isActive() ?? false,
|
|
73
|
-
isPaused: () => trapRef.current?.isPaused() ?? false
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
var DialogContext = createContext(null);
|
|
77
|
-
function useDialogContext() {
|
|
78
|
-
const context = useContext(DialogContext);
|
|
79
|
-
if (!context) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"Dialog compound components must be used within a Dialog component"
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
return context;
|
|
85
|
-
}
|
|
86
|
-
var DialogProvider = DialogContext.Provider;
|
|
87
|
-
var warnings = createComponentWarnings("Dialog");
|
|
88
|
-
function Dialog({
|
|
89
|
-
open,
|
|
90
|
-
onOpenChange,
|
|
91
|
-
children,
|
|
92
|
-
className,
|
|
93
|
-
initialFocus,
|
|
94
|
-
closeOnOutsideClick = true,
|
|
95
|
-
closeOnEscape = true,
|
|
96
|
-
container,
|
|
97
|
-
"aria-label": ariaLabel,
|
|
98
|
-
"aria-labelledby": ariaLabelledBy,
|
|
99
|
-
unstyled = false
|
|
100
|
-
}) {
|
|
101
|
-
const dialogId = useId("dialog");
|
|
102
|
-
const titleId = useId("dialog-title");
|
|
103
|
-
const descriptionId = useId("dialog-desc");
|
|
104
|
-
const [hasTitle, setHasTitle] = useState(false);
|
|
105
|
-
const [hasDescription, setHasDescription] = useState(false);
|
|
106
|
-
const close = useCallback(() => {
|
|
107
|
-
onOpenChange(false);
|
|
108
|
-
}, [onOpenChange]);
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
if (open && !hasTitle && !ariaLabel && !ariaLabelledBy) {
|
|
111
|
-
warnings.warning(
|
|
112
|
-
"Dialog has no accessible title. Add a DialogTitle or aria-label prop.",
|
|
113
|
-
'Use <Dialog.Title> or provide aria-label="..."'
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
}, [open, hasTitle, ariaLabel, ariaLabelledBy]);
|
|
117
|
-
const contextValue = {
|
|
118
|
-
isOpen: open,
|
|
119
|
-
close,
|
|
120
|
-
dialogId,
|
|
121
|
-
titleId,
|
|
122
|
-
descriptionId,
|
|
123
|
-
hasTitle,
|
|
124
|
-
hasDescription,
|
|
125
|
-
setHasTitle,
|
|
126
|
-
setHasDescription
|
|
127
|
-
};
|
|
128
|
-
if (!open) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
const dialogContent = /* @__PURE__ */ jsx(DialogProvider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
132
|
-
DialogOverlay,
|
|
133
|
-
{
|
|
134
|
-
className,
|
|
135
|
-
closeOnOutsideClick,
|
|
136
|
-
closeOnEscape,
|
|
137
|
-
initialFocus,
|
|
138
|
-
ariaLabel,
|
|
139
|
-
ariaLabelledBy,
|
|
140
|
-
unstyled,
|
|
141
|
-
children
|
|
142
|
-
}
|
|
143
|
-
) });
|
|
144
|
-
const portalContainer = container ?? document.body;
|
|
145
|
-
return createPortal(dialogContent, portalContainer);
|
|
146
|
-
}
|
|
147
|
-
function DialogOverlay({
|
|
148
|
-
children,
|
|
149
|
-
className,
|
|
150
|
-
closeOnOutsideClick,
|
|
151
|
-
closeOnEscape,
|
|
152
|
-
initialFocus,
|
|
153
|
-
ariaLabel,
|
|
154
|
-
ariaLabelledBy,
|
|
155
|
-
unstyled
|
|
156
|
-
}) {
|
|
157
|
-
const { close, dialogId, titleId, descriptionId, hasTitle, hasDescription } = useDialogContext();
|
|
158
|
-
const { announce } = useAnnouncer();
|
|
159
|
-
const trapRef = useFocusTrap({
|
|
160
|
-
active: true,
|
|
161
|
-
initialFocus: initialFocus?.current ?? void 0,
|
|
162
|
-
escapeDeactivates: closeOnEscape,
|
|
163
|
-
// Don't use clickOutsideDeactivates - we handle this in handleOverlayClick
|
|
164
|
-
clickOutsideDeactivates: false,
|
|
165
|
-
onDeactivate: close
|
|
166
|
-
});
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
announce("Dialog opened", { politeness: "polite" });
|
|
169
|
-
return () => {
|
|
170
|
-
announce("Dialog closed", { politeness: "polite" });
|
|
171
|
-
};
|
|
172
|
-
}, [announce]);
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
const originalOverflow = document.body.style.overflow;
|
|
175
|
-
document.body.style.overflow = "hidden";
|
|
176
|
-
return () => {
|
|
177
|
-
document.body.style.overflow = originalOverflow;
|
|
178
|
-
};
|
|
179
|
-
}, []);
|
|
180
|
-
const handleOverlayClick = (event) => {
|
|
181
|
-
if (closeOnOutsideClick && event.target === event.currentTarget) {
|
|
182
|
-
close();
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const labelledBy = ariaLabelledBy ?? (hasTitle ? titleId : void 0);
|
|
186
|
-
const describedBy = hasDescription ? descriptionId : void 0;
|
|
187
|
-
const handleDialogClick = (event) => {
|
|
188
|
-
event.stopPropagation();
|
|
189
|
-
};
|
|
190
|
-
const overlayStructuralStyles = {
|
|
191
|
-
position: "fixed",
|
|
192
|
-
inset: 0,
|
|
193
|
-
display: "flex",
|
|
194
|
-
alignItems: "center",
|
|
195
|
-
justifyContent: "center",
|
|
196
|
-
zIndex: 9999
|
|
197
|
-
};
|
|
198
|
-
const overlayVisualStyles = unstyled ? {} : {
|
|
199
|
-
backgroundColor: "rgba(0, 0, 0, 0.5)"
|
|
200
|
-
};
|
|
201
|
-
const overlayStyles = {
|
|
202
|
-
...overlayStructuralStyles,
|
|
203
|
-
...overlayVisualStyles
|
|
204
|
-
};
|
|
205
|
-
const dialogStyles = unstyled ? {} : {
|
|
206
|
-
backgroundColor: "white",
|
|
207
|
-
borderRadius: "8px",
|
|
208
|
-
padding: "1.5rem",
|
|
209
|
-
minWidth: "300px",
|
|
210
|
-
maxWidth: "500px",
|
|
211
|
-
boxShadow: "0 10px 25px rgba(0, 0, 0, 0.2)"
|
|
212
|
-
};
|
|
213
|
-
return /* @__PURE__ */ jsx(
|
|
214
|
-
"div",
|
|
215
|
-
{
|
|
216
|
-
className,
|
|
217
|
-
style: overlayStyles,
|
|
218
|
-
onClick: handleOverlayClick,
|
|
219
|
-
"data-compa11y-dialog-overlay": true,
|
|
220
|
-
children: /* @__PURE__ */ jsx(
|
|
221
|
-
"div",
|
|
222
|
-
{
|
|
223
|
-
ref: trapRef,
|
|
224
|
-
id: dialogId,
|
|
225
|
-
role: "dialog",
|
|
226
|
-
"aria-modal": "true",
|
|
227
|
-
"aria-label": ariaLabel,
|
|
228
|
-
"aria-labelledby": labelledBy,
|
|
229
|
-
"aria-describedby": describedBy,
|
|
230
|
-
onClick: handleDialogClick,
|
|
231
|
-
style: dialogStyles,
|
|
232
|
-
"data-compa11y-dialog": true,
|
|
233
|
-
children
|
|
234
|
-
}
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
var DialogTrigger = forwardRef(
|
|
240
|
-
function DialogTrigger2({ children, ...props }, ref) {
|
|
241
|
-
return /* @__PURE__ */ jsx(
|
|
242
|
-
"button",
|
|
243
|
-
{
|
|
244
|
-
ref,
|
|
245
|
-
type: "button",
|
|
246
|
-
tabIndex: 0,
|
|
247
|
-
"data-compa11y-dialog-trigger": true,
|
|
248
|
-
...props,
|
|
249
|
-
children
|
|
250
|
-
}
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
var DialogTitle = forwardRef(
|
|
255
|
-
function DialogTitle2({ as: Component = "h2", children, ...props }, ref) {
|
|
256
|
-
const { titleId, setHasTitle } = useDialogContext();
|
|
257
|
-
useEffect(() => {
|
|
258
|
-
setHasTitle(true);
|
|
259
|
-
return () => setHasTitle(false);
|
|
260
|
-
}, [setHasTitle]);
|
|
261
|
-
return /* @__PURE__ */ jsx(Component, { ref, id: titleId, "data-compa11y-dialog-title": true, ...props, children });
|
|
262
|
-
}
|
|
263
|
-
);
|
|
264
|
-
var DialogDescription = forwardRef(function DialogDescription2({ children, ...props }, ref) {
|
|
265
|
-
const { descriptionId, setHasDescription } = useDialogContext();
|
|
266
|
-
useEffect(() => {
|
|
267
|
-
setHasDescription(true);
|
|
268
|
-
return () => setHasDescription(false);
|
|
269
|
-
}, [setHasDescription]);
|
|
270
|
-
return /* @__PURE__ */ jsx("p", { ref, id: descriptionId, "data-compa11y-dialog-description": true, ...props, children });
|
|
271
|
-
});
|
|
272
|
-
var DialogClose = forwardRef(
|
|
273
|
-
function DialogClose2({ children, onClick, ...props }, ref) {
|
|
274
|
-
const { close } = useDialogContext();
|
|
275
|
-
const handleClick = (event) => {
|
|
276
|
-
onClick?.(event);
|
|
277
|
-
if (!event.defaultPrevented) {
|
|
278
|
-
close();
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
return /* @__PURE__ */ jsx(
|
|
282
|
-
"button",
|
|
283
|
-
{
|
|
284
|
-
ref,
|
|
285
|
-
type: "button",
|
|
286
|
-
tabIndex: 0,
|
|
287
|
-
onClick: handleClick,
|
|
288
|
-
"aria-label": children ? void 0 : "Close dialog",
|
|
289
|
-
"data-compa11y-dialog-close": true,
|
|
290
|
-
...props,
|
|
291
|
-
children: children ?? "\xD7"
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
);
|
|
296
|
-
var DialogContent = forwardRef(
|
|
297
|
-
function DialogContent2({ children, ...props }, ref) {
|
|
298
|
-
return /* @__PURE__ */ jsx("div", { ref, "data-compa11y-dialog-content": true, ...props, children });
|
|
299
|
-
}
|
|
300
|
-
);
|
|
301
|
-
var DialogActions = forwardRef(
|
|
302
|
-
function DialogActions2({ children, ...props }, ref) {
|
|
303
|
-
return /* @__PURE__ */ jsx("div", { ref, "data-compa11y-dialog-actions": true, ...props, children });
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
var DialogCompound = Object.assign(Dialog, {
|
|
307
|
-
Trigger: DialogTrigger,
|
|
308
|
-
Title: DialogTitle,
|
|
309
|
-
Description: DialogDescription,
|
|
310
|
-
Close: DialogClose,
|
|
311
|
-
Content: DialogContent,
|
|
312
|
-
Actions: DialogActions
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
export { Dialog, DialogActions, DialogClose, DialogCompound, DialogContent, DialogDescription, DialogTitle, DialogTrigger, useDialogContext, useFocusTrap, useFocusTrapControls };
|
|
316
|
-
//# sourceMappingURL=chunk-O3YYQZ5O.js.map
|
|
317
|
-
//# sourceMappingURL=chunk-O3YYQZ5O.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/use-focus-trap.ts","../src/components/dialog/dialog-context.ts","../src/components/dialog/dialog.tsx"],"names":["useCallback","useEffect","DialogTrigger","DialogTitle","DialogDescription","DialogClose","DialogContent","DialogActions"],"mappings":"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","file":"chunk-O3YYQZ5O.js","sourcesContent":["import { useEffect, useRef, useCallback } from 'react';\nimport { createFocusTrap, type FocusTrapOptions } from '@compa11y/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 '@compa11y/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-compa11y-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-compa11y-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-compa11y-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-compa11y-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 ref={ref} id={descriptionId} data-compa11y-dialog-description {...props}>\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-compa11y-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-compa11y-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-compa11y-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"]}
|