@santi020k/lumen-react 3.0.1 → 4.0.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 +666 -13
- package/dist/ai-surfaces.d.ts +49 -0
- package/dist/ai-surfaces.d.ts.map +1 -0
- package/dist/ai-surfaces.js +88 -0
- package/dist/amount-field.d.ts +11 -0
- package/dist/amount-field.d.ts.map +1 -0
- package/dist/amount-field.js +37 -0
- package/dist/attachments.d.ts +17 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +23 -0
- package/dist/bullet-chart.d.ts +14 -0
- package/dist/bullet-chart.d.ts.map +1 -0
- package/dist/bullet-chart.js +22 -0
- package/dist/change-summary.d.ts +14 -0
- package/dist/change-summary.d.ts.map +1 -0
- package/dist/change-summary.js +4 -0
- package/dist/chart-inspection.d.ts +10 -0
- package/dist/chart-inspection.d.ts.map +1 -0
- package/dist/chart-inspection.js +11 -0
- package/dist/chart-interaction.d.ts +17 -0
- package/dist/chart-interaction.d.ts.map +1 -0
- package/dist/chart-interaction.js +50 -0
- package/dist/chart-label.d.ts +3 -0
- package/dist/chart-label.d.ts.map +1 -0
- package/dist/chart-label.js +1 -0
- package/dist/chart-recipes.d.ts +8 -0
- package/dist/chart-recipes.d.ts.map +1 -1
- package/dist/chart-recipes.js +35 -0
- package/dist/combobox.d.ts +9 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +137 -0
- package/dist/comparison-chart.d.ts +13 -0
- package/dist/comparison-chart.d.ts.map +1 -0
- package/dist/comparison-chart.js +18 -0
- package/dist/components.d.ts +106 -70
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +4 -1949
- package/dist/dashboard.d.ts +20 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +19 -0
- package/dist/data-table-range-model.d.ts +8 -0
- package/dist/data-table-range-model.d.ts.map +1 -0
- package/dist/data-table-range-model.js +31 -0
- package/dist/data-table-range.d.ts +17 -0
- package/dist/data-table-range.d.ts.map +1 -0
- package/dist/data-table-range.js +8 -0
- package/dist/data-table-saved-views.d.ts +30 -0
- package/dist/data-table-saved-views.d.ts.map +1 -0
- package/dist/data-table-saved-views.js +49 -0
- package/dist/data-table-selection-model.d.ts +23 -0
- package/dist/data-table-selection-model.d.ts.map +1 -0
- package/dist/data-table-selection-model.js +36 -0
- package/dist/data-table-selection.d.ts +11 -0
- package/dist/data-table-selection.d.ts.map +1 -0
- package/dist/data-table-selection.js +10 -0
- package/dist/data-table-state.d.ts +6 -0
- package/dist/data-table-state.d.ts.map +1 -0
- package/dist/data-table-state.js +38 -0
- package/dist/data-table-view.d.ts +88 -0
- package/dist/data-table-view.d.ts.map +1 -0
- package/dist/data-table-view.js +1 -0
- package/dist/data-table.d.ts +60 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +1 -0
- package/dist/date-range-calendar.d.ts +29 -0
- package/dist/date-range-calendar.d.ts.map +1 -0
- package/dist/date-range-calendar.js +69 -0
- package/dist/date-range-input.d.ts +25 -0
- package/dist/date-range-input.d.ts.map +1 -0
- package/dist/date-range-input.js +195 -0
- package/dist/device-frame.d.ts +13 -0
- package/dist/device-frame.d.ts.map +1 -0
- package/dist/device-frame.js +23 -0
- package/dist/dialog-lifecycle.d.ts +3 -0
- package/dist/dialog-lifecycle.d.ts.map +1 -0
- package/dist/dialog-lifecycle.js +70 -0
- package/dist/expanded-charts.d.ts +26 -0
- package/dist/expanded-charts.d.ts.map +1 -0
- package/dist/expanded-charts.js +46 -0
- package/dist/floating-panel.d.ts +11 -0
- package/dist/floating-panel.d.ts.map +1 -0
- package/dist/floating-panel.js +1 -0
- package/dist/form-workflow.d.ts +77 -0
- package/dist/form-workflow.d.ts.map +1 -0
- package/dist/form-workflow.js +337 -0
- package/dist/hooks.d.ts +24 -21
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +1 -2468
- package/dist/image-comparison.d.ts +20 -0
- package/dist/image-comparison.d.ts.map +1 -0
- package/dist/image-comparison.js +55 -0
- package/dist/index.d.ts +25 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -0
- package/dist/interval-charts.d.ts +21 -0
- package/dist/interval-charts.d.ts.map +1 -0
- package/dist/interval-charts.js +23 -0
- package/dist/media-selection.d.ts +18 -0
- package/dist/media-selection.d.ts.map +1 -0
- package/dist/media-selection.js +12 -0
- package/dist/media-viewport.d.ts +16 -0
- package/dist/media-viewport.d.ts.map +1 -0
- package/dist/media-viewport.js +37 -0
- package/dist/motion.d.ts +597 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +22 -0
- package/dist/review-workflow.d.ts +32 -0
- package/dist/review-workflow.d.ts.map +1 -0
- package/dist/review-workflow.js +76 -0
- package/dist/rich-text-editor.d.ts +21 -0
- package/dist/rich-text-editor.d.ts.map +1 -0
- package/dist/rich-text-editor.js +143 -0
- package/dist/select-form.d.ts +3 -0
- package/dist/select-form.d.ts.map +1 -0
- package/dist/select-form.js +1 -0
- package/dist/server-components.d.ts +24 -4
- package/dist/server-components.d.ts.map +1 -1
- package/dist/server-components.js +11 -3
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -0
- package/dist/toast-context.d.ts +4 -0
- package/dist/toast-context.d.ts.map +1 -0
- package/dist/toast-context.js +10 -0
- package/dist/toast-provider.d.ts +3 -0
- package/dist/toast-provider.d.ts.map +1 -0
- package/dist/toast-provider.js +3 -0
- package/dist/use-copy-feedback.d.ts +17 -0
- package/dist/use-copy-feedback.d.ts.map +1 -0
- package/dist/use-copy-feedback.js +54 -0
- package/dist/virtual-list-data.d.ts +13 -0
- package/dist/virtual-list-data.d.ts.map +1 -0
- package/dist/virtual-list-data.js +87 -0
- package/dist/virtual-list.d.ts +14 -0
- package/dist/virtual-list.d.ts.map +1 -0
- package/dist/virtual-list.js +24 -0
- package/dist/visual-interactions.d.ts +16 -0
- package/dist/visual-interactions.d.ts.map +1 -0
- package/dist/visual-interactions.js +30 -0
- package/dist/world-map.d.ts +26 -0
- package/dist/world-map.d.ts.map +1 -0
- package/dist/world-map.js +81 -0
- package/package.json +99 -11
- package/styles/critical.css +634 -61
- package/styles/data-table-view.css +36 -0
package/dist/hooks.js
CHANGED
|
@@ -1,2468 +1 @@
|
|
|
1
|
-
/* eslint-disable @eslint-react/no-context-provider, @eslint-react/no-use-context */
|
|
2
|
-
/* eslint-disable react-refresh/only-export-components */
|
|
3
|
-
/* Public hooks, explicit context providers, and their small helpers intentionally share this module. */
|
|
4
|
-
'use client';
|
|
5
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
7
|
-
import { coerceThemeBuilderExportFormat, composeClassName, createLumenKanbanMoveDetail, createThemeBuilderTokens, exportThemeBuilderValue, getLumenLocalePair, getLumenRichTextShortcut, isLumenRichTextToggleCommand, normalizeLumenLocales, scrollLumenTabIntoView } from '@santi020k/lumen-core';
|
|
8
|
-
const setRefValue = (ref, value) => {
|
|
9
|
-
if (typeof ref === 'function') {
|
|
10
|
-
ref(value);
|
|
11
|
-
}
|
|
12
|
-
else if (ref) {
|
|
13
|
-
ref.current = value;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
const defaultToastDuration = 5000;
|
|
17
|
-
const defaultToastMax = 5;
|
|
18
|
-
const closeDelay = 240;
|
|
19
|
-
const ToastContext = createContext(null);
|
|
20
|
-
const focusableSelector = [
|
|
21
|
-
'a[href]',
|
|
22
|
-
'button:not([disabled])',
|
|
23
|
-
'input:not([disabled])',
|
|
24
|
-
'select:not([disabled])',
|
|
25
|
-
'textarea:not([disabled])',
|
|
26
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
27
|
-
].join(',');
|
|
28
|
-
const composeHandlers = (userHandler, lumenHandler) => (event) => {
|
|
29
|
-
userHandler?.(event);
|
|
30
|
-
lumenHandler(event);
|
|
31
|
-
};
|
|
32
|
-
const isElementVisible = (element) => {
|
|
33
|
-
if (typeof element.checkVisibility === 'function') {
|
|
34
|
-
return element.checkVisibility();
|
|
35
|
-
}
|
|
36
|
-
return element.offsetParent !== null || element.getClientRects().length > 0;
|
|
37
|
-
};
|
|
38
|
-
const getFocusable = (root) => {
|
|
39
|
-
if (!root)
|
|
40
|
-
return [];
|
|
41
|
-
return [...root.querySelectorAll(focusableSelector)].filter(element => !element.hasAttribute('hidden') && isElementVisible(element));
|
|
42
|
-
};
|
|
43
|
-
const getLoopedIndex = (key, currentIndex, itemCount, forwardKeys) => {
|
|
44
|
-
const lastIndex = itemCount - 1;
|
|
45
|
-
if (key === 'Home')
|
|
46
|
-
return 0;
|
|
47
|
-
if (key === 'End')
|
|
48
|
-
return lastIndex;
|
|
49
|
-
if (forwardKeys.includes(key))
|
|
50
|
-
return (currentIndex + 1) % itemCount;
|
|
51
|
-
return (currentIndex - 1 + itemCount) % itemCount;
|
|
52
|
-
};
|
|
53
|
-
const getOwnedTarget = (event) => event.target instanceof Node ? event.target : null;
|
|
54
|
-
const useSafeId = (prefix, id) => {
|
|
55
|
-
const reactId = useId().replaceAll(':', '');
|
|
56
|
-
return id ?? `${prefix}-${reactId}`;
|
|
57
|
-
};
|
|
58
|
-
const useControllableState = ({ defaultValue, onChange, value }) => {
|
|
59
|
-
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
60
|
-
const currentValue = value ?? uncontrolledValue;
|
|
61
|
-
const setValue = useCallback(next => {
|
|
62
|
-
const resolvedValue = typeof next === 'function' ?
|
|
63
|
-
next(currentValue) :
|
|
64
|
-
next;
|
|
65
|
-
if (value === undefined) {
|
|
66
|
-
setUncontrolledValue(resolvedValue);
|
|
67
|
-
}
|
|
68
|
-
onChange?.(resolvedValue);
|
|
69
|
-
}, [currentValue, onChange, value]);
|
|
70
|
-
return [currentValue, setValue];
|
|
71
|
-
};
|
|
72
|
-
const focusFirstIn = (root) => {
|
|
73
|
-
globalThis.setTimeout(() => {
|
|
74
|
-
getFocusable(root)[0]?.focus();
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
const focusTrigger = (trigger) => {
|
|
78
|
-
trigger?.focus({ preventScroll: true });
|
|
79
|
-
};
|
|
80
|
-
const clampViewportPosition = (value, size, viewportSize) => Math.max(8, Math.min(value, viewportSize - size - 8));
|
|
81
|
-
const getContextMenuPosition = (x, y, rect) => {
|
|
82
|
-
const width = rect?.width ?? 0;
|
|
83
|
-
const height = rect?.height ?? 0;
|
|
84
|
-
const viewportWidth = typeof window === 'undefined' ? x + width + 16 : window.innerWidth;
|
|
85
|
-
const viewportHeight = typeof window === 'undefined' ? y + height + 16 : window.innerHeight;
|
|
86
|
-
return {
|
|
87
|
-
left: clampViewportPosition(x, width, viewportWidth),
|
|
88
|
-
top: clampViewportPosition(y, height, viewportHeight)
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
const useOutsideClose = (open, refs, onClose) => {
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
if (!open || typeof document === 'undefined')
|
|
94
|
-
return;
|
|
95
|
-
const handlePointerDown = (event) => {
|
|
96
|
-
const target = getOwnedTarget(event);
|
|
97
|
-
if (!target)
|
|
98
|
-
return;
|
|
99
|
-
const isInside = refs.some(ref => ref.current?.contains(target));
|
|
100
|
-
if (!isInside)
|
|
101
|
-
onClose();
|
|
102
|
-
};
|
|
103
|
-
document.addEventListener('pointerdown', handlePointerDown);
|
|
104
|
-
return () => {
|
|
105
|
-
document.removeEventListener('pointerdown', handlePointerDown);
|
|
106
|
-
};
|
|
107
|
-
}, [onClose, open, refs]);
|
|
108
|
-
};
|
|
109
|
-
const useDisclosureController = (options, hasPopup) => {
|
|
110
|
-
const rootRef = useRef(null);
|
|
111
|
-
const triggerRef = useRef(null);
|
|
112
|
-
const panelRef = useRef(null);
|
|
113
|
-
const panelId = useSafeId(`ui-${hasPopup}`, options.id);
|
|
114
|
-
const [open, setOpen] = useControllableState({
|
|
115
|
-
defaultValue: options.defaultOpen ?? false,
|
|
116
|
-
onChange: options.onOpenChange,
|
|
117
|
-
value: options.open
|
|
118
|
-
});
|
|
119
|
-
const close = useCallback(() => {
|
|
120
|
-
setOpen(false);
|
|
121
|
-
}, [setOpen]);
|
|
122
|
-
const toggle = useCallback(() => {
|
|
123
|
-
setOpen(current => !current);
|
|
124
|
-
}, [setOpen]);
|
|
125
|
-
useOutsideClose(open, [rootRef, triggerRef, panelRef], close);
|
|
126
|
-
const triggerProps = {
|
|
127
|
-
'aria-controls': panelId,
|
|
128
|
-
'aria-expanded': open,
|
|
129
|
-
'aria-haspopup': hasPopup,
|
|
130
|
-
'data-ui-trigger': true,
|
|
131
|
-
onClick: () => {
|
|
132
|
-
toggle();
|
|
133
|
-
},
|
|
134
|
-
onKeyDown: event => {
|
|
135
|
-
if (event.key !== 'ArrowDown' &&
|
|
136
|
-
event.key !== 'Enter' &&
|
|
137
|
-
event.key !== ' ')
|
|
138
|
-
return;
|
|
139
|
-
event.preventDefault();
|
|
140
|
-
setOpen(true);
|
|
141
|
-
focusFirstIn(panelRef.current);
|
|
142
|
-
},
|
|
143
|
-
ref: triggerRef,
|
|
144
|
-
type: 'button'
|
|
145
|
-
};
|
|
146
|
-
const panelProps = {
|
|
147
|
-
'data-state': open ? 'open' : 'closed',
|
|
148
|
-
hidden: !open,
|
|
149
|
-
id: panelId,
|
|
150
|
-
onKeyDown: event => {
|
|
151
|
-
if (event.key === 'Escape') {
|
|
152
|
-
event.preventDefault();
|
|
153
|
-
close();
|
|
154
|
-
focusTrigger(triggerRef.current);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
158
|
-
if (!keys.includes(event.key))
|
|
159
|
-
return;
|
|
160
|
-
const items = getFocusable(panelRef.current);
|
|
161
|
-
if (!items.length)
|
|
162
|
-
return;
|
|
163
|
-
event.preventDefault();
|
|
164
|
-
const currentIndex = items.indexOf(document.activeElement);
|
|
165
|
-
const nextItem = items[getLoopedIndex(event.key, Math.max(0, currentIndex), items.length, [
|
|
166
|
-
'ArrowDown'
|
|
167
|
-
])];
|
|
168
|
-
nextItem?.focus();
|
|
169
|
-
},
|
|
170
|
-
ref: panelRef
|
|
171
|
-
};
|
|
172
|
-
return {
|
|
173
|
-
close,
|
|
174
|
-
open,
|
|
175
|
-
panelProps,
|
|
176
|
-
panelRef,
|
|
177
|
-
rootProps: {
|
|
178
|
-
ref: rootRef
|
|
179
|
-
},
|
|
180
|
-
rootRef,
|
|
181
|
-
setOpen,
|
|
182
|
-
toggle,
|
|
183
|
-
triggerProps,
|
|
184
|
-
triggerRef
|
|
185
|
-
};
|
|
186
|
-
};
|
|
187
|
-
export const useDialog = (options = {}) => {
|
|
188
|
-
const dialogRef = useRef(null);
|
|
189
|
-
const triggerRef = useRef(null);
|
|
190
|
-
const [open, setOpen] = useControllableState({
|
|
191
|
-
defaultValue: options.defaultOpen ?? false,
|
|
192
|
-
onChange: options.onOpenChange,
|
|
193
|
-
value: options.open
|
|
194
|
-
});
|
|
195
|
-
const close = useCallback(() => {
|
|
196
|
-
setOpen(false);
|
|
197
|
-
}, [setOpen]);
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
const dialog = dialogRef.current;
|
|
200
|
-
if (!dialog)
|
|
201
|
-
return;
|
|
202
|
-
if (open && !dialog.open) {
|
|
203
|
-
if (typeof dialog.showModal === 'function') {
|
|
204
|
-
dialog.showModal();
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
dialog.setAttribute('open', '');
|
|
208
|
-
}
|
|
209
|
-
focusFirstIn(dialog);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (!open && dialog.open) {
|
|
213
|
-
dialog.close();
|
|
214
|
-
}
|
|
215
|
-
}, [open]);
|
|
216
|
-
const dialogProps = {
|
|
217
|
-
'aria-modal': true,
|
|
218
|
-
'data-ui-alert-dialog': options.alert ? true : undefined,
|
|
219
|
-
'data-ui-dialog': options.alert ? undefined : true,
|
|
220
|
-
onClick: event => {
|
|
221
|
-
if (event.target === dialogRef.current && !options.alert) {
|
|
222
|
-
close();
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
onClose: () => {
|
|
226
|
-
close();
|
|
227
|
-
focusTrigger(triggerRef.current);
|
|
228
|
-
},
|
|
229
|
-
ref: dialogRef,
|
|
230
|
-
role: options.alert ? 'alertdialog' : 'dialog'
|
|
231
|
-
};
|
|
232
|
-
const triggerProps = {
|
|
233
|
-
'aria-haspopup': 'dialog',
|
|
234
|
-
onClick: () => {
|
|
235
|
-
setOpen(true);
|
|
236
|
-
},
|
|
237
|
-
ref: triggerRef,
|
|
238
|
-
type: 'button'
|
|
239
|
-
};
|
|
240
|
-
return {
|
|
241
|
-
close,
|
|
242
|
-
closeProps: {
|
|
243
|
-
onClick: close,
|
|
244
|
-
type: 'button'
|
|
245
|
-
},
|
|
246
|
-
dialogProps,
|
|
247
|
-
dialogRef,
|
|
248
|
-
open,
|
|
249
|
-
setOpen,
|
|
250
|
-
triggerProps,
|
|
251
|
-
triggerRef
|
|
252
|
-
};
|
|
253
|
-
};
|
|
254
|
-
export const usePopover = (options = {}) => useDisclosureController(options, 'menu');
|
|
255
|
-
export const useDropdownMenu = (options = {}) => useDisclosureController(options, 'menu');
|
|
256
|
-
const readLanguageStorage = (storageKey) => {
|
|
257
|
-
if (!storageKey)
|
|
258
|
-
return null;
|
|
259
|
-
try {
|
|
260
|
-
return localStorage.getItem(storageKey);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
export const useLanguageToggle = ({ defaultValue, locales: localeOptions, onValueChange, storageKey, value: controlledValue } = {}) => {
|
|
267
|
-
const locales = useMemo(() => normalizeLumenLocales(localeOptions), [localeOptions]);
|
|
268
|
-
const defaultPair = getLumenLocalePair(locales, defaultValue);
|
|
269
|
-
const [internalValue, setInternalValue] = useState(defaultPair.current.value);
|
|
270
|
-
const value = controlledValue ?? internalValue;
|
|
271
|
-
const pair = getLumenLocalePair(locales, value);
|
|
272
|
-
const initializedRef = useRef(false);
|
|
273
|
-
useEffect(() => {
|
|
274
|
-
if (controlledValue !== undefined)
|
|
275
|
-
return;
|
|
276
|
-
if (!initializedRef.current) {
|
|
277
|
-
initializedRef.current = true;
|
|
278
|
-
const preferredValue = readLanguageStorage(storageKey) ??
|
|
279
|
-
defaultValue ??
|
|
280
|
-
document.documentElement.lang;
|
|
281
|
-
if (preferredValue &&
|
|
282
|
-
preferredValue !== value &&
|
|
283
|
-
locales.some(locale => locale.value === preferredValue)) {
|
|
284
|
-
setInternalValue(preferredValue);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
document.documentElement.lang = value;
|
|
289
|
-
if (storageKey) {
|
|
290
|
-
try {
|
|
291
|
-
localStorage.setItem(storageKey, value);
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
// Storage can be unavailable in privacy modes.
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}, [controlledValue, defaultValue, locales, storageKey, value]);
|
|
298
|
-
return {
|
|
299
|
-
currentLocale: pair.current,
|
|
300
|
-
nextLocale: pair.next,
|
|
301
|
-
selectNext: () => {
|
|
302
|
-
if (controlledValue === undefined)
|
|
303
|
-
setInternalValue(pair.next.value);
|
|
304
|
-
onValueChange?.(pair.next.value);
|
|
305
|
-
},
|
|
306
|
-
value: pair.current.value
|
|
307
|
-
};
|
|
308
|
-
};
|
|
309
|
-
export const useContextMenu = ({ defaultOpen = false, id, onOpenChange, open: controlledOpen } = {}) => {
|
|
310
|
-
const menuRef = useRef(null);
|
|
311
|
-
const triggerRef = useRef(null);
|
|
312
|
-
const menuId = useSafeId('ui-context-menu', id);
|
|
313
|
-
const [position, setPosition] = useState({ left: 0, top: 0 });
|
|
314
|
-
const [open, setOpen] = useControllableState({
|
|
315
|
-
defaultValue: defaultOpen,
|
|
316
|
-
onChange: onOpenChange,
|
|
317
|
-
value: controlledOpen
|
|
318
|
-
});
|
|
319
|
-
const close = useCallback(() => {
|
|
320
|
-
setOpen(false);
|
|
321
|
-
}, [setOpen]);
|
|
322
|
-
const openAt = useCallback((x, y) => {
|
|
323
|
-
const rect = menuRef.current?.getBoundingClientRect();
|
|
324
|
-
setPosition(getContextMenuPosition(x, y, rect));
|
|
325
|
-
setOpen(true);
|
|
326
|
-
focusFirstIn(menuRef.current);
|
|
327
|
-
}, [setOpen]);
|
|
328
|
-
const triggerProps = {
|
|
329
|
-
'aria-controls': menuId,
|
|
330
|
-
'aria-haspopup': 'menu',
|
|
331
|
-
'data-ui-context-menu-trigger': menuId,
|
|
332
|
-
onContextMenu: event => {
|
|
333
|
-
event.preventDefault();
|
|
334
|
-
openAt(event.clientX, event.clientY);
|
|
335
|
-
},
|
|
336
|
-
onKeyDown: event => {
|
|
337
|
-
if (event.key !== 'ContextMenu' &&
|
|
338
|
-
!(event.shiftKey && event.key === 'F10'))
|
|
339
|
-
return;
|
|
340
|
-
event.preventDefault();
|
|
341
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
342
|
-
openAt(rect.left + 16, rect.top + 16);
|
|
343
|
-
},
|
|
344
|
-
ref: triggerRef,
|
|
345
|
-
type: 'button'
|
|
346
|
-
};
|
|
347
|
-
const menuProps = {
|
|
348
|
-
'data-state': open ? 'open' : 'closed',
|
|
349
|
-
'data-ui-context-menu': true,
|
|
350
|
-
hidden: !open,
|
|
351
|
-
id: menuId,
|
|
352
|
-
onClick: event => {
|
|
353
|
-
if (event.target instanceof HTMLElement &&
|
|
354
|
-
event.target.closest('[role="menuitem"]')) {
|
|
355
|
-
close();
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
onKeyDown: event => {
|
|
359
|
-
if (event.key === 'Escape') {
|
|
360
|
-
event.preventDefault();
|
|
361
|
-
close();
|
|
362
|
-
focusTrigger(triggerRef.current);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (!['ArrowDown', 'ArrowUp', 'End', 'Home'].includes(event.key))
|
|
366
|
-
return;
|
|
367
|
-
const items = getFocusable(menuRef.current);
|
|
368
|
-
if (items.length === 0)
|
|
369
|
-
return;
|
|
370
|
-
event.preventDefault();
|
|
371
|
-
const activeElement = typeof document === 'undefined' ? null : document.activeElement;
|
|
372
|
-
const currentIndex = activeElement instanceof HTMLElement ?
|
|
373
|
-
items.indexOf(activeElement) :
|
|
374
|
-
-1;
|
|
375
|
-
items[getLoopedIndex(event.key, currentIndex, items.length, ['ArrowDown'])]?.focus();
|
|
376
|
-
},
|
|
377
|
-
ref: menuRef,
|
|
378
|
-
role: 'menu',
|
|
379
|
-
style: {
|
|
380
|
-
left: position.left,
|
|
381
|
-
position: 'fixed',
|
|
382
|
-
top: position.top,
|
|
383
|
-
zIndex: 60
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
return {
|
|
387
|
-
close,
|
|
388
|
-
menuProps,
|
|
389
|
-
menuRef,
|
|
390
|
-
open,
|
|
391
|
-
openAt,
|
|
392
|
-
setOpen,
|
|
393
|
-
triggerProps,
|
|
394
|
-
triggerRef
|
|
395
|
-
};
|
|
396
|
-
};
|
|
397
|
-
export const useTabs = ({ defaultValue = '', id, onValueChange, orientation = 'horizontal', value } = {}) => {
|
|
398
|
-
const rootRef = useRef(null);
|
|
399
|
-
const tabsId = useSafeId('ui-tabs', id);
|
|
400
|
-
const [selectedValue, setSelectedValue] = useControllableState({
|
|
401
|
-
defaultValue,
|
|
402
|
-
onChange: onValueChange,
|
|
403
|
-
value
|
|
404
|
-
});
|
|
405
|
-
const activate = useCallback((nextValue) => {
|
|
406
|
-
setSelectedValue(nextValue);
|
|
407
|
-
}, [setSelectedValue]);
|
|
408
|
-
const getTriggerProps = useCallback((triggerValue, props = {}) => {
|
|
409
|
-
const selected = selectedValue === triggerValue;
|
|
410
|
-
const triggerId = `${tabsId}-tab-${triggerValue}`;
|
|
411
|
-
const panelId = `${tabsId}-panel-${triggerValue}`;
|
|
412
|
-
return {
|
|
413
|
-
...props,
|
|
414
|
-
'aria-controls': panelId,
|
|
415
|
-
'aria-selected': selected,
|
|
416
|
-
id: props.id ?? triggerId,
|
|
417
|
-
onClick: composeHandlers(props.onClick, event => {
|
|
418
|
-
activate(triggerValue);
|
|
419
|
-
scrollLumenTabIntoView(event.currentTarget);
|
|
420
|
-
}),
|
|
421
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
422
|
-
const keys = orientation === 'vertical' ?
|
|
423
|
-
['ArrowUp', 'ArrowDown', 'Home', 'End'] :
|
|
424
|
-
['ArrowLeft', 'ArrowRight', 'Home', 'End'];
|
|
425
|
-
if (!keys.includes(event.key))
|
|
426
|
-
return;
|
|
427
|
-
const tabs = rootRef.current ?
|
|
428
|
-
[...rootRef.current.querySelectorAll('[role="tab"]')] :
|
|
429
|
-
[];
|
|
430
|
-
if (!tabs.length)
|
|
431
|
-
return;
|
|
432
|
-
event.preventDefault();
|
|
433
|
-
const currentIndex = tabs.indexOf(event.currentTarget);
|
|
434
|
-
const nextTab = tabs[getLoopedIndex(event.key, Math.max(0, currentIndex), tabs.length, orientation === 'vertical' ? ['ArrowDown'] : ['ArrowRight'])];
|
|
435
|
-
const nextValue = nextTab?.dataset.value;
|
|
436
|
-
if (!nextTab || !nextValue)
|
|
437
|
-
return;
|
|
438
|
-
activate(nextValue);
|
|
439
|
-
scrollLumenTabIntoView(nextTab);
|
|
440
|
-
nextTab.focus();
|
|
441
|
-
}),
|
|
442
|
-
role: 'tab',
|
|
443
|
-
tabIndex: selected ? 0 : -1,
|
|
444
|
-
type: props.type ?? 'button',
|
|
445
|
-
'data-value': triggerValue
|
|
446
|
-
};
|
|
447
|
-
}, [activate, orientation, selectedValue, tabsId]);
|
|
448
|
-
const getPanelProps = useCallback((panelValue, props = {}) => {
|
|
449
|
-
const selected = selectedValue === panelValue;
|
|
450
|
-
return {
|
|
451
|
-
...props,
|
|
452
|
-
'aria-labelledby': `${tabsId}-tab-${panelValue}`,
|
|
453
|
-
hidden: !selected,
|
|
454
|
-
id: props.id ?? `${tabsId}-panel-${panelValue}`,
|
|
455
|
-
role: 'tabpanel',
|
|
456
|
-
tabIndex: props.tabIndex ?? 0
|
|
457
|
-
};
|
|
458
|
-
}, [selectedValue, tabsId]);
|
|
459
|
-
return {
|
|
460
|
-
getPanelProps,
|
|
461
|
-
getTriggerProps,
|
|
462
|
-
listProps: {
|
|
463
|
-
'aria-orientation': orientation,
|
|
464
|
-
role: 'tablist'
|
|
465
|
-
},
|
|
466
|
-
rootProps: {
|
|
467
|
-
'data-ui-tabs': true,
|
|
468
|
-
ref: rootRef
|
|
469
|
-
},
|
|
470
|
-
rootRef,
|
|
471
|
-
setValue: setSelectedValue,
|
|
472
|
-
value: selectedValue
|
|
473
|
-
};
|
|
474
|
-
};
|
|
475
|
-
const normalizeOption = (option) => typeof option === 'string' ? { label: option, value: option } : option;
|
|
476
|
-
const isPrintableKey = (event) => (event.key.length === 1 &&
|
|
477
|
-
!event.altKey &&
|
|
478
|
-
!event.ctrlKey &&
|
|
479
|
-
!event.metaKey);
|
|
480
|
-
/* eslint-disable complexity -- Select owns the native select, ARIA listbox props, and keyboard/typeahead behavior as one public hook contract. */
|
|
481
|
-
export const useSelect = ({ defaultValue = '', disabled = false, id, name, onValueChange, options = [], placeholder, required = false, value } = {}) => {
|
|
482
|
-
const rootRef = useRef(null);
|
|
483
|
-
const triggerRef = useRef(null);
|
|
484
|
-
const listRef = useRef(null);
|
|
485
|
-
const typeaheadRef = useRef('');
|
|
486
|
-
const typeaheadTimerRef = useRef(undefined);
|
|
487
|
-
const selectId = useSafeId('ui-select', id);
|
|
488
|
-
const listId = `${selectId}-listbox`;
|
|
489
|
-
const normalizedOptions = useMemo(() => options.map(normalizeOption), [options]);
|
|
490
|
-
const [open, setOpen] = useState(false);
|
|
491
|
-
const [selectedValue, setSelectedValue] = useControllableState({
|
|
492
|
-
defaultValue,
|
|
493
|
-
onChange: onValueChange,
|
|
494
|
-
value
|
|
495
|
-
});
|
|
496
|
-
const selectedOption = normalizedOptions.find(option => option.value === selectedValue);
|
|
497
|
-
const triggerText = selectedOption?.label ?? placeholder ?? 'Select an option';
|
|
498
|
-
const hasSelection = Boolean(selectedOption && selectedValue !== '');
|
|
499
|
-
const close = useCallback(() => {
|
|
500
|
-
setOpen(false);
|
|
501
|
-
}, [setOpen]);
|
|
502
|
-
const openList = useCallback(() => {
|
|
503
|
-
setOpen(true);
|
|
504
|
-
}, [setOpen]);
|
|
505
|
-
useOutsideClose(open, [rootRef, triggerRef, listRef], close);
|
|
506
|
-
useEffect(() => () => {
|
|
507
|
-
if (typeaheadTimerRef.current) {
|
|
508
|
-
clearTimeout(typeaheadTimerRef.current);
|
|
509
|
-
}
|
|
510
|
-
}, []);
|
|
511
|
-
const getEnabledItems = useCallback(() => {
|
|
512
|
-
if (!listRef.current)
|
|
513
|
-
return [];
|
|
514
|
-
return [
|
|
515
|
-
...listRef.current.querySelectorAll('[data-ui-select-option]')
|
|
516
|
-
].filter(item => !item.hasAttribute('disabled') &&
|
|
517
|
-
item.getAttribute('aria-disabled') !== 'true');
|
|
518
|
-
}, []);
|
|
519
|
-
const focusOption = useCallback((key, currentItem) => {
|
|
520
|
-
const items = getEnabledItems();
|
|
521
|
-
if (!items.length)
|
|
522
|
-
return;
|
|
523
|
-
const selectedItem = items.find(item => item.getAttribute('aria-selected') === 'true');
|
|
524
|
-
const current = currentItem ?? selectedItem ?? items[0];
|
|
525
|
-
if (!current)
|
|
526
|
-
return;
|
|
527
|
-
const currentIndex = items.indexOf(current);
|
|
528
|
-
const nextItem = items[getLoopedIndex(key, Math.max(0, currentIndex), items.length, [
|
|
529
|
-
'ArrowDown'
|
|
530
|
-
])];
|
|
531
|
-
nextItem?.focus();
|
|
532
|
-
}, [getEnabledItems]);
|
|
533
|
-
const focusTypeaheadOption = useCallback((currentItem) => {
|
|
534
|
-
const items = getEnabledItems();
|
|
535
|
-
const typeahead = typeaheadRef.current;
|
|
536
|
-
if (!items.length || !typeahead)
|
|
537
|
-
return;
|
|
538
|
-
const selectedItem = items.find(item => item.getAttribute('aria-selected') === 'true');
|
|
539
|
-
const current = currentItem ?? selectedItem ?? items[0];
|
|
540
|
-
if (!current)
|
|
541
|
-
return;
|
|
542
|
-
const currentIndex = items.indexOf(current);
|
|
543
|
-
const startIndex = Math.max(0, currentIndex + 1);
|
|
544
|
-
const orderedItems = [
|
|
545
|
-
...items.slice(startIndex),
|
|
546
|
-
...items.slice(0, startIndex)
|
|
547
|
-
];
|
|
548
|
-
const nextItem = orderedItems.find(item => item.textContent.trim().toLowerCase().startsWith(typeahead));
|
|
549
|
-
nextItem?.focus();
|
|
550
|
-
}, [getEnabledItems]);
|
|
551
|
-
const handleTypeahead = useCallback((event, currentItem) => {
|
|
552
|
-
if (!isPrintableKey(event))
|
|
553
|
-
return false;
|
|
554
|
-
event.preventDefault();
|
|
555
|
-
typeaheadRef.current += event.key.toLowerCase();
|
|
556
|
-
if (typeaheadTimerRef.current) {
|
|
557
|
-
clearTimeout(typeaheadTimerRef.current);
|
|
558
|
-
}
|
|
559
|
-
typeaheadTimerRef.current = setTimeout(() => {
|
|
560
|
-
typeaheadRef.current = '';
|
|
561
|
-
}, 700);
|
|
562
|
-
openList();
|
|
563
|
-
focusTypeaheadOption(currentItem);
|
|
564
|
-
return true;
|
|
565
|
-
}, [focusTypeaheadOption, openList]);
|
|
566
|
-
const selectOption = useCallback((nextValue) => {
|
|
567
|
-
const option = normalizedOptions.find(item => item.value === nextValue);
|
|
568
|
-
if (!option || option.disabled)
|
|
569
|
-
return;
|
|
570
|
-
setSelectedValue(nextValue);
|
|
571
|
-
close();
|
|
572
|
-
focusTrigger(triggerRef.current);
|
|
573
|
-
}, [close, normalizedOptions, setSelectedValue]);
|
|
574
|
-
const rootProps = {
|
|
575
|
-
'data-placeholder': hasSelection ? 'false' : 'true',
|
|
576
|
-
'data-ui-select': true,
|
|
577
|
-
ref: rootRef
|
|
578
|
-
};
|
|
579
|
-
const controlProps = {
|
|
580
|
-
'data-ui-select-control': true
|
|
581
|
-
};
|
|
582
|
-
const nativeSelectProps = {
|
|
583
|
-
'aria-hidden': true,
|
|
584
|
-
'data-ui-enhanced': true,
|
|
585
|
-
'data-ui-select-native': true,
|
|
586
|
-
disabled,
|
|
587
|
-
id: selectId,
|
|
588
|
-
name,
|
|
589
|
-
onChange: event => {
|
|
590
|
-
setSelectedValue(event.currentTarget.value);
|
|
591
|
-
},
|
|
592
|
-
required,
|
|
593
|
-
tabIndex: -1,
|
|
594
|
-
value: selectedValue
|
|
595
|
-
};
|
|
596
|
-
const triggerProps = {
|
|
597
|
-
'aria-controls': listId,
|
|
598
|
-
'aria-expanded': open,
|
|
599
|
-
'aria-haspopup': 'listbox',
|
|
600
|
-
'aria-required': required || undefined,
|
|
601
|
-
'data-ui-select-trigger': true,
|
|
602
|
-
disabled,
|
|
603
|
-
id: `${selectId}-trigger`,
|
|
604
|
-
onClick: () => {
|
|
605
|
-
setOpen(current => !current);
|
|
606
|
-
},
|
|
607
|
-
onKeyDown: event => {
|
|
608
|
-
if (handleTypeahead(event))
|
|
609
|
-
return;
|
|
610
|
-
if (event.key === 'Escape') {
|
|
611
|
-
close();
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter', ' '];
|
|
615
|
-
if (!keys.includes(event.key))
|
|
616
|
-
return;
|
|
617
|
-
event.preventDefault();
|
|
618
|
-
openList();
|
|
619
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
620
|
-
const selectedItem = getEnabledItems().find(item => item.getAttribute('aria-selected') === 'true');
|
|
621
|
-
const firstItem = getEnabledItems()[0];
|
|
622
|
-
(selectedItem ?? firstItem)?.focus();
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
focusOption(event.key);
|
|
626
|
-
},
|
|
627
|
-
ref: triggerRef,
|
|
628
|
-
role: 'combobox',
|
|
629
|
-
type: 'button'
|
|
630
|
-
};
|
|
631
|
-
const listProps = {
|
|
632
|
-
'data-state': open ? 'open' : 'closed',
|
|
633
|
-
'data-ui-select-list': true,
|
|
634
|
-
hidden: !open,
|
|
635
|
-
id: listId,
|
|
636
|
-
ref: listRef,
|
|
637
|
-
role: 'listbox'
|
|
638
|
-
};
|
|
639
|
-
const getOptionProps = useCallback((option, props = {}) => ({
|
|
640
|
-
...props,
|
|
641
|
-
'aria-disabled': option.disabled ? 'true' : undefined,
|
|
642
|
-
'aria-selected': selectedValue === option.value,
|
|
643
|
-
'data-ui-select-option': true,
|
|
644
|
-
'data-value': option.value,
|
|
645
|
-
disabled: option.disabled,
|
|
646
|
-
onClick: composeHandlers(props.onClick, () => {
|
|
647
|
-
selectOption(option.value);
|
|
648
|
-
}),
|
|
649
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
650
|
-
if (handleTypeahead(event, event.currentTarget))
|
|
651
|
-
return;
|
|
652
|
-
if (event.key === 'Escape') {
|
|
653
|
-
close();
|
|
654
|
-
focusTrigger(triggerRef.current);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
658
|
-
event.preventDefault();
|
|
659
|
-
selectOption(option.value);
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
663
|
-
if (!keys.includes(event.key))
|
|
664
|
-
return;
|
|
665
|
-
event.preventDefault();
|
|
666
|
-
focusOption(event.key, event.currentTarget);
|
|
667
|
-
}),
|
|
668
|
-
role: 'option',
|
|
669
|
-
tabIndex: -1,
|
|
670
|
-
type: props.type ?? 'button'
|
|
671
|
-
}), [close, focusOption, handleTypeahead, selectOption, selectedValue]);
|
|
672
|
-
return {
|
|
673
|
-
close,
|
|
674
|
-
controlProps,
|
|
675
|
-
getOptionProps,
|
|
676
|
-
listProps,
|
|
677
|
-
nativeSelectProps,
|
|
678
|
-
open,
|
|
679
|
-
options: normalizedOptions,
|
|
680
|
-
rootProps,
|
|
681
|
-
rootRef,
|
|
682
|
-
selectOption,
|
|
683
|
-
selectedOption,
|
|
684
|
-
setOpen,
|
|
685
|
-
triggerProps,
|
|
686
|
-
triggerText,
|
|
687
|
-
value: selectedValue
|
|
688
|
-
};
|
|
689
|
-
};
|
|
690
|
-
/* eslint-enable complexity */
|
|
691
|
-
const fieldControlSelector = [
|
|
692
|
-
'input:not([type="hidden"])',
|
|
693
|
-
'select',
|
|
694
|
-
'textarea',
|
|
695
|
-
'[contenteditable="true"]',
|
|
696
|
-
'[role="combobox"]',
|
|
697
|
-
'[role="listbox"]',
|
|
698
|
-
'[role="spinbutton"]',
|
|
699
|
-
'[role="textbox"]'
|
|
700
|
-
].join(',');
|
|
701
|
-
const fieldDescriptionSelector = '[data-ui-field-hint], [data-ui-field-error], .ui-field__hint, .ui-field__error';
|
|
702
|
-
const fieldErrorSelector = '[data-ui-field-error], .ui-field__error';
|
|
703
|
-
const formControlSelector = [
|
|
704
|
-
'input:not([type="hidden"])',
|
|
705
|
-
'select',
|
|
706
|
-
'textarea'
|
|
707
|
-
].join(',');
|
|
708
|
-
const validityMessageAttributes = [
|
|
709
|
-
['valueMissing', 'data-error-required'],
|
|
710
|
-
['typeMismatch', 'data-error-type'],
|
|
711
|
-
['patternMismatch', 'data-error-pattern'],
|
|
712
|
-
['tooShort', 'data-error-too-short'],
|
|
713
|
-
['tooLong', 'data-error-too-long'],
|
|
714
|
-
['rangeUnderflow', 'data-error-min'],
|
|
715
|
-
['rangeOverflow', 'data-error-max'],
|
|
716
|
-
['stepMismatch', 'data-error-step'],
|
|
717
|
-
['badInput', 'data-error-bad-input'],
|
|
718
|
-
['customError', 'data-error-custom']
|
|
719
|
-
];
|
|
720
|
-
const isNativeFormControl = (element) => element instanceof HTMLInputElement ||
|
|
721
|
-
element instanceof HTMLSelectElement ||
|
|
722
|
-
element instanceof HTMLTextAreaElement;
|
|
723
|
-
const getFieldRoot = (control) => control.closest('.ui-field, [data-ui-field]');
|
|
724
|
-
const getValidationMessage = (control, field) => {
|
|
725
|
-
for (const [validityKey, attributeName] of validityMessageAttributes) {
|
|
726
|
-
if (!control.validity[validityKey])
|
|
727
|
-
continue;
|
|
728
|
-
return (control.getAttribute(attributeName) ??
|
|
729
|
-
field?.getAttribute(attributeName) ??
|
|
730
|
-
control.validationMessage);
|
|
731
|
-
}
|
|
732
|
-
return control.validationMessage;
|
|
733
|
-
};
|
|
734
|
-
const getFieldDescriptionId = (element) => {
|
|
735
|
-
if (!element.id) {
|
|
736
|
-
element.id = `ui-field-description-${Math.random().toString(36).slice(2)}`;
|
|
737
|
-
}
|
|
738
|
-
return element.id;
|
|
739
|
-
};
|
|
740
|
-
const syncFieldDescription = (field) => {
|
|
741
|
-
const controlId = field.dataset.uiFieldControl;
|
|
742
|
-
const control = controlId ?
|
|
743
|
-
document.getElementById(controlId) :
|
|
744
|
-
field.querySelector(fieldControlSelector);
|
|
745
|
-
if (!(control instanceof HTMLElement))
|
|
746
|
-
return;
|
|
747
|
-
const existingIds = control.getAttribute('aria-describedby')?.trim().split(/\s+/) ?? [];
|
|
748
|
-
const configuredIds = field.dataset.uiFieldDescribedby?.trim().split(/\s+/) ?? [];
|
|
749
|
-
const descriptionIds = [
|
|
750
|
-
...field.querySelectorAll(fieldDescriptionSelector)
|
|
751
|
-
].map(element => getFieldDescriptionId(element));
|
|
752
|
-
const describedBy = [
|
|
753
|
-
...new Set([...existingIds, ...configuredIds, ...descriptionIds].filter(Boolean))
|
|
754
|
-
];
|
|
755
|
-
if (describedBy.length) {
|
|
756
|
-
control.setAttribute('aria-describedby', describedBy.join(' '));
|
|
757
|
-
}
|
|
758
|
-
};
|
|
759
|
-
export const useFormValidation = ({ onInvalid, onValid, onValidate } = {}) => {
|
|
760
|
-
const formRef = useRef(null);
|
|
761
|
-
const setFieldValidity = useCallback((control, invalid, message = '') => {
|
|
762
|
-
const field = getFieldRoot(control);
|
|
763
|
-
if (invalid) {
|
|
764
|
-
control.setAttribute('aria-invalid', 'true');
|
|
765
|
-
control.dataset.uiValidationInvalid = 'true';
|
|
766
|
-
}
|
|
767
|
-
else if (control.dataset.uiValidationInvalid === 'true') {
|
|
768
|
-
control.removeAttribute('aria-invalid');
|
|
769
|
-
delete control.dataset.uiValidationInvalid;
|
|
770
|
-
}
|
|
771
|
-
if (!field)
|
|
772
|
-
return;
|
|
773
|
-
field.dataset.invalid = String(invalid);
|
|
774
|
-
for (const error of field.querySelectorAll(fieldErrorSelector)) {
|
|
775
|
-
error.dataset.uiValidationErrorBound = 'true';
|
|
776
|
-
error.hidden = !invalid;
|
|
777
|
-
error.textContent = invalid ? message : '';
|
|
778
|
-
}
|
|
779
|
-
}, []);
|
|
780
|
-
const getControls = useCallback((form = formRef.current) => {
|
|
781
|
-
if (!form)
|
|
782
|
-
return [];
|
|
783
|
-
return [
|
|
784
|
-
...form.querySelectorAll(formControlSelector)
|
|
785
|
-
].filter(control => control.form === form && !control.disabled);
|
|
786
|
-
}, []);
|
|
787
|
-
const validateControl = useCallback((control, form = formRef.current ?? control.form) => {
|
|
788
|
-
if (!form)
|
|
789
|
-
return true;
|
|
790
|
-
const validateDetail = { control, form, value: control.value };
|
|
791
|
-
form.dispatchEvent(new CustomEvent('ui:validate', {
|
|
792
|
-
bubbles: true,
|
|
793
|
-
detail: validateDetail
|
|
794
|
-
}));
|
|
795
|
-
onValidate?.(validateDetail);
|
|
796
|
-
const invalid = !control.validity.valid;
|
|
797
|
-
const message = invalid ?
|
|
798
|
-
getValidationMessage(control, getFieldRoot(control)) :
|
|
799
|
-
'';
|
|
800
|
-
setFieldValidity(control, invalid, message);
|
|
801
|
-
return !invalid;
|
|
802
|
-
}, [onValidate, setFieldValidity]);
|
|
803
|
-
const validateForm = useCallback((form = formRef.current) => {
|
|
804
|
-
if (!form)
|
|
805
|
-
return [];
|
|
806
|
-
return getControls(form).filter(control => !validateControl(control, form));
|
|
807
|
-
}, [getControls, validateControl]);
|
|
808
|
-
const emitState = useCallback((name, detail) => {
|
|
809
|
-
detail.form.dispatchEvent(new CustomEvent(name, {
|
|
810
|
-
bubbles: true,
|
|
811
|
-
detail
|
|
812
|
-
}));
|
|
813
|
-
if (name === 'ui:invalid') {
|
|
814
|
-
onInvalid?.(detail);
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
onValid?.(detail);
|
|
818
|
-
}
|
|
819
|
-
}, [onInvalid, onValid]);
|
|
820
|
-
const syncFields = useCallback((form) => {
|
|
821
|
-
for (const field of form.querySelectorAll('.ui-field, [data-ui-field]')) {
|
|
822
|
-
syncFieldDescription(field);
|
|
823
|
-
}
|
|
824
|
-
}, []);
|
|
825
|
-
const formProps = {
|
|
826
|
-
'data-ui-form': true,
|
|
827
|
-
noValidate: true,
|
|
828
|
-
onBlur: event => {
|
|
829
|
-
const form = event.currentTarget;
|
|
830
|
-
syncFields(form);
|
|
831
|
-
if (!isNativeFormControl(event.target) || event.target.form !== form)
|
|
832
|
-
return;
|
|
833
|
-
const valid = validateControl(event.target, form);
|
|
834
|
-
emitState(valid ? 'ui:valid' : 'ui:invalid', valid ?
|
|
835
|
-
{ control: event.target, form } :
|
|
836
|
-
{ control: event.target, controls: [event.target], form });
|
|
837
|
-
},
|
|
838
|
-
onSubmit: event => {
|
|
839
|
-
const form = event.currentTarget;
|
|
840
|
-
syncFields(form);
|
|
841
|
-
const invalidControls = validateForm(form);
|
|
842
|
-
if (invalidControls.length) {
|
|
843
|
-
event.preventDefault();
|
|
844
|
-
const firstInvalid = invalidControls[0];
|
|
845
|
-
if (!firstInvalid)
|
|
846
|
-
return;
|
|
847
|
-
emitState('ui:invalid', {
|
|
848
|
-
control: firstInvalid,
|
|
849
|
-
controls: invalidControls,
|
|
850
|
-
form
|
|
851
|
-
});
|
|
852
|
-
firstInvalid.focus({ preventScroll: true });
|
|
853
|
-
firstInvalid.reportValidity();
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
emitState('ui:valid', { controls: getControls(form), form });
|
|
857
|
-
},
|
|
858
|
-
ref: formRef
|
|
859
|
-
};
|
|
860
|
-
return {
|
|
861
|
-
formProps,
|
|
862
|
-
formRef,
|
|
863
|
-
getControls,
|
|
864
|
-
setFieldValidity,
|
|
865
|
-
validateControl,
|
|
866
|
-
validateForm
|
|
867
|
-
};
|
|
868
|
-
};
|
|
869
|
-
/* eslint-disable @stylistic/padding-line-between-statements, complexity, no-nested-ternary -- Calendar mirrors Astro's UTC date grid and keyboard runtime. */
|
|
870
|
-
const calendarDatePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
871
|
-
const calendarMonthPattern = /^\d{4}-\d{2}$/;
|
|
872
|
-
const parseCalendarDate = (value) => {
|
|
873
|
-
if (!value || !calendarDatePattern.test(value))
|
|
874
|
-
return null;
|
|
875
|
-
const [year = Number.NaN, month = Number.NaN, day = Number.NaN] = value
|
|
876
|
-
.split('-')
|
|
877
|
-
.map(Number);
|
|
878
|
-
const date = new Date(Date.UTC(year, month - 1, day));
|
|
879
|
-
return date.getUTCFullYear() === year &&
|
|
880
|
-
date.getUTCMonth() === month - 1 &&
|
|
881
|
-
date.getUTCDate() === day ?
|
|
882
|
-
date :
|
|
883
|
-
null;
|
|
884
|
-
};
|
|
885
|
-
const parseCalendarMonth = (value) => {
|
|
886
|
-
if (!value || !calendarMonthPattern.test(value))
|
|
887
|
-
return null;
|
|
888
|
-
const [year = Number.NaN, month = Number.NaN] = value.split('-').map(Number);
|
|
889
|
-
const date = new Date(Date.UTC(year, month - 1, 1));
|
|
890
|
-
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 ?
|
|
891
|
-
date :
|
|
892
|
-
null;
|
|
893
|
-
};
|
|
894
|
-
const formatCalendarDate = (date) => date.toISOString().slice(0, 10);
|
|
895
|
-
const formatCalendarMonth = (date) => date.toISOString().slice(0, 7);
|
|
896
|
-
const startOfCalendarMonth = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
|
|
897
|
-
const addCalendarDays = (date, days) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
|
|
898
|
-
const getCalendarDaysInMonth = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
|
|
899
|
-
const addCalendarMonths = (date, months) => {
|
|
900
|
-
const targetMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
|
|
901
|
-
const day = Math.min(date.getUTCDate(), getCalendarDaysInMonth(targetMonth));
|
|
902
|
-
return new Date(Date.UTC(targetMonth.getUTCFullYear(), targetMonth.getUTCMonth(), day));
|
|
903
|
-
};
|
|
904
|
-
const getCalendarToday = () => {
|
|
905
|
-
const today = new Date();
|
|
906
|
-
return new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()));
|
|
907
|
-
};
|
|
908
|
-
const compareCalendarDates = (date, other) => {
|
|
909
|
-
if (!other)
|
|
910
|
-
return 0;
|
|
911
|
-
return formatCalendarDate(date).localeCompare(formatCalendarDate(other));
|
|
912
|
-
};
|
|
913
|
-
const isCalendarDateDisabled = (disabled, date, min, max) => disabled ||
|
|
914
|
-
compareCalendarDates(date, min) < 0 ||
|
|
915
|
-
compareCalendarDates(date, max) > 0;
|
|
916
|
-
const clampCalendarDate = (date, min, max) => {
|
|
917
|
-
if (min && compareCalendarDates(date, min) < 0)
|
|
918
|
-
return min;
|
|
919
|
-
if (max && compareCalendarDates(date, max) > 0)
|
|
920
|
-
return max;
|
|
921
|
-
return date;
|
|
922
|
-
};
|
|
923
|
-
const getCalendarGridStart = (month) => addCalendarDays(month, -((month.getUTCDay() + 6) % 7));
|
|
924
|
-
const getCalendarLocale = (locale) => {
|
|
925
|
-
if (locale)
|
|
926
|
-
return locale;
|
|
927
|
-
if (typeof document !== 'undefined' && document.documentElement.lang) {
|
|
928
|
-
return document.documentElement.lang;
|
|
929
|
-
}
|
|
930
|
-
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
931
|
-
return navigator.language;
|
|
932
|
-
}
|
|
933
|
-
return 'en';
|
|
934
|
-
};
|
|
935
|
-
const coerceCalendarDate = (value) => (value instanceof Date ? value : parseCalendarDate(value));
|
|
936
|
-
const getCalendarFocusDate = (disabled, month, min, max, selectedDate, requestedDate) => {
|
|
937
|
-
const today = getCalendarToday();
|
|
938
|
-
const candidates = [requestedDate, selectedDate, today, month];
|
|
939
|
-
for (const candidate of candidates) {
|
|
940
|
-
if (candidate &&
|
|
941
|
-
formatCalendarMonth(candidate) === formatCalendarMonth(month) &&
|
|
942
|
-
!isCalendarDateDisabled(disabled, candidate, min, max)) {
|
|
943
|
-
return candidate;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
for (let index = 0; index < getCalendarDaysInMonth(month); index += 1) {
|
|
947
|
-
const date = addCalendarDays(month, index);
|
|
948
|
-
if (!isCalendarDateDisabled(disabled, date, min, max))
|
|
949
|
-
return date;
|
|
950
|
-
}
|
|
951
|
-
return month;
|
|
952
|
-
};
|
|
953
|
-
export const useCalendar = ({ defaultValue, disabled = false, locale, max, min, month, name, onValueChange, value: controlledValue } = {}) => {
|
|
954
|
-
const rootRef = useRef(null);
|
|
955
|
-
const generatedId = useId();
|
|
956
|
-
const calendarId = `ui-calendar-${generatedId}`;
|
|
957
|
-
const labelId = `${calendarId}-label`;
|
|
958
|
-
const minDate = parseCalendarDate(min);
|
|
959
|
-
const maxDate = parseCalendarDate(max);
|
|
960
|
-
const defaultDate = parseCalendarDate(defaultValue);
|
|
961
|
-
const controlledDate = parseCalendarDate(controlledValue);
|
|
962
|
-
const [uncontrolledValue, setUncontrolledValue] = useState(() => defaultDate ? formatCalendarDate(defaultDate) : '');
|
|
963
|
-
const selectedValue = controlledValue === undefined ?
|
|
964
|
-
uncontrolledValue :
|
|
965
|
-
controlledDate ?
|
|
966
|
-
formatCalendarDate(controlledDate) :
|
|
967
|
-
'';
|
|
968
|
-
const selectedDate = parseCalendarDate(selectedValue);
|
|
969
|
-
const initialMonth = parseCalendarMonth(month) ??
|
|
970
|
-
(selectedDate ? startOfCalendarMonth(selectedDate) : null) ??
|
|
971
|
-
startOfCalendarMonth(getCalendarToday());
|
|
972
|
-
const [visibleMonthValue, setVisibleMonthValue] = useState(() => formatCalendarMonth(initialMonth));
|
|
973
|
-
const visibleMonth = parseCalendarMonth(month ?? visibleMonthValue) ?? initialMonth;
|
|
974
|
-
const [focusValue, setFocusValue] = useState(null);
|
|
975
|
-
const focusDate = getCalendarFocusDate(disabled, visibleMonth, minDate, maxDate, selectedDate, parseCalendarDate(focusValue));
|
|
976
|
-
const focusIso = formatCalendarDate(focusDate);
|
|
977
|
-
const selectedIso = selectedDate ? formatCalendarDate(selectedDate) : '';
|
|
978
|
-
const currentLocale = getCalendarLocale(locale);
|
|
979
|
-
const monthFormatter = useMemo(() => new Intl.DateTimeFormat(currentLocale, {
|
|
980
|
-
month: 'long',
|
|
981
|
-
timeZone: 'UTC',
|
|
982
|
-
year: 'numeric'
|
|
983
|
-
}), [currentLocale]);
|
|
984
|
-
const dayFormatter = useMemo(() => new Intl.DateTimeFormat(currentLocale, {
|
|
985
|
-
day: 'numeric',
|
|
986
|
-
month: 'long',
|
|
987
|
-
timeZone: 'UTC',
|
|
988
|
-
weekday: 'long',
|
|
989
|
-
year: 'numeric'
|
|
990
|
-
}), [currentLocale]);
|
|
991
|
-
const weekdayFormatter = useMemo(() => new Intl.DateTimeFormat(currentLocale, {
|
|
992
|
-
timeZone: 'UTC',
|
|
993
|
-
weekday: 'short'
|
|
994
|
-
}), [currentLocale]);
|
|
995
|
-
const todayIso = formatCalendarDate(getCalendarToday());
|
|
996
|
-
const label = monthFormatter.format(visibleMonth);
|
|
997
|
-
const weekdays = useMemo(() => Array.from({ length: 7 }, (_, index) => weekdayFormatter.format(new Date(Date.UTC(2026, 0, 5 + index)))), [weekdayFormatter]);
|
|
998
|
-
const weeks = useMemo(() => {
|
|
999
|
-
const firstCell = getCalendarGridStart(visibleMonth);
|
|
1000
|
-
return Array.from({ length: 6 }, (_, rowIndex) => Array.from({ length: 7 }, (_, columnIndex) => {
|
|
1001
|
-
const date = addCalendarDays(firstCell, rowIndex * 7 + columnIndex);
|
|
1002
|
-
const dateIso = formatCalendarDate(date);
|
|
1003
|
-
const unavailable = isCalendarDateDisabled(disabled, date, minDate, maxDate);
|
|
1004
|
-
return {
|
|
1005
|
-
date: dateIso,
|
|
1006
|
-
day: date.getUTCDate(),
|
|
1007
|
-
disabled: unavailable,
|
|
1008
|
-
label: dayFormatter.format(date),
|
|
1009
|
-
outside: formatCalendarMonth(date) !== formatCalendarMonth(visibleMonth),
|
|
1010
|
-
selected: selectedIso === dateIso,
|
|
1011
|
-
tabIndex: !unavailable && focusIso === dateIso ? 0 : -1,
|
|
1012
|
-
today: todayIso === dateIso
|
|
1013
|
-
};
|
|
1014
|
-
}));
|
|
1015
|
-
}, [
|
|
1016
|
-
dayFormatter,
|
|
1017
|
-
disabled,
|
|
1018
|
-
focusIso,
|
|
1019
|
-
maxDate,
|
|
1020
|
-
minDate,
|
|
1021
|
-
selectedIso,
|
|
1022
|
-
todayIso,
|
|
1023
|
-
visibleMonth
|
|
1024
|
-
]);
|
|
1025
|
-
const focusCalendarDate = dateInput => {
|
|
1026
|
-
const date = coerceCalendarDate(dateInput);
|
|
1027
|
-
if (!date)
|
|
1028
|
-
return;
|
|
1029
|
-
const nextDate = clampCalendarDate(date, minDate, maxDate);
|
|
1030
|
-
setVisibleMonthValue(formatCalendarMonth(startOfCalendarMonth(nextDate)));
|
|
1031
|
-
setFocusValue(formatCalendarDate(nextDate));
|
|
1032
|
-
};
|
|
1033
|
-
const selectDate = dateInput => {
|
|
1034
|
-
if (disabled)
|
|
1035
|
-
return;
|
|
1036
|
-
const date = coerceCalendarDate(dateInput);
|
|
1037
|
-
if (!date)
|
|
1038
|
-
return;
|
|
1039
|
-
const nextDate = clampCalendarDate(date, minDate, maxDate);
|
|
1040
|
-
if (isCalendarDateDisabled(disabled, nextDate, minDate, maxDate))
|
|
1041
|
-
return;
|
|
1042
|
-
const nextValue = formatCalendarDate(nextDate);
|
|
1043
|
-
if (controlledValue === undefined) {
|
|
1044
|
-
setUncontrolledValue(nextValue);
|
|
1045
|
-
}
|
|
1046
|
-
setVisibleMonthValue(formatCalendarMonth(startOfCalendarMonth(nextDate)));
|
|
1047
|
-
setFocusValue(nextValue);
|
|
1048
|
-
onValueChange?.(nextValue);
|
|
1049
|
-
};
|
|
1050
|
-
const moveFocus = (currentDate, key) => {
|
|
1051
|
-
const column = (currentDate.getUTCDay() + 6) % 7;
|
|
1052
|
-
const keyOffsets = {
|
|
1053
|
-
ArrowDown: 7,
|
|
1054
|
-
ArrowLeft: -1,
|
|
1055
|
-
ArrowRight: 1,
|
|
1056
|
-
ArrowUp: -7,
|
|
1057
|
-
End: 6 - column,
|
|
1058
|
-
Home: -column
|
|
1059
|
-
};
|
|
1060
|
-
if (key === 'PageDown' || key === 'PageUp') {
|
|
1061
|
-
focusCalendarDate(addCalendarMonths(currentDate, key === 'PageDown' ? 1 : -1));
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
const offset = keyOffsets[key];
|
|
1065
|
-
if (offset !== undefined) {
|
|
1066
|
-
focusCalendarDate(addCalendarDays(currentDate, offset));
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
const previousMonthLastDay = addCalendarDays(visibleMonth, -1);
|
|
1070
|
-
const nextMonthFirstDay = addCalendarMonths(visibleMonth, 1);
|
|
1071
|
-
const previousDisabled = disabled || compareCalendarDates(previousMonthLastDay, minDate) < 0;
|
|
1072
|
-
const nextDisabled = disabled || compareCalendarDates(nextMonthFirstDay, maxDate) > 0;
|
|
1073
|
-
const getDayProps = (day, props = {}) => ({
|
|
1074
|
-
...props,
|
|
1075
|
-
'aria-disabled': day.disabled ? true : undefined,
|
|
1076
|
-
'aria-label': props['aria-label'] ?? day.label,
|
|
1077
|
-
'aria-selected': day.selected ? 'true' : 'false',
|
|
1078
|
-
'data-date': day.date,
|
|
1079
|
-
'data-outside': day.outside ? 'true' : undefined,
|
|
1080
|
-
'data-selected': day.selected ? 'true' : undefined,
|
|
1081
|
-
'data-today': day.today ? 'true' : undefined,
|
|
1082
|
-
'data-ui-calendar-day': true,
|
|
1083
|
-
onClick: composeHandlers(props.onClick, () => {
|
|
1084
|
-
if (!day.disabled) {
|
|
1085
|
-
selectDate(day.date);
|
|
1086
|
-
}
|
|
1087
|
-
}),
|
|
1088
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
1089
|
-
if (day.disabled)
|
|
1090
|
-
return;
|
|
1091
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
1092
|
-
event.preventDefault();
|
|
1093
|
-
selectDate(day.date);
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
if (event.key !== 'ArrowDown' &&
|
|
1097
|
-
event.key !== 'ArrowLeft' &&
|
|
1098
|
-
event.key !== 'ArrowRight' &&
|
|
1099
|
-
event.key !== 'ArrowUp' &&
|
|
1100
|
-
event.key !== 'End' &&
|
|
1101
|
-
event.key !== 'Home' &&
|
|
1102
|
-
event.key !== 'PageDown' &&
|
|
1103
|
-
event.key !== 'PageUp') {
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
const date = parseCalendarDate(day.date);
|
|
1107
|
-
if (!date)
|
|
1108
|
-
return;
|
|
1109
|
-
event.preventDefault();
|
|
1110
|
-
moveFocus(date, event.key);
|
|
1111
|
-
}),
|
|
1112
|
-
role: 'gridcell',
|
|
1113
|
-
tabIndex: props.tabIndex ?? day.tabIndex
|
|
1114
|
-
});
|
|
1115
|
-
return {
|
|
1116
|
-
focusDate: focusCalendarDate,
|
|
1117
|
-
getDayProps,
|
|
1118
|
-
gridProps: {
|
|
1119
|
-
'aria-labelledby': labelId,
|
|
1120
|
-
className: 'ui-calendar__grid',
|
|
1121
|
-
'data-ui-calendar-grid': true,
|
|
1122
|
-
role: 'grid'
|
|
1123
|
-
},
|
|
1124
|
-
inputProps: {
|
|
1125
|
-
'data-ui-calendar-input': true,
|
|
1126
|
-
disabled,
|
|
1127
|
-
name,
|
|
1128
|
-
type: 'hidden',
|
|
1129
|
-
value: selectedValue
|
|
1130
|
-
},
|
|
1131
|
-
label,
|
|
1132
|
-
labelProps: {
|
|
1133
|
-
className: 'ui-calendar__label',
|
|
1134
|
-
'data-ui-calendar-label': true,
|
|
1135
|
-
id: labelId
|
|
1136
|
-
},
|
|
1137
|
-
month: formatCalendarMonth(visibleMonth),
|
|
1138
|
-
nextProps: {
|
|
1139
|
-
'aria-label': 'Next month',
|
|
1140
|
-
className: 'ui-calendar__nav',
|
|
1141
|
-
'data-ui-calendar-next': true,
|
|
1142
|
-
disabled: nextDisabled,
|
|
1143
|
-
onClick: () => {
|
|
1144
|
-
const nextMonth = startOfCalendarMonth(addCalendarMonths(visibleMonth, 1));
|
|
1145
|
-
setVisibleMonthValue(formatCalendarMonth(nextMonth));
|
|
1146
|
-
setFocusValue(formatCalendarDate(getCalendarFocusDate(disabled, nextMonth, minDate, maxDate, selectedDate)));
|
|
1147
|
-
},
|
|
1148
|
-
type: 'button'
|
|
1149
|
-
},
|
|
1150
|
-
previousProps: {
|
|
1151
|
-
'aria-label': 'Previous month',
|
|
1152
|
-
className: 'ui-calendar__nav',
|
|
1153
|
-
'data-ui-calendar-prev': true,
|
|
1154
|
-
disabled: previousDisabled,
|
|
1155
|
-
onClick: () => {
|
|
1156
|
-
const previousMonth = startOfCalendarMonth(addCalendarMonths(visibleMonth, -1));
|
|
1157
|
-
setVisibleMonthValue(formatCalendarMonth(previousMonth));
|
|
1158
|
-
setFocusValue(formatCalendarDate(getCalendarFocusDate(disabled, previousMonth, minDate, maxDate, selectedDate)));
|
|
1159
|
-
},
|
|
1160
|
-
type: 'button'
|
|
1161
|
-
},
|
|
1162
|
-
rootProps: {
|
|
1163
|
-
'aria-disabled': disabled ? true : undefined,
|
|
1164
|
-
className: 'ui-calendar',
|
|
1165
|
-
'data-disabled': disabled ? 'true' : undefined,
|
|
1166
|
-
'data-ui-calendar': true,
|
|
1167
|
-
'data-ui-calendar-initial-month': formatCalendarMonth(initialMonth),
|
|
1168
|
-
'data-ui-calendar-max': maxDate ? formatCalendarDate(maxDate) : undefined,
|
|
1169
|
-
'data-ui-calendar-min': minDate ? formatCalendarDate(minDate) : undefined,
|
|
1170
|
-
'data-ui-calendar-month': formatCalendarMonth(visibleMonth),
|
|
1171
|
-
'data-ui-calendar-value': selectedValue || undefined,
|
|
1172
|
-
ref: rootRef
|
|
1173
|
-
},
|
|
1174
|
-
rootRef,
|
|
1175
|
-
selectDate,
|
|
1176
|
-
value: selectedValue,
|
|
1177
|
-
weekdays,
|
|
1178
|
-
weeks
|
|
1179
|
-
};
|
|
1180
|
-
};
|
|
1181
|
-
/* eslint-enable @stylistic/padding-line-between-statements, complexity, no-nested-ternary */
|
|
1182
|
-
const defaultInputOtpLength = 6;
|
|
1183
|
-
const defaultInputOtpPattern = '[0-9]*';
|
|
1184
|
-
const coerceInputOtpLength = (length) => Number(length) || defaultInputOtpLength;
|
|
1185
|
-
const clampInputOtpLength = (length) => Math.max(1, Math.floor(length));
|
|
1186
|
-
const normalizeInputOtpLength = (length) => (clampInputOtpLength(coerceInputOtpLength(length)));
|
|
1187
|
-
const isNumericInputOtp = (inputMode, pattern) => (inputMode === 'numeric' || pattern === defaultInputOtpPattern);
|
|
1188
|
-
const sanitizeInputOtpValue = (value, length, inputMode, pattern) => {
|
|
1189
|
-
const normalized = isNumericInputOtp(inputMode, pattern) ?
|
|
1190
|
-
value.replaceAll(/\D/g, '') :
|
|
1191
|
-
value.replaceAll(/\s/g, '');
|
|
1192
|
-
return normalized.slice(0, length);
|
|
1193
|
-
};
|
|
1194
|
-
const getInputOtpActiveIndex = (length, value, selectionStart) => Math.min(length - 1, Math.max(0, selectionStart ?? value.length));
|
|
1195
|
-
export const useInputOTP = ({ defaultValue, disabled = false, inputMode = 'numeric', inputRef: forwardedInputRef, invalid = false, length: lengthOption, onValueChange, pattern = defaultInputOtpPattern, value: controlledValue } = {}) => {
|
|
1196
|
-
const length = normalizeInputOtpLength(lengthOption);
|
|
1197
|
-
const rootRef = useRef(null);
|
|
1198
|
-
const inputRef = useRef(null);
|
|
1199
|
-
const [focused, setFocused] = useState(false);
|
|
1200
|
-
const [activeIndex, setActiveIndex] = useState(0);
|
|
1201
|
-
const [uncontrolledValue, setUncontrolledValue] = useState(() => sanitizeInputOtpValue(defaultValue ?? '', length, inputMode, pattern));
|
|
1202
|
-
const value = sanitizeInputOtpValue(controlledValue ?? uncontrolledValue, length, inputMode, pattern);
|
|
1203
|
-
const segmentIndexes = useMemo(() => Array.from({ length }, (_, index) => index), [length]);
|
|
1204
|
-
const syncValue = useCallback((input = inputRef.current) => {
|
|
1205
|
-
if (!input)
|
|
1206
|
-
return value;
|
|
1207
|
-
const nextValue = sanitizeInputOtpValue(input.value, length, inputMode, pattern);
|
|
1208
|
-
if (input.value !== nextValue) {
|
|
1209
|
-
input.value = nextValue;
|
|
1210
|
-
}
|
|
1211
|
-
if (controlledValue === undefined) {
|
|
1212
|
-
setUncontrolledValue(nextValue);
|
|
1213
|
-
}
|
|
1214
|
-
setActiveIndex(getInputOtpActiveIndex(length, nextValue, input.selectionStart));
|
|
1215
|
-
return nextValue;
|
|
1216
|
-
}, [controlledValue, inputMode, length, pattern, value]);
|
|
1217
|
-
const setSelection = useCallback(index => {
|
|
1218
|
-
const input = inputRef.current;
|
|
1219
|
-
const position = Math.min(value.length, Math.max(0, index));
|
|
1220
|
-
setActiveIndex(getInputOtpActiveIndex(length, value, position));
|
|
1221
|
-
input?.focus({ preventScroll: true });
|
|
1222
|
-
input?.setSelectionRange(position, position);
|
|
1223
|
-
}, [length, value]);
|
|
1224
|
-
const setValue = useCallback(nextValue => {
|
|
1225
|
-
const sanitizedValue = sanitizeInputOtpValue(nextValue, length, inputMode, pattern);
|
|
1226
|
-
const input = inputRef.current;
|
|
1227
|
-
if (controlledValue === undefined) {
|
|
1228
|
-
setUncontrolledValue(sanitizedValue);
|
|
1229
|
-
}
|
|
1230
|
-
if (input) {
|
|
1231
|
-
input.value = sanitizedValue;
|
|
1232
|
-
input.setSelectionRange(sanitizedValue.length, sanitizedValue.length);
|
|
1233
|
-
}
|
|
1234
|
-
setActiveIndex(getInputOtpActiveIndex(length, sanitizedValue, sanitizedValue.length));
|
|
1235
|
-
onValueChange?.(sanitizedValue);
|
|
1236
|
-
return sanitizedValue;
|
|
1237
|
-
}, [controlledValue, inputMode, length, onValueChange, pattern]);
|
|
1238
|
-
const commitInputValue = useCallback((input) => {
|
|
1239
|
-
onValueChange?.(syncValue(input));
|
|
1240
|
-
}, [onValueChange, syncValue]);
|
|
1241
|
-
useEffect(() => {
|
|
1242
|
-
const form = inputRef.current?.form;
|
|
1243
|
-
let resetTimer;
|
|
1244
|
-
if (!form)
|
|
1245
|
-
return;
|
|
1246
|
-
const handleReset = () => {
|
|
1247
|
-
resetTimer = globalThis.setTimeout(() => {
|
|
1248
|
-
syncValue(inputRef.current);
|
|
1249
|
-
});
|
|
1250
|
-
};
|
|
1251
|
-
form.addEventListener('reset', handleReset);
|
|
1252
|
-
return () => {
|
|
1253
|
-
if (resetTimer) {
|
|
1254
|
-
globalThis.clearTimeout(resetTimer);
|
|
1255
|
-
}
|
|
1256
|
-
form.removeEventListener('reset', handleReset);
|
|
1257
|
-
};
|
|
1258
|
-
}, [syncValue]);
|
|
1259
|
-
const getInputProps = useCallback((props = {}) => ({
|
|
1260
|
-
...props,
|
|
1261
|
-
'aria-invalid': props['aria-invalid'] ?? (invalid ? true : undefined),
|
|
1262
|
-
autoComplete: props.autoComplete ?? 'one-time-code',
|
|
1263
|
-
className: composeClassName('ui-input-otp ui-input-otp__native', props.className),
|
|
1264
|
-
'data-ui-enhanced': true,
|
|
1265
|
-
'data-ui-input-otp-native': true,
|
|
1266
|
-
disabled: props.disabled ?? disabled,
|
|
1267
|
-
inputMode: props.inputMode ?? inputMode,
|
|
1268
|
-
maxLength: props.maxLength ?? length,
|
|
1269
|
-
onBlur: composeHandlers(props.onBlur, event => {
|
|
1270
|
-
setFocused(false);
|
|
1271
|
-
syncValue(event.currentTarget);
|
|
1272
|
-
}),
|
|
1273
|
-
onChange: composeHandlers(props.onChange, event => {
|
|
1274
|
-
commitInputValue(event.currentTarget);
|
|
1275
|
-
}),
|
|
1276
|
-
onClick: composeHandlers(props.onClick, event => {
|
|
1277
|
-
syncValue(event.currentTarget);
|
|
1278
|
-
}),
|
|
1279
|
-
onFocus: composeHandlers(props.onFocus, event => {
|
|
1280
|
-
setFocused(true);
|
|
1281
|
-
syncValue(event.currentTarget);
|
|
1282
|
-
}),
|
|
1283
|
-
onInput: composeHandlers(props.onInput, event => {
|
|
1284
|
-
commitInputValue(event.currentTarget);
|
|
1285
|
-
}),
|
|
1286
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
1287
|
-
if (event.key === 'ArrowLeft') {
|
|
1288
|
-
event.preventDefault();
|
|
1289
|
-
setSelection((event.currentTarget.selectionStart ??
|
|
1290
|
-
event.currentTarget.value.length) - 1);
|
|
1291
|
-
return;
|
|
1292
|
-
}
|
|
1293
|
-
if (event.key === 'ArrowRight') {
|
|
1294
|
-
event.preventDefault();
|
|
1295
|
-
setSelection((event.currentTarget.selectionStart ??
|
|
1296
|
-
event.currentTarget.value.length) + 1);
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
if (event.key === 'Home') {
|
|
1300
|
-
event.preventDefault();
|
|
1301
|
-
setSelection(0);
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
if (event.key === 'End') {
|
|
1305
|
-
event.preventDefault();
|
|
1306
|
-
setSelection(event.currentTarget.value.length);
|
|
1307
|
-
}
|
|
1308
|
-
}),
|
|
1309
|
-
onKeyUp: composeHandlers(props.onKeyUp, event => {
|
|
1310
|
-
syncValue(event.currentTarget);
|
|
1311
|
-
}),
|
|
1312
|
-
onPaste: composeHandlers(props.onPaste, event => {
|
|
1313
|
-
const pasted = event.clipboardData.getData('text');
|
|
1314
|
-
if (!pasted)
|
|
1315
|
-
return;
|
|
1316
|
-
event.preventDefault();
|
|
1317
|
-
setValue(pasted);
|
|
1318
|
-
}),
|
|
1319
|
-
pattern: props.pattern ?? pattern,
|
|
1320
|
-
ref: node => {
|
|
1321
|
-
inputRef.current = node;
|
|
1322
|
-
setRefValue(forwardedInputRef, node);
|
|
1323
|
-
},
|
|
1324
|
-
type: 'text',
|
|
1325
|
-
value
|
|
1326
|
-
}), [
|
|
1327
|
-
commitInputValue,
|
|
1328
|
-
disabled,
|
|
1329
|
-
forwardedInputRef,
|
|
1330
|
-
inputMode,
|
|
1331
|
-
invalid,
|
|
1332
|
-
length,
|
|
1333
|
-
pattern,
|
|
1334
|
-
setSelection,
|
|
1335
|
-
setValue,
|
|
1336
|
-
syncValue,
|
|
1337
|
-
value
|
|
1338
|
-
]);
|
|
1339
|
-
const getSegmentChar = useCallback(index => value[index] ?? '\u00a0', [value]);
|
|
1340
|
-
const getSegmentProps = useCallback((index, props = {}) => ({
|
|
1341
|
-
...props,
|
|
1342
|
-
'aria-hidden': true,
|
|
1343
|
-
className: composeClassName('ui-input-otp__segment', props.className),
|
|
1344
|
-
'data-active': String(focused && index === activeIndex),
|
|
1345
|
-
'data-index': index,
|
|
1346
|
-
'data-ui-input-otp-segment': true,
|
|
1347
|
-
disabled: props.disabled ?? disabled,
|
|
1348
|
-
onClick: composeHandlers(props.onClick, () => {
|
|
1349
|
-
setSelection(index);
|
|
1350
|
-
}),
|
|
1351
|
-
tabIndex: props.tabIndex ?? -1,
|
|
1352
|
-
type: 'button'
|
|
1353
|
-
}), [activeIndex, disabled, focused, setSelection]);
|
|
1354
|
-
return {
|
|
1355
|
-
activeIndex,
|
|
1356
|
-
getInputProps,
|
|
1357
|
-
getSegmentChar,
|
|
1358
|
-
getSegmentProps,
|
|
1359
|
-
inputRef,
|
|
1360
|
-
rootProps: {
|
|
1361
|
-
className: 'ui-input-otp-field',
|
|
1362
|
-
'data-disabled': disabled ? 'true' : 'false',
|
|
1363
|
-
'data-invalid': invalid ? 'true' : 'false',
|
|
1364
|
-
'data-ui-input-otp': true,
|
|
1365
|
-
'data-ui-input-otp-length': length,
|
|
1366
|
-
ref: rootRef
|
|
1367
|
-
},
|
|
1368
|
-
rootRef,
|
|
1369
|
-
segmentIndexes,
|
|
1370
|
-
segmentsProps: {
|
|
1371
|
-
className: 'ui-input-otp__segments',
|
|
1372
|
-
'data-ui-input-otp-segments': true
|
|
1373
|
-
},
|
|
1374
|
-
setSelection,
|
|
1375
|
-
setValue,
|
|
1376
|
-
syncValue,
|
|
1377
|
-
value
|
|
1378
|
-
};
|
|
1379
|
-
};
|
|
1380
|
-
const getDateRangeRoot = (control, fallback) => control.closest('[data-ui-date-range-picker]') ?? fallback;
|
|
1381
|
-
const getDateRangeInputs = (root, start, end) => {
|
|
1382
|
-
if (start && end && start !== end)
|
|
1383
|
-
return [start, end];
|
|
1384
|
-
const inputs = root ?
|
|
1385
|
-
[...root.querySelectorAll('input[type="date"]')] :
|
|
1386
|
-
[];
|
|
1387
|
-
return [start ?? inputs[0] ?? null, end ?? inputs.at(-1) ?? null];
|
|
1388
|
-
};
|
|
1389
|
-
const syncDateInputConstraint = (input, property, value) => {
|
|
1390
|
-
if (value) {
|
|
1391
|
-
input[property] = value;
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
input.removeAttribute(property);
|
|
1395
|
-
};
|
|
1396
|
-
const syncDateRangeInputs = (start, end) => {
|
|
1397
|
-
if (!start || !end || start === end)
|
|
1398
|
-
return {};
|
|
1399
|
-
syncDateInputConstraint(end, 'min', start.value);
|
|
1400
|
-
syncDateInputConstraint(start, 'max', end.value);
|
|
1401
|
-
if (start.value && end.value && end.value < start.value) {
|
|
1402
|
-
end.value = start.value;
|
|
1403
|
-
}
|
|
1404
|
-
return {
|
|
1405
|
-
...(end.value ? { end: end.value } : {}),
|
|
1406
|
-
...(start.value ? { start: start.value } : {})
|
|
1407
|
-
};
|
|
1408
|
-
};
|
|
1409
|
-
export const useDateRangePicker = ({ onRangeChange } = {}) => {
|
|
1410
|
-
const rootRef = useRef(null);
|
|
1411
|
-
const startRef = useRef(null);
|
|
1412
|
-
const endRef = useRef(null);
|
|
1413
|
-
const syncRange = useCallback((root = rootRef.current) => {
|
|
1414
|
-
const [start, end] = getDateRangeInputs(root, startRef.current, endRef.current);
|
|
1415
|
-
const detail = syncDateRangeInputs(start, end);
|
|
1416
|
-
onRangeChange?.(detail);
|
|
1417
|
-
return detail;
|
|
1418
|
-
}, [onRangeChange]);
|
|
1419
|
-
const getStartProps = useCallback((props = {}) => ({
|
|
1420
|
-
...props,
|
|
1421
|
-
onChange: composeHandlers(props.onChange, event => {
|
|
1422
|
-
startRef.current = event.currentTarget;
|
|
1423
|
-
syncRange(getDateRangeRoot(event.currentTarget, rootRef.current));
|
|
1424
|
-
}),
|
|
1425
|
-
ref: startRef,
|
|
1426
|
-
type: props.type ?? 'date'
|
|
1427
|
-
}), [syncRange]);
|
|
1428
|
-
const getEndProps = useCallback((props = {}) => ({
|
|
1429
|
-
...props,
|
|
1430
|
-
onChange: composeHandlers(props.onChange, event => {
|
|
1431
|
-
endRef.current = event.currentTarget;
|
|
1432
|
-
syncRange(getDateRangeRoot(event.currentTarget, rootRef.current));
|
|
1433
|
-
}),
|
|
1434
|
-
ref: endRef,
|
|
1435
|
-
type: props.type ?? 'date'
|
|
1436
|
-
}), [syncRange]);
|
|
1437
|
-
return {
|
|
1438
|
-
endRef,
|
|
1439
|
-
getEndProps,
|
|
1440
|
-
getStartProps,
|
|
1441
|
-
rootProps: {
|
|
1442
|
-
'data-ui-date-range-picker': true,
|
|
1443
|
-
ref: rootRef
|
|
1444
|
-
},
|
|
1445
|
-
rootRef,
|
|
1446
|
-
startRef,
|
|
1447
|
-
syncRange
|
|
1448
|
-
};
|
|
1449
|
-
};
|
|
1450
|
-
const richTextEditorContentSelector = '[data-ui-rich-text-editable], [contenteditable="true"]';
|
|
1451
|
-
const getRichTextChangeDetail = (root) => {
|
|
1452
|
-
const editable = root?.querySelector(richTextEditorContentSelector);
|
|
1453
|
-
if (!editable)
|
|
1454
|
-
return null;
|
|
1455
|
-
return {
|
|
1456
|
-
html: editable.innerHTML,
|
|
1457
|
-
text: editable.textContent
|
|
1458
|
-
};
|
|
1459
|
-
};
|
|
1460
|
-
const executeRichTextDocumentCommand = (commandDocument, command, value) => {
|
|
1461
|
-
if (typeof commandDocument?.execCommand !== 'function')
|
|
1462
|
-
return false;
|
|
1463
|
-
return value === undefined ?
|
|
1464
|
-
commandDocument.execCommand(command) :
|
|
1465
|
-
commandDocument.execCommand(command, false, value);
|
|
1466
|
-
};
|
|
1467
|
-
// eslint-disable-next-line complexity -- Command-state normalization covers toggle and value-bearing controls together.
|
|
1468
|
-
const syncRichTextCommandStates = (root) => {
|
|
1469
|
-
if (!root || typeof document === 'undefined')
|
|
1470
|
-
return;
|
|
1471
|
-
const commandDocument = document;
|
|
1472
|
-
for (const control of root.querySelectorAll('[data-ui-editor-command]')) {
|
|
1473
|
-
const command = control.dataset.uiEditorCommand ?? '';
|
|
1474
|
-
const value = control.dataset.uiEditorValue;
|
|
1475
|
-
let active = false;
|
|
1476
|
-
if (isLumenRichTextToggleCommand(command)) {
|
|
1477
|
-
active = commandDocument.queryCommandState?.(command) ?? false;
|
|
1478
|
-
control.setAttribute('aria-pressed', String(active));
|
|
1479
|
-
}
|
|
1480
|
-
else if (command === 'formatBlock' && value) {
|
|
1481
|
-
const currentValue = commandDocument
|
|
1482
|
-
.queryCommandValue?.(command)
|
|
1483
|
-
.replaceAll(/[<>]/g, '')
|
|
1484
|
-
.toLowerCase();
|
|
1485
|
-
active = currentValue === value.replaceAll(/[<>]/g, '').toLowerCase();
|
|
1486
|
-
}
|
|
1487
|
-
control.dataset.state = active ? 'on' : 'off';
|
|
1488
|
-
}
|
|
1489
|
-
};
|
|
1490
|
-
export const useRichTextEditor = ({ onChange, onCommand } = {}) => {
|
|
1491
|
-
const rootRef = useRef(null);
|
|
1492
|
-
const emitChange = useCallback((root) => {
|
|
1493
|
-
const detail = getRichTextChangeDetail(root);
|
|
1494
|
-
if (!detail)
|
|
1495
|
-
return;
|
|
1496
|
-
if (typeof CustomEvent !== 'undefined') {
|
|
1497
|
-
root?.dispatchEvent(new CustomEvent('ui:editor-change', {
|
|
1498
|
-
bubbles: true,
|
|
1499
|
-
detail
|
|
1500
|
-
}));
|
|
1501
|
-
}
|
|
1502
|
-
onChange?.(detail);
|
|
1503
|
-
}, [onChange]);
|
|
1504
|
-
const executeCommand = useCallback((command, root = rootRef.current, value) => {
|
|
1505
|
-
if (!command)
|
|
1506
|
-
return false;
|
|
1507
|
-
const commandDocument = typeof document === 'undefined' ?
|
|
1508
|
-
undefined :
|
|
1509
|
-
document;
|
|
1510
|
-
const executed = executeRichTextDocumentCommand(commandDocument, command, value);
|
|
1511
|
-
const detail = {
|
|
1512
|
-
command,
|
|
1513
|
-
executed,
|
|
1514
|
-
...(value === undefined ? {} : { value })
|
|
1515
|
-
};
|
|
1516
|
-
if (root && typeof CustomEvent !== 'undefined') {
|
|
1517
|
-
root.dispatchEvent(new CustomEvent('ui:editor-command', {
|
|
1518
|
-
bubbles: true,
|
|
1519
|
-
detail
|
|
1520
|
-
}));
|
|
1521
|
-
}
|
|
1522
|
-
onCommand?.(detail);
|
|
1523
|
-
syncRichTextCommandStates(root);
|
|
1524
|
-
emitChange(root);
|
|
1525
|
-
return executed;
|
|
1526
|
-
}, [emitChange, onCommand]);
|
|
1527
|
-
const getCommandProps = useCallback((command, props = {}) => ({
|
|
1528
|
-
...props,
|
|
1529
|
-
'data-ui-editor-command': command,
|
|
1530
|
-
onClick: composeHandlers(props.onClick, event => {
|
|
1531
|
-
const root = event.currentTarget.closest('[data-ui-rich-text-editor]') ?? rootRef.current;
|
|
1532
|
-
executeCommand(command, root, event.currentTarget.dataset.uiEditorValue);
|
|
1533
|
-
}),
|
|
1534
|
-
type: props.type ?? 'button'
|
|
1535
|
-
}), [executeCommand]);
|
|
1536
|
-
const getEditableProps = useCallback((props = {}) => ({
|
|
1537
|
-
...props,
|
|
1538
|
-
'aria-multiline': props['aria-multiline'] ?? true,
|
|
1539
|
-
contentEditable: props.contentEditable ?? true,
|
|
1540
|
-
'data-ui-rich-text-editable': true,
|
|
1541
|
-
onInput: composeHandlers(props.onInput, event => {
|
|
1542
|
-
const root = event.currentTarget.closest('[data-ui-rich-text-editor]') ?? rootRef.current;
|
|
1543
|
-
syncRichTextCommandStates(root);
|
|
1544
|
-
emitChange(root);
|
|
1545
|
-
}),
|
|
1546
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
1547
|
-
const command = getLumenRichTextShortcut(event);
|
|
1548
|
-
if (!command)
|
|
1549
|
-
return;
|
|
1550
|
-
event.preventDefault();
|
|
1551
|
-
const root = event.currentTarget.closest('[data-ui-rich-text-editor]') ?? rootRef.current;
|
|
1552
|
-
executeCommand(command, root);
|
|
1553
|
-
}),
|
|
1554
|
-
onKeyUp: composeHandlers(props.onKeyUp, event => {
|
|
1555
|
-
syncRichTextCommandStates(event.currentTarget.closest('[data-ui-rich-text-editor]') ?? rootRef.current);
|
|
1556
|
-
}),
|
|
1557
|
-
onMouseUp: composeHandlers(props.onMouseUp, event => {
|
|
1558
|
-
syncRichTextCommandStates(event.currentTarget.closest('[data-ui-rich-text-editor]') ?? rootRef.current);
|
|
1559
|
-
}),
|
|
1560
|
-
role: props.role ?? 'textbox',
|
|
1561
|
-
suppressContentEditableWarning: props.suppressContentEditableWarning ?? true
|
|
1562
|
-
}), [emitChange, executeCommand]);
|
|
1563
|
-
return {
|
|
1564
|
-
executeCommand,
|
|
1565
|
-
getCommandProps,
|
|
1566
|
-
getEditableProps,
|
|
1567
|
-
rootProps: {
|
|
1568
|
-
'data-ui-rich-text-editor': true,
|
|
1569
|
-
ref: rootRef
|
|
1570
|
-
},
|
|
1571
|
-
rootRef
|
|
1572
|
-
};
|
|
1573
|
-
};
|
|
1574
|
-
const getScheduleRoot = (element, fallback) => element.closest('[data-ui-schedule]') ?? fallback;
|
|
1575
|
-
export const useSchedule = ({ onChange } = {}) => {
|
|
1576
|
-
const rootRef = useRef(null);
|
|
1577
|
-
const emitChange = useCallback((detail, root = rootRef.current) => {
|
|
1578
|
-
if (root && typeof CustomEvent !== 'undefined') {
|
|
1579
|
-
root.dispatchEvent(new CustomEvent('ui:schedule-change', {
|
|
1580
|
-
bubbles: true,
|
|
1581
|
-
detail
|
|
1582
|
-
}));
|
|
1583
|
-
}
|
|
1584
|
-
onChange?.(detail);
|
|
1585
|
-
}, [onChange]);
|
|
1586
|
-
const getEventProps = useCallback((eventId, props = {}) => ({
|
|
1587
|
-
...props,
|
|
1588
|
-
...(eventId && !props.id ? { id: eventId } : {}),
|
|
1589
|
-
'data-ui-draggable': true,
|
|
1590
|
-
'data-ui-schedule-event': true,
|
|
1591
|
-
draggable: props.draggable ?? true,
|
|
1592
|
-
onDragEnd: composeHandlers(props.onDragEnd, event => {
|
|
1593
|
-
const root = getScheduleRoot(event.currentTarget, rootRef.current);
|
|
1594
|
-
if (root) {
|
|
1595
|
-
delete root.dataset.uiDragging;
|
|
1596
|
-
}
|
|
1597
|
-
}),
|
|
1598
|
-
onDragStart: composeHandlers(props.onDragStart, event => {
|
|
1599
|
-
const transferValue = eventId ||
|
|
1600
|
-
event.currentTarget.id ||
|
|
1601
|
-
event.currentTarget.textContent.trim();
|
|
1602
|
-
const root = getScheduleRoot(event.currentTarget, rootRef.current);
|
|
1603
|
-
event.dataTransfer.setData('text/plain', transferValue);
|
|
1604
|
-
if (root) {
|
|
1605
|
-
root.dataset.uiDragging = 'true';
|
|
1606
|
-
}
|
|
1607
|
-
})
|
|
1608
|
-
}), []);
|
|
1609
|
-
const getSlotProps = useCallback((slot, props = {}) => ({
|
|
1610
|
-
...props,
|
|
1611
|
-
'data-ui-schedule-slot': slot,
|
|
1612
|
-
onDragLeave: composeHandlers(props.onDragLeave, event => {
|
|
1613
|
-
delete event.currentTarget.dataset.state;
|
|
1614
|
-
}),
|
|
1615
|
-
onDragOver: composeHandlers(props.onDragOver, event => {
|
|
1616
|
-
event.preventDefault();
|
|
1617
|
-
event.currentTarget.dataset.state = 'drag-over';
|
|
1618
|
-
}),
|
|
1619
|
-
onDrop: composeHandlers(props.onDrop, event => {
|
|
1620
|
-
event.preventDefault();
|
|
1621
|
-
delete event.currentTarget.dataset.state;
|
|
1622
|
-
const draggedId = event.dataTransfer.getData('text/plain');
|
|
1623
|
-
const dragged = typeof document !== 'undefined' && draggedId ?
|
|
1624
|
-
document.getElementById(draggedId) :
|
|
1625
|
-
null;
|
|
1626
|
-
if (typeof HTMLElement !== 'undefined' &&
|
|
1627
|
-
dragged instanceof HTMLElement) {
|
|
1628
|
-
event.currentTarget.append(dragged);
|
|
1629
|
-
}
|
|
1630
|
-
emitChange({
|
|
1631
|
-
...(draggedId ? { eventId: draggedId } : {}),
|
|
1632
|
-
...(slot ? { slot } : {})
|
|
1633
|
-
}, getScheduleRoot(event.currentTarget, rootRef.current));
|
|
1634
|
-
})
|
|
1635
|
-
}), [emitChange]);
|
|
1636
|
-
return {
|
|
1637
|
-
emitChange,
|
|
1638
|
-
getEventProps,
|
|
1639
|
-
getSlotProps,
|
|
1640
|
-
rootProps: {
|
|
1641
|
-
'data-ui-schedule': true,
|
|
1642
|
-
ref: rootRef
|
|
1643
|
-
},
|
|
1644
|
-
rootRef
|
|
1645
|
-
};
|
|
1646
|
-
};
|
|
1647
|
-
const getKanbanRoot = (element, fallback) => element.closest('[data-ui-kanban]') ?? fallback;
|
|
1648
|
-
const getKanbanItem = (root, itemId) => [...(root?.querySelectorAll('[data-ui-kanban-item]') ?? [])]
|
|
1649
|
-
.find(item => item.dataset.uiKanbanItem === itemId) ?? null;
|
|
1650
|
-
const getKanbanColumnValues = (root) => [...(root?.querySelectorAll('[data-ui-kanban-column]') ?? [])]
|
|
1651
|
-
.map(column => column.dataset.uiKanbanColumn ?? '')
|
|
1652
|
-
.filter(Boolean);
|
|
1653
|
-
const isKanbanArrowKey = (key) => ['ArrowLeft', 'ArrowRight'].includes(key);
|
|
1654
|
-
const clearKanbanInteractionState = (root, item) => {
|
|
1655
|
-
if (item?.dataset.state === 'dragging')
|
|
1656
|
-
delete item.dataset.state;
|
|
1657
|
-
for (const column of root?.querySelectorAll('[data-ui-kanban-column]') ?? [])
|
|
1658
|
-
delete column.dataset.state;
|
|
1659
|
-
};
|
|
1660
|
-
const setKanbanDropTarget = (root, target) => {
|
|
1661
|
-
for (const column of root?.querySelectorAll('[data-ui-kanban-column]') ?? []) {
|
|
1662
|
-
if (column === target)
|
|
1663
|
-
column.dataset.state = 'drop-target';
|
|
1664
|
-
else
|
|
1665
|
-
delete column.dataset.state;
|
|
1666
|
-
}
|
|
1667
|
-
};
|
|
1668
|
-
const getKanbanColumnAtPoint = (root, clientX, clientY) => {
|
|
1669
|
-
if (!root)
|
|
1670
|
-
return null;
|
|
1671
|
-
const column = document.elementFromPoint(clientX, clientY)
|
|
1672
|
-
?.closest('[data-ui-kanban-column]') ?? null;
|
|
1673
|
-
return column?.closest('[data-ui-kanban]') === root ? column : null;
|
|
1674
|
-
};
|
|
1675
|
-
const requestKeyboardKanbanMove = (event, itemId, root, requestMove) => {
|
|
1676
|
-
if (!isKanbanArrowKey(event.key))
|
|
1677
|
-
return;
|
|
1678
|
-
if (!root)
|
|
1679
|
-
return;
|
|
1680
|
-
const item = getKanbanItem(root, itemId);
|
|
1681
|
-
if (!item)
|
|
1682
|
-
return;
|
|
1683
|
-
if (item.getAttribute('aria-busy') === 'true')
|
|
1684
|
-
return;
|
|
1685
|
-
const fromColumn = item.closest('[data-ui-kanban-column]')
|
|
1686
|
-
?.dataset.uiKanbanColumn;
|
|
1687
|
-
if (!fromColumn)
|
|
1688
|
-
return;
|
|
1689
|
-
const columns = getKanbanColumnValues(root);
|
|
1690
|
-
const currentIndex = columns.indexOf(fromColumn);
|
|
1691
|
-
const visualDirection = event.key === 'ArrowRight' ? 1 : -1;
|
|
1692
|
-
const direction = getComputedStyle(root).direction === 'rtl' ?
|
|
1693
|
-
-visualDirection :
|
|
1694
|
-
visualDirection;
|
|
1695
|
-
const toColumn = columns[currentIndex + direction];
|
|
1696
|
-
if (!toColumn)
|
|
1697
|
-
return;
|
|
1698
|
-
event.preventDefault();
|
|
1699
|
-
requestMove({ fromColumn, input: 'keyboard', itemId, toColumn });
|
|
1700
|
-
};
|
|
1701
|
-
const updateKanbanPointer = (event, pointer, root) => {
|
|
1702
|
-
if (pointer?.pointerId !== event.pointerId)
|
|
1703
|
-
return;
|
|
1704
|
-
const distance = Math.hypot(event.clientX - pointer.startX, event.clientY - pointer.startY);
|
|
1705
|
-
if (distance < 8) {
|
|
1706
|
-
if (!pointer.active)
|
|
1707
|
-
return;
|
|
1708
|
-
}
|
|
1709
|
-
pointer.active = true;
|
|
1710
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1711
|
-
const item = getKanbanItem(root, pointer.itemId);
|
|
1712
|
-
const target = getKanbanColumnAtPoint(root, event.clientX, event.clientY);
|
|
1713
|
-
if (item)
|
|
1714
|
-
item.dataset.state = 'dragging';
|
|
1715
|
-
setKanbanDropTarget(root, target);
|
|
1716
|
-
};
|
|
1717
|
-
const finishKanbanPointer = (event, pointer, root, requestMove) => {
|
|
1718
|
-
if (pointer?.pointerId !== event.pointerId)
|
|
1719
|
-
return;
|
|
1720
|
-
if (!pointer.active)
|
|
1721
|
-
return;
|
|
1722
|
-
const item = getKanbanItem(root, pointer.itemId);
|
|
1723
|
-
const fromColumn = item?.closest('[data-ui-kanban-column]')
|
|
1724
|
-
?.dataset.uiKanbanColumn;
|
|
1725
|
-
const toColumn = getKanbanColumnAtPoint(root, event.clientX, event.clientY)
|
|
1726
|
-
?.dataset.uiKanbanColumn;
|
|
1727
|
-
clearKanbanInteractionState(root, item);
|
|
1728
|
-
if (fromColumn && toColumn)
|
|
1729
|
-
requestMove({ fromColumn, input: 'pointer', itemId: pointer.itemId, toColumn });
|
|
1730
|
-
};
|
|
1731
|
-
export const useKanban = ({ onMoveRequest } = {}) => {
|
|
1732
|
-
const rootRef = useRef(null);
|
|
1733
|
-
const pointerRef = useRef(null);
|
|
1734
|
-
const requestMove = useCallback(detail => {
|
|
1735
|
-
const normalized = createLumenKanbanMoveDetail(detail);
|
|
1736
|
-
if (!normalized)
|
|
1737
|
-
return false;
|
|
1738
|
-
const accepted = rootRef.current && typeof CustomEvent !== 'undefined' ?
|
|
1739
|
-
rootRef.current.dispatchEvent(new CustomEvent('ui:kanban-move-request', {
|
|
1740
|
-
bubbles: true,
|
|
1741
|
-
cancelable: true,
|
|
1742
|
-
detail: normalized
|
|
1743
|
-
})) :
|
|
1744
|
-
true;
|
|
1745
|
-
if (accepted)
|
|
1746
|
-
onMoveRequest?.(normalized);
|
|
1747
|
-
return accepted;
|
|
1748
|
-
}, [onMoveRequest]);
|
|
1749
|
-
const getItemProps = useCallback((itemId, props = {}) => ({
|
|
1750
|
-
...props,
|
|
1751
|
-
'data-ui-kanban-item': itemId,
|
|
1752
|
-
id: props.id ?? itemId
|
|
1753
|
-
}), []);
|
|
1754
|
-
const getHandleProps = useCallback((itemId, props = {}) => ({
|
|
1755
|
-
...props,
|
|
1756
|
-
'aria-keyshortcuts': props['aria-keyshortcuts'] ?? 'ArrowLeft ArrowRight',
|
|
1757
|
-
'data-ui-kanban-handle': itemId,
|
|
1758
|
-
draggable: props.draggable ?? true,
|
|
1759
|
-
onDragEnd: composeHandlers(props.onDragEnd, event => {
|
|
1760
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1761
|
-
const item = getKanbanItem(root, itemId);
|
|
1762
|
-
if (item?.dataset.state === 'dragging')
|
|
1763
|
-
delete item.dataset.state;
|
|
1764
|
-
}),
|
|
1765
|
-
onDragStart: composeHandlers(props.onDragStart, event => {
|
|
1766
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1767
|
-
const item = getKanbanItem(root, itemId);
|
|
1768
|
-
if (!item || item.getAttribute('aria-busy') === 'true') {
|
|
1769
|
-
event.preventDefault();
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
event.dataTransfer.setData('text/plain', itemId);
|
|
1773
|
-
event.dataTransfer.effectAllowed = 'move';
|
|
1774
|
-
item.dataset.state = 'dragging';
|
|
1775
|
-
}),
|
|
1776
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
1777
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1778
|
-
requestKeyboardKanbanMove(event, itemId, root, requestMove);
|
|
1779
|
-
}),
|
|
1780
|
-
onPointerDown: composeHandlers(props.onPointerDown, (event) => {
|
|
1781
|
-
if (event.pointerType === 'mouse')
|
|
1782
|
-
return;
|
|
1783
|
-
pointerRef.current = {
|
|
1784
|
-
active: false,
|
|
1785
|
-
itemId,
|
|
1786
|
-
pointerId: event.pointerId,
|
|
1787
|
-
startX: event.clientX,
|
|
1788
|
-
startY: event.clientY
|
|
1789
|
-
};
|
|
1790
|
-
}),
|
|
1791
|
-
onPointerMove: composeHandlers(props.onPointerMove, (event) => {
|
|
1792
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1793
|
-
updateKanbanPointer(event, pointerRef.current, root);
|
|
1794
|
-
}),
|
|
1795
|
-
onPointerCancel: composeHandlers(props.onPointerCancel, (event) => {
|
|
1796
|
-
const pointer = pointerRef.current;
|
|
1797
|
-
if (pointer?.pointerId !== event.pointerId)
|
|
1798
|
-
return;
|
|
1799
|
-
pointerRef.current = null;
|
|
1800
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1801
|
-
const item = getKanbanItem(root, pointer.itemId);
|
|
1802
|
-
clearKanbanInteractionState(root, item);
|
|
1803
|
-
}),
|
|
1804
|
-
onPointerUp: composeHandlers(props.onPointerUp, (event) => {
|
|
1805
|
-
const pointer = pointerRef.current;
|
|
1806
|
-
pointerRef.current = null;
|
|
1807
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1808
|
-
finishKanbanPointer(event, pointer, root, requestMove);
|
|
1809
|
-
}),
|
|
1810
|
-
type: props.type ?? 'button'
|
|
1811
|
-
}), [requestMove]);
|
|
1812
|
-
const getColumnProps = useCallback((column, props = {}) => ({
|
|
1813
|
-
...props,
|
|
1814
|
-
'data-ui-kanban-column': column,
|
|
1815
|
-
onDragLeave: composeHandlers(props.onDragLeave, event => {
|
|
1816
|
-
delete event.currentTarget.dataset.state;
|
|
1817
|
-
}),
|
|
1818
|
-
onDragOver: composeHandlers(props.onDragOver, event => {
|
|
1819
|
-
event.preventDefault();
|
|
1820
|
-
event.dataTransfer.dropEffect = 'move';
|
|
1821
|
-
event.currentTarget.dataset.state = 'drop-target';
|
|
1822
|
-
}),
|
|
1823
|
-
onDrop: composeHandlers(props.onDrop, event => {
|
|
1824
|
-
event.preventDefault();
|
|
1825
|
-
delete event.currentTarget.dataset.state;
|
|
1826
|
-
const itemId = event.dataTransfer.getData('text/plain');
|
|
1827
|
-
const root = getKanbanRoot(event.currentTarget, rootRef.current);
|
|
1828
|
-
const item = getKanbanItem(root, itemId);
|
|
1829
|
-
const fromColumn = item?.closest('[data-ui-kanban-column]')
|
|
1830
|
-
?.dataset.uiKanbanColumn;
|
|
1831
|
-
if (item?.dataset.state === 'dragging')
|
|
1832
|
-
delete item.dataset.state;
|
|
1833
|
-
if (fromColumn)
|
|
1834
|
-
requestMove({ fromColumn, input: 'pointer', itemId, toColumn: column });
|
|
1835
|
-
})
|
|
1836
|
-
}), [requestMove]);
|
|
1837
|
-
return {
|
|
1838
|
-
getColumnProps,
|
|
1839
|
-
getHandleProps,
|
|
1840
|
-
getItemProps,
|
|
1841
|
-
requestMove,
|
|
1842
|
-
rootProps: {
|
|
1843
|
-
'aria-orientation': 'horizontal',
|
|
1844
|
-
'data-ui-kanban': true,
|
|
1845
|
-
ref: rootRef,
|
|
1846
|
-
tabIndex: 0
|
|
1847
|
-
},
|
|
1848
|
-
rootRef
|
|
1849
|
-
};
|
|
1850
|
-
};
|
|
1851
|
-
/* eslint-disable @stylistic/padding-line-between-statements, @eslint-react/set-state-in-effect */
|
|
1852
|
-
/* eslint-disable @typescript-eslint/prefer-optional-chain, complexity, no-nested-ternary */
|
|
1853
|
-
/* Resizable mirrors Astro's compact pane sizing runtime. */
|
|
1854
|
-
const parseResizableNumberList = (value, count, fallback) => {
|
|
1855
|
-
const values = Array.isArray(value) ?
|
|
1856
|
-
value :
|
|
1857
|
-
value === undefined ?
|
|
1858
|
-
[] :
|
|
1859
|
-
[value];
|
|
1860
|
-
return Array.from({ length: count }, (_, index) => values[index] ?? values[0] ?? fallback);
|
|
1861
|
-
};
|
|
1862
|
-
const normalizeResizableSizes = (sizes, count) => {
|
|
1863
|
-
const fallbackSize = 100 / Math.max(1, count);
|
|
1864
|
-
const usableSizes = Array.from({ length: count }, (_, index) => {
|
|
1865
|
-
const size = sizes[index] ?? fallbackSize;
|
|
1866
|
-
return Number.isFinite(size) && size > 0 ? size : fallbackSize;
|
|
1867
|
-
});
|
|
1868
|
-
const total = usableSizes.reduce((sum, size) => sum + size, 0);
|
|
1869
|
-
if (total <= 0)
|
|
1870
|
-
return Array.from({ length: count }, () => fallbackSize);
|
|
1871
|
-
return usableSizes.map(size => (size / total) * 100);
|
|
1872
|
-
};
|
|
1873
|
-
const serializeResizableSize = (value) => Array.isArray(value) ?
|
|
1874
|
-
value.join(',') :
|
|
1875
|
-
value === undefined ?
|
|
1876
|
-
undefined :
|
|
1877
|
-
String(value);
|
|
1878
|
-
export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize, minSize, onSizesChange, panelCount: panelCountOption = 2, resetOnDoubleClick = true } = {}) => {
|
|
1879
|
-
const panelCount = Math.max(0, Math.floor(panelCountOption));
|
|
1880
|
-
const rootRef = useRef(null);
|
|
1881
|
-
const dragRef = useRef(null);
|
|
1882
|
-
const minSizes = useMemo(() => parseResizableNumberList(minSize, panelCount, 12), [minSize, panelCount]);
|
|
1883
|
-
const maxSizes = useMemo(() => parseResizableNumberList(maxSize, panelCount, 88), [maxSize, panelCount]);
|
|
1884
|
-
const initialSizes = useMemo(() => normalizeResizableSizes(parseResizableNumberList(defaultSizes, panelCount, 100 / Math.max(1, panelCount)), panelCount), [defaultSizes, panelCount]);
|
|
1885
|
-
const [sizes, setSizes] = useState(initialSizes);
|
|
1886
|
-
const axis = direction === 'horizontal' ? 'clientX' : 'clientY';
|
|
1887
|
-
const sizeProperty = direction === 'horizontal' ? 'width' : 'height';
|
|
1888
|
-
const separatorOrientation = direction === 'horizontal' ? 'vertical' : 'horizontal';
|
|
1889
|
-
const panelIndexes = useMemo(() => Array.from({ length: panelCount }, (_, index) => index), [panelCount]);
|
|
1890
|
-
const handleIndexes = useMemo(() => Array.from({ length: Math.max(0, panelCount - 1) }, (_, index) => index), [panelCount]);
|
|
1891
|
-
useEffect(() => {
|
|
1892
|
-
setSizes(initialSizes);
|
|
1893
|
-
}, [initialSizes]);
|
|
1894
|
-
const updateSizes = useCallback((updater) => {
|
|
1895
|
-
setSizes(currentSizes => {
|
|
1896
|
-
const nextSizes = updater(currentSizes);
|
|
1897
|
-
onSizesChange?.(nextSizes);
|
|
1898
|
-
return nextSizes;
|
|
1899
|
-
});
|
|
1900
|
-
}, [onSizesChange]);
|
|
1901
|
-
const resizePair = useCallback((index, nextSize) => {
|
|
1902
|
-
updateSizes(currentSizes => {
|
|
1903
|
-
const nextSizes = [...currentSizes];
|
|
1904
|
-
const nextIndex = index + 1;
|
|
1905
|
-
const total = (nextSizes[index] ?? 0) + (nextSizes[nextIndex] ?? 0);
|
|
1906
|
-
const min = Math.max(minSizes[index] ?? 0, total - (maxSizes[nextIndex] ?? 100));
|
|
1907
|
-
const max = Math.min(maxSizes[index] ?? 100, total - (minSizes[nextIndex] ?? 0));
|
|
1908
|
-
const paneSize = Math.min(max, Math.max(min, nextSize));
|
|
1909
|
-
nextSizes[index] = paneSize;
|
|
1910
|
-
nextSizes[nextIndex] = total - paneSize;
|
|
1911
|
-
return nextSizes;
|
|
1912
|
-
});
|
|
1913
|
-
}, [maxSizes, minSizes, updateSizes]);
|
|
1914
|
-
const reset = useCallback(() => {
|
|
1915
|
-
updateSizes(() => [...initialSizes]);
|
|
1916
|
-
}, [initialSizes, updateSizes]);
|
|
1917
|
-
const getPanelProps = useCallback((index, props = {}) => ({
|
|
1918
|
-
...props,
|
|
1919
|
-
'data-ui-resizable-panel': true,
|
|
1920
|
-
style: {
|
|
1921
|
-
...props.style,
|
|
1922
|
-
'--ui-resizable-size': `${sizes[index] ?? 0}%`
|
|
1923
|
-
}
|
|
1924
|
-
}), [sizes]);
|
|
1925
|
-
const getHandleProps = useCallback((index, props = {}) => ({
|
|
1926
|
-
...props,
|
|
1927
|
-
'aria-label': props['aria-label'] ?? `Resize panel ${index + 1}`,
|
|
1928
|
-
'aria-orientation': separatorOrientation,
|
|
1929
|
-
'aria-valuemax': Math.round(maxSizes[index] ?? 100),
|
|
1930
|
-
'aria-valuemin': Math.round(minSizes[index] ?? 0),
|
|
1931
|
-
'aria-valuenow': Math.round(sizes[index] ?? 0),
|
|
1932
|
-
className: composeClassName('ui-resizable__handle', props.className),
|
|
1933
|
-
'data-index': index,
|
|
1934
|
-
'data-ui-resizable-handle': true,
|
|
1935
|
-
onDoubleClick: composeHandlers(props.onDoubleClick, () => {
|
|
1936
|
-
if (resetOnDoubleClick) {
|
|
1937
|
-
reset();
|
|
1938
|
-
}
|
|
1939
|
-
}),
|
|
1940
|
-
onKeyDown: composeHandlers(props.onKeyDown, event => {
|
|
1941
|
-
const step = event.shiftKey ? 10 : 2;
|
|
1942
|
-
const keyDeltas = direction === 'horizontal' ?
|
|
1943
|
-
{ ArrowLeft: -step, ArrowRight: step } :
|
|
1944
|
-
{ ArrowDown: step, ArrowUp: -step };
|
|
1945
|
-
if (event.key === 'Home') {
|
|
1946
|
-
event.preventDefault();
|
|
1947
|
-
resizePair(index, minSizes[index] ?? 0);
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
if (event.key === 'End') {
|
|
1951
|
-
event.preventDefault();
|
|
1952
|
-
resizePair(index, maxSizes[index] ?? 100);
|
|
1953
|
-
return;
|
|
1954
|
-
}
|
|
1955
|
-
const delta = keyDeltas[event.key];
|
|
1956
|
-
if (delta === undefined)
|
|
1957
|
-
return;
|
|
1958
|
-
event.preventDefault();
|
|
1959
|
-
resizePair(index, (sizes[index] ?? 0) + delta);
|
|
1960
|
-
}),
|
|
1961
|
-
onPointerDown: composeHandlers(props.onPointerDown, event => {
|
|
1962
|
-
if (event.button !== 0)
|
|
1963
|
-
return;
|
|
1964
|
-
event.preventDefault();
|
|
1965
|
-
dragRef.current = {
|
|
1966
|
-
containerSize: Math.max(1, rootRef.current?.getBoundingClientRect()[sizeProperty] ?? 1),
|
|
1967
|
-
index,
|
|
1968
|
-
startPosition: event[axis],
|
|
1969
|
-
startSize: sizes[index] ?? 0
|
|
1970
|
-
};
|
|
1971
|
-
if (rootRef.current) {
|
|
1972
|
-
rootRef.current.dataset.resizing = 'true';
|
|
1973
|
-
}
|
|
1974
|
-
event.currentTarget.dataset.active = 'true';
|
|
1975
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1976
|
-
}),
|
|
1977
|
-
onPointerMove: composeHandlers(props.onPointerMove, event => {
|
|
1978
|
-
const drag = dragRef.current;
|
|
1979
|
-
if (!drag ||
|
|
1980
|
-
drag.index !== index ||
|
|
1981
|
-
event.currentTarget.dataset.active !== 'true')
|
|
1982
|
-
return;
|
|
1983
|
-
const delta = ((event[axis] - drag.startPosition) / drag.containerSize) * 100;
|
|
1984
|
-
resizePair(index, drag.startSize + delta);
|
|
1985
|
-
}),
|
|
1986
|
-
onPointerUp: composeHandlers(props.onPointerUp, event => {
|
|
1987
|
-
if (event.currentTarget.dataset.active !== 'true')
|
|
1988
|
-
return;
|
|
1989
|
-
delete event.currentTarget.dataset.active;
|
|
1990
|
-
if (rootRef.current) {
|
|
1991
|
-
delete rootRef.current.dataset.resizing;
|
|
1992
|
-
}
|
|
1993
|
-
dragRef.current = null;
|
|
1994
|
-
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
1995
|
-
}),
|
|
1996
|
-
role: 'separator',
|
|
1997
|
-
tabIndex: props.tabIndex ?? 0,
|
|
1998
|
-
type: props.type ?? 'button'
|
|
1999
|
-
}), [
|
|
2000
|
-
axis,
|
|
2001
|
-
direction,
|
|
2002
|
-
maxSizes,
|
|
2003
|
-
minSizes,
|
|
2004
|
-
reset,
|
|
2005
|
-
resetOnDoubleClick,
|
|
2006
|
-
resizePair,
|
|
2007
|
-
separatorOrientation,
|
|
2008
|
-
sizeProperty,
|
|
2009
|
-
sizes
|
|
2010
|
-
]);
|
|
2011
|
-
return {
|
|
2012
|
-
direction,
|
|
2013
|
-
getHandleProps,
|
|
2014
|
-
getPanelProps,
|
|
2015
|
-
handleIndexes,
|
|
2016
|
-
panelIndexes,
|
|
2017
|
-
reset,
|
|
2018
|
-
resizePair,
|
|
2019
|
-
rootProps: {
|
|
2020
|
-
className: composeClassName('ui-resizable', direction === 'vertical' && 'ui-resizable--vertical'),
|
|
2021
|
-
'data-orientation': direction,
|
|
2022
|
-
'data-ui-resizable': true,
|
|
2023
|
-
'data-ui-resizable-default-sizes': defaultSizes?.join(','),
|
|
2024
|
-
'data-ui-resizable-enhanced': true,
|
|
2025
|
-
'data-ui-resizable-max-size': serializeResizableSize(maxSize),
|
|
2026
|
-
'data-ui-resizable-min-size': serializeResizableSize(minSize),
|
|
2027
|
-
'data-ui-resizable-reset': resetOnDoubleClick ? 'true' : undefined,
|
|
2028
|
-
ref: rootRef
|
|
2029
|
-
},
|
|
2030
|
-
rootRef,
|
|
2031
|
-
sizes
|
|
2032
|
-
};
|
|
2033
|
-
};
|
|
2034
|
-
/* eslint-enable @stylistic/padding-line-between-statements, @eslint-react/set-state-in-effect, @typescript-eslint/prefer-optional-chain, complexity, no-nested-ternary */
|
|
2035
|
-
export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExportFormat = 'css', defaultHue = 264, defaultMode = 'generated', defaultPrimaryColor = '#6f20f0', defaultScheme = 'light', defaultSecondaryColor = '#14b8a6', exportFormat, hue, mode, onAccentHueChange, onExportFormatChange, onHueChange, onModeChange, onPrimaryColorChange, onSchemeChange, onSecondaryColorChange, onThemeChange, onThemeExport, primaryColor, scheme, secondaryColor } = {}) => {
|
|
2036
|
-
const [currentHue, setHue] = useControllableState({
|
|
2037
|
-
defaultValue: defaultHue,
|
|
2038
|
-
onChange: onHueChange,
|
|
2039
|
-
value: hue
|
|
2040
|
-
});
|
|
2041
|
-
const [currentAccentHue, setAccentHue] = useControllableState({
|
|
2042
|
-
defaultValue: defaultAccentHue,
|
|
2043
|
-
onChange: onAccentHueChange,
|
|
2044
|
-
value: accentHue
|
|
2045
|
-
});
|
|
2046
|
-
const [currentMode, setMode] = useControllableState({
|
|
2047
|
-
defaultValue: defaultMode,
|
|
2048
|
-
onChange: onModeChange,
|
|
2049
|
-
value: mode
|
|
2050
|
-
});
|
|
2051
|
-
const [currentScheme, setScheme] = useControllableState({
|
|
2052
|
-
defaultValue: defaultScheme,
|
|
2053
|
-
onChange: onSchemeChange,
|
|
2054
|
-
value: scheme
|
|
2055
|
-
});
|
|
2056
|
-
const [currentPrimaryColor, setPrimaryColor] = useControllableState({
|
|
2057
|
-
defaultValue: defaultPrimaryColor,
|
|
2058
|
-
onChange: onPrimaryColorChange,
|
|
2059
|
-
value: primaryColor
|
|
2060
|
-
});
|
|
2061
|
-
const [currentSecondaryColor, setSecondaryColor] = useControllableState({
|
|
2062
|
-
defaultValue: defaultSecondaryColor,
|
|
2063
|
-
onChange: onSecondaryColorChange,
|
|
2064
|
-
value: secondaryColor
|
|
2065
|
-
});
|
|
2066
|
-
const [currentExportFormat, setExportFormat] = useControllableState({
|
|
2067
|
-
defaultValue: defaultExportFormat,
|
|
2068
|
-
onChange: onExportFormatChange,
|
|
2069
|
-
value: exportFormat
|
|
2070
|
-
});
|
|
2071
|
-
const result = useMemo(() => createThemeBuilderTokens({
|
|
2072
|
-
accentHue: currentAccentHue,
|
|
2073
|
-
hue: currentHue,
|
|
2074
|
-
mode: currentMode,
|
|
2075
|
-
primaryColor: currentPrimaryColor,
|
|
2076
|
-
scheme: currentScheme,
|
|
2077
|
-
secondaryColor: currentSecondaryColor
|
|
2078
|
-
}), [
|
|
2079
|
-
currentAccentHue,
|
|
2080
|
-
currentHue,
|
|
2081
|
-
currentMode,
|
|
2082
|
-
currentPrimaryColor,
|
|
2083
|
-
currentScheme,
|
|
2084
|
-
currentSecondaryColor
|
|
2085
|
-
]);
|
|
2086
|
-
const exportValue = useMemo(() => exportThemeBuilderValue(result.tokens, result.scheme, currentExportFormat), [currentExportFormat, result.scheme, result.tokens]);
|
|
2087
|
-
const previewStyle = useMemo(() => Object.fromEntries(Object.entries(result.tokens).map(([token, value]) => [
|
|
2088
|
-
`--${token}`,
|
|
2089
|
-
value
|
|
2090
|
-
])), [result.tokens]);
|
|
2091
|
-
useEffect(() => {
|
|
2092
|
-
onThemeChange?.(result);
|
|
2093
|
-
}, [onThemeChange, result]);
|
|
2094
|
-
const exportDetail = useMemo(() => ({
|
|
2095
|
-
...(currentExportFormat === 'css' ? { css: exportValue } : {}),
|
|
2096
|
-
format: currentExportFormat,
|
|
2097
|
-
tokens: result.tokens,
|
|
2098
|
-
value: exportValue
|
|
2099
|
-
}), [currentExportFormat, exportValue, result.tokens]);
|
|
2100
|
-
const copyExport = useCallback(async () => {
|
|
2101
|
-
if (typeof navigator !== 'undefined') {
|
|
2102
|
-
await navigator.clipboard.writeText(exportValue).catch(() => null);
|
|
2103
|
-
}
|
|
2104
|
-
onThemeExport?.(exportDetail);
|
|
2105
|
-
return exportValue;
|
|
2106
|
-
}, [exportDetail, exportValue, onThemeExport]);
|
|
2107
|
-
const getModeProps = useCallback((nextMode, props = {}) => ({
|
|
2108
|
-
...props,
|
|
2109
|
-
'aria-pressed': currentMode === nextMode,
|
|
2110
|
-
'data-ui-theme-mode': nextMode,
|
|
2111
|
-
onClick: composeHandlers(props.onClick, () => {
|
|
2112
|
-
setMode(nextMode);
|
|
2113
|
-
}),
|
|
2114
|
-
type: props.type ?? 'button'
|
|
2115
|
-
}), [currentMode, setMode]);
|
|
2116
|
-
const getSchemeProps = useCallback((nextScheme, props = {}) => ({
|
|
2117
|
-
...props,
|
|
2118
|
-
'aria-pressed': currentScheme === nextScheme,
|
|
2119
|
-
'data-ui-theme-scheme': nextScheme,
|
|
2120
|
-
onClick: composeHandlers(props.onClick, () => {
|
|
2121
|
-
setScheme(nextScheme);
|
|
2122
|
-
}),
|
|
2123
|
-
type: props.type ?? 'button'
|
|
2124
|
-
}), [currentScheme, setScheme]);
|
|
2125
|
-
const getExportFormatProps = useCallback((format, props = {}) => ({
|
|
2126
|
-
...props,
|
|
2127
|
-
'aria-pressed': currentExportFormat === format,
|
|
2128
|
-
'data-ui-theme-export-format': format,
|
|
2129
|
-
onClick: composeHandlers(props.onClick, () => {
|
|
2130
|
-
setExportFormat(coerceThemeBuilderExportFormat(format));
|
|
2131
|
-
}),
|
|
2132
|
-
type: props.type ?? 'button'
|
|
2133
|
-
}), [currentExportFormat, setExportFormat]);
|
|
2134
|
-
return {
|
|
2135
|
-
...result,
|
|
2136
|
-
accentHueProps: {
|
|
2137
|
-
'data-ui-theme-accent-hue': true,
|
|
2138
|
-
max: 359,
|
|
2139
|
-
min: 0,
|
|
2140
|
-
onChange: event => {
|
|
2141
|
-
setAccentHue(Number(event.currentTarget.value));
|
|
2142
|
-
},
|
|
2143
|
-
type: 'range',
|
|
2144
|
-
value: currentAccentHue
|
|
2145
|
-
},
|
|
2146
|
-
copyExport,
|
|
2147
|
-
exportButtonProps: {
|
|
2148
|
-
'data-ui-theme-export': true,
|
|
2149
|
-
onClick: () => {
|
|
2150
|
-
copyExport().catch(() => null);
|
|
2151
|
-
},
|
|
2152
|
-
type: 'button'
|
|
2153
|
-
},
|
|
2154
|
-
exportFormat: currentExportFormat,
|
|
2155
|
-
exportValue,
|
|
2156
|
-
getExportFormatProps,
|
|
2157
|
-
getModeProps,
|
|
2158
|
-
getSchemeProps,
|
|
2159
|
-
hueProps: {
|
|
2160
|
-
'data-ui-theme-brand-hue': true,
|
|
2161
|
-
max: 359,
|
|
2162
|
-
min: 0,
|
|
2163
|
-
onChange: event => {
|
|
2164
|
-
setHue(Number(event.currentTarget.value));
|
|
2165
|
-
},
|
|
2166
|
-
type: 'range',
|
|
2167
|
-
value: currentHue
|
|
2168
|
-
},
|
|
2169
|
-
outputProps: {
|
|
2170
|
-
'data-ui-theme-output': true,
|
|
2171
|
-
readOnly: true,
|
|
2172
|
-
value: exportValue
|
|
2173
|
-
},
|
|
2174
|
-
previewStyle,
|
|
2175
|
-
primaryColorProps: {
|
|
2176
|
-
'data-ui-theme-primary-color': true,
|
|
2177
|
-
onChange: event => {
|
|
2178
|
-
setPrimaryColor(event.currentTarget.value);
|
|
2179
|
-
},
|
|
2180
|
-
type: 'color',
|
|
2181
|
-
value: currentPrimaryColor
|
|
2182
|
-
},
|
|
2183
|
-
rootProps: {
|
|
2184
|
-
'data-ui-theme-builder': true
|
|
2185
|
-
},
|
|
2186
|
-
secondaryColorProps: {
|
|
2187
|
-
'data-ui-theme-secondary-color': true,
|
|
2188
|
-
onChange: event => {
|
|
2189
|
-
setSecondaryColor(event.currentTarget.value);
|
|
2190
|
-
},
|
|
2191
|
-
type: 'color',
|
|
2192
|
-
value: currentSecondaryColor
|
|
2193
|
-
},
|
|
2194
|
-
setAccentHue,
|
|
2195
|
-
setExportFormat,
|
|
2196
|
-
setHue,
|
|
2197
|
-
setMode,
|
|
2198
|
-
setPrimaryColor,
|
|
2199
|
-
setScheme,
|
|
2200
|
-
setSecondaryColor
|
|
2201
|
-
};
|
|
2202
|
-
};
|
|
2203
|
-
export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange, open } = {}) => {
|
|
2204
|
-
const tooltipId = useSafeId('ui-tooltip', id);
|
|
2205
|
-
const timerRef = useRef(undefined);
|
|
2206
|
-
const [isOpen, setIsOpen] = useControllableState({
|
|
2207
|
-
defaultValue: defaultOpen,
|
|
2208
|
-
onChange: onOpenChange,
|
|
2209
|
-
value: open
|
|
2210
|
-
});
|
|
2211
|
-
const close = useCallback(() => {
|
|
2212
|
-
if (timerRef.current) {
|
|
2213
|
-
clearTimeout(timerRef.current);
|
|
2214
|
-
}
|
|
2215
|
-
setIsOpen(false);
|
|
2216
|
-
}, [setIsOpen]);
|
|
2217
|
-
const scheduleOpen = useCallback((nextDelay) => {
|
|
2218
|
-
if (timerRef.current) {
|
|
2219
|
-
clearTimeout(timerRef.current);
|
|
2220
|
-
}
|
|
2221
|
-
timerRef.current = setTimeout(() => {
|
|
2222
|
-
setIsOpen(true);
|
|
2223
|
-
}, nextDelay);
|
|
2224
|
-
}, [setIsOpen]);
|
|
2225
|
-
useEffect(() => () => {
|
|
2226
|
-
if (timerRef.current) {
|
|
2227
|
-
clearTimeout(timerRef.current);
|
|
2228
|
-
}
|
|
2229
|
-
}, []);
|
|
2230
|
-
return {
|
|
2231
|
-
close,
|
|
2232
|
-
open: isOpen,
|
|
2233
|
-
rootProps: {
|
|
2234
|
-
'aria-describedby': isOpen ? tooltipId : undefined,
|
|
2235
|
-
'data-ui-tooltip': true,
|
|
2236
|
-
onFocus: () => {
|
|
2237
|
-
scheduleOpen(0);
|
|
2238
|
-
},
|
|
2239
|
-
onKeyDown: event => {
|
|
2240
|
-
if (event.key === 'Escape') {
|
|
2241
|
-
close();
|
|
2242
|
-
}
|
|
2243
|
-
},
|
|
2244
|
-
onMouseEnter: () => {
|
|
2245
|
-
scheduleOpen(delay);
|
|
2246
|
-
},
|
|
2247
|
-
onMouseLeave: close
|
|
2248
|
-
},
|
|
2249
|
-
setOpen: setIsOpen,
|
|
2250
|
-
tooltipProps: {
|
|
2251
|
-
hidden: !isOpen,
|
|
2252
|
-
id: tooltipId,
|
|
2253
|
-
role: 'tooltip'
|
|
2254
|
-
}
|
|
2255
|
-
};
|
|
2256
|
-
};
|
|
2257
|
-
const createToastId = () => {
|
|
2258
|
-
if (typeof crypto !== 'undefined' &&
|
|
2259
|
-
typeof crypto.randomUUID === 'function') {
|
|
2260
|
-
return `ui-toast-${crypto.randomUUID()}`;
|
|
2261
|
-
}
|
|
2262
|
-
return `ui-toast-${Math.random().toString(36).slice(2)}`;
|
|
2263
|
-
};
|
|
2264
|
-
const getToastVariantClass = (variant) => {
|
|
2265
|
-
if (variant === 'success')
|
|
2266
|
-
return 'ui-toast--success';
|
|
2267
|
-
if (variant === 'warning')
|
|
2268
|
-
return 'ui-toast--warning';
|
|
2269
|
-
if (variant === 'destructive')
|
|
2270
|
-
return 'ui-toast--destructive';
|
|
2271
|
-
return false;
|
|
2272
|
-
};
|
|
2273
|
-
const createToastRecord = (detail, placement) => ({
|
|
2274
|
-
...detail,
|
|
2275
|
-
id: detail.id ?? createToastId(),
|
|
2276
|
-
open: true,
|
|
2277
|
-
placement: detail.placement ?? placement,
|
|
2278
|
-
title: detail.title ?? 'Notification',
|
|
2279
|
-
variant: detail.variant ?? 'default'
|
|
2280
|
-
});
|
|
2281
|
-
export const ToastProvider = ({ children, maxCount = defaultToastMax, placement = 'bottom-right' }) => {
|
|
2282
|
-
const [toasts, setToasts] = useState([]);
|
|
2283
|
-
const removeToast = useCallback((id) => {
|
|
2284
|
-
setToasts(current => current.filter(toast => toast.id !== id));
|
|
2285
|
-
}, []);
|
|
2286
|
-
const dismiss = useCallback((id) => {
|
|
2287
|
-
setToasts(current => current.map(toast => {
|
|
2288
|
-
if (id && toast.id !== id)
|
|
2289
|
-
return toast;
|
|
2290
|
-
return { ...toast, open: false };
|
|
2291
|
-
}));
|
|
2292
|
-
}, []);
|
|
2293
|
-
const create = useCallback((detail) => {
|
|
2294
|
-
const record = createToastRecord(detail, placement);
|
|
2295
|
-
const stackMax = detail.max ?? maxCount;
|
|
2296
|
-
setToasts(current => {
|
|
2297
|
-
const next = current.some(toast => toast.id === record.id) ?
|
|
2298
|
-
current.map(toast => (toast.id === record.id ? record : toast)) :
|
|
2299
|
-
[...current, record];
|
|
2300
|
-
const samePlacement = next.filter(toast => toast.placement === record.placement);
|
|
2301
|
-
const staleIds = samePlacement
|
|
2302
|
-
.slice(0, Math.max(0, samePlacement.length - stackMax))
|
|
2303
|
-
.map(toast => toast.id);
|
|
2304
|
-
return next.map(toast => staleIds.includes(toast.id) ? { ...toast, open: false } : toast);
|
|
2305
|
-
});
|
|
2306
|
-
return record.id;
|
|
2307
|
-
}, [maxCount, placement]);
|
|
2308
|
-
const update = useCallback((id, detail) => {
|
|
2309
|
-
setToasts(current => current.map(toast => {
|
|
2310
|
-
if (toast.id !== id)
|
|
2311
|
-
return toast;
|
|
2312
|
-
return {
|
|
2313
|
-
...toast,
|
|
2314
|
-
...detail,
|
|
2315
|
-
id,
|
|
2316
|
-
open: true,
|
|
2317
|
-
placement: detail.placement ?? toast.placement,
|
|
2318
|
-
title: detail.title ?? toast.title,
|
|
2319
|
-
variant: detail.variant ?? toast.variant
|
|
2320
|
-
};
|
|
2321
|
-
}));
|
|
2322
|
-
}, []);
|
|
2323
|
-
const api = useMemo(() => ({
|
|
2324
|
-
create,
|
|
2325
|
-
dismiss,
|
|
2326
|
-
toasts,
|
|
2327
|
-
update
|
|
2328
|
-
}), [create, dismiss, toasts, update]);
|
|
2329
|
-
const placements = [
|
|
2330
|
-
...new Set([placement, ...toasts.map(toast => toast.placement)])
|
|
2331
|
-
];
|
|
2332
|
-
return (_jsxs(ToastContext.Provider, { value: api, children: [children, placements.map(item => (_jsx(ToastViewport, { maxCount: maxCount, placement: item, removeToast: removeToast, toasts: toasts.filter(toast => toast.placement === item) }, item)))] }));
|
|
2333
|
-
};
|
|
2334
|
-
export const useToast = () => {
|
|
2335
|
-
const api = useContext(ToastContext);
|
|
2336
|
-
if (!api) {
|
|
2337
|
-
throw new Error('useToast must be used inside a ToastProvider.');
|
|
2338
|
-
}
|
|
2339
|
-
return api;
|
|
2340
|
-
};
|
|
2341
|
-
const ToastViewport = ({ maxCount, placement, removeToast, toasts }) => (_jsx("div", { "aria-atomic": "false", "aria-label": "Notifications", "aria-live": "polite", className: "ui-tvp", "data-placement": placement, "data-ui-toast-viewport": true, "data-ui-toast-max": maxCount, children: toasts.map(toast => (_jsx(ToastItem, { onDismiss: () => {
|
|
2342
|
-
removeToast(toast.id);
|
|
2343
|
-
}, toast: toast }, toast.id))) }));
|
|
2344
|
-
const ToastItem = ({ onDismiss, toast }) => {
|
|
2345
|
-
const toastRef = useRef(null);
|
|
2346
|
-
const timerRef = useRef(undefined);
|
|
2347
|
-
const remainingRef = useRef(toast.duration ?? defaultToastDuration);
|
|
2348
|
-
const startedAtRef = useRef(0);
|
|
2349
|
-
const dismiss = useToast().dismiss;
|
|
2350
|
-
const clearTimer = useCallback(() => {
|
|
2351
|
-
if (timerRef.current) {
|
|
2352
|
-
clearTimeout(timerRef.current);
|
|
2353
|
-
}
|
|
2354
|
-
}, []);
|
|
2355
|
-
const startTimer = useCallback(() => {
|
|
2356
|
-
const remaining = remainingRef.current;
|
|
2357
|
-
clearTimer();
|
|
2358
|
-
if (!Number.isFinite(remaining) || remaining <= 0)
|
|
2359
|
-
return;
|
|
2360
|
-
startedAtRef.current = Date.now();
|
|
2361
|
-
timerRef.current = setTimeout(() => {
|
|
2362
|
-
dismiss(toast.id);
|
|
2363
|
-
}, remaining);
|
|
2364
|
-
}, [clearTimer, dismiss, toast.id]);
|
|
2365
|
-
const pauseTimer = useCallback(() => {
|
|
2366
|
-
clearTimer();
|
|
2367
|
-
remainingRef.current = Math.max(0, remainingRef.current - (Date.now() - startedAtRef.current));
|
|
2368
|
-
}, [clearTimer]);
|
|
2369
|
-
const resumeTimer = useCallback(() => {
|
|
2370
|
-
if (remainingRef.current > 0) {
|
|
2371
|
-
startTimer();
|
|
2372
|
-
}
|
|
2373
|
-
}, [startTimer]);
|
|
2374
|
-
useEffect(() => {
|
|
2375
|
-
remainingRef.current = toast.duration ?? defaultToastDuration;
|
|
2376
|
-
startTimer();
|
|
2377
|
-
return clearTimer;
|
|
2378
|
-
}, [clearTimer, startTimer, toast.duration]);
|
|
2379
|
-
useEffect(() => {
|
|
2380
|
-
if (!toast.open) {
|
|
2381
|
-
const timer = setTimeout(onDismiss, closeDelay);
|
|
2382
|
-
return () => {
|
|
2383
|
-
clearTimeout(timer);
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
}, [onDismiss, toast.open]);
|
|
2387
|
-
const action = toast.action;
|
|
2388
|
-
const description = toast.description ?? '';
|
|
2389
|
-
const variantClass = getToastVariantClass(toast.variant);
|
|
2390
|
-
return (_jsxs("aside", { "aria-live": toast.variant === 'destructive' ? 'assertive' : 'polite', className: composeClassName('ui-toast', variantClass), "data-description": description, "data-state": toast.open ? 'open' : 'closed', "data-title": toast.title, "data-ui-toast": true, "data-variant": toast.variant, id: toast.id, onFocus: pauseTimer, onKeyDown: event => {
|
|
2391
|
-
if (event.key !== 'Escape')
|
|
2392
|
-
return;
|
|
2393
|
-
event.preventDefault();
|
|
2394
|
-
dismiss(toast.id);
|
|
2395
|
-
}, onMouseEnter: pauseTimer, onMouseLeave: resumeTimer, ref: toastRef, role: toast.variant === 'destructive' ? 'alert' : 'status', children: [_jsxs("div", { className: "ui-toast__body", children: [_jsx("strong", { children: toast.title }), description && _jsx("p", { children: description })] }), action?.label && (_jsx("button", { className: "\n ui-button ui-button--secondary ui-button--sm ui-toast__action\n ", onClick: event => {
|
|
2396
|
-
action.onClick?.(event, toastRef.current);
|
|
2397
|
-
toastRef.current?.dispatchEvent(new CustomEvent(action.event ?? 'ui:toast-action', {
|
|
2398
|
-
bubbles: true,
|
|
2399
|
-
detail: { id: toast.id, value: action.value }
|
|
2400
|
-
}));
|
|
2401
|
-
dismiss(toast.id);
|
|
2402
|
-
}, type: "button", children: action.label })), _jsx("button", { "aria-label": "Dismiss notification", className: "ui-toast__dismiss", onClick: () => {
|
|
2403
|
-
dismiss(toast.id);
|
|
2404
|
-
}, type: "button", children: "Dismiss" })] }));
|
|
2405
|
-
};
|
|
2406
|
-
export const useThemeToggle = (defaultTheme = 'light') => {
|
|
2407
|
-
const [theme, setTheme] = useState(() => {
|
|
2408
|
-
if (typeof window !== 'undefined') {
|
|
2409
|
-
return (localStorage.getItem('theme') ??
|
|
2410
|
-
(document.documentElement.classList.contains('dark') ? 'dark' : 'light'));
|
|
2411
|
-
}
|
|
2412
|
-
return defaultTheme;
|
|
2413
|
-
});
|
|
2414
|
-
useEffect(() => {
|
|
2415
|
-
const doc = document.documentElement;
|
|
2416
|
-
doc.setAttribute('data-theme', theme);
|
|
2417
|
-
if (theme === 'dark') {
|
|
2418
|
-
doc.classList.add('dark');
|
|
2419
|
-
}
|
|
2420
|
-
else {
|
|
2421
|
-
doc.classList.remove('dark');
|
|
2422
|
-
}
|
|
2423
|
-
localStorage.setItem('theme', theme);
|
|
2424
|
-
window.dispatchEvent(new CustomEvent('theme-change', { detail: theme }));
|
|
2425
|
-
}, [theme]);
|
|
2426
|
-
const toggleTheme = (event) => {
|
|
2427
|
-
const isDark = theme === 'dark';
|
|
2428
|
-
const newTheme = isDark ? 'light' : 'dark';
|
|
2429
|
-
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
2430
|
-
const isTouchDevice = window.matchMedia('(hover: none) and (pointer: coarse)').matches;
|
|
2431
|
-
if (prefersReducedMotion || isTouchDevice || !event) {
|
|
2432
|
-
setTheme(newTheme);
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
2436
|
-
const x = Math.round(rect.left + rect.width / 2);
|
|
2437
|
-
const y = Math.round(rect.top + rect.height / 2);
|
|
2438
|
-
const maxRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y));
|
|
2439
|
-
const oldBg = isDark ? 'hsl(277 20% 10%)' : 'hsl(268 20% 98%)';
|
|
2440
|
-
const overlay = document.createElement('div');
|
|
2441
|
-
overlay.setAttribute('aria-hidden', 'true');
|
|
2442
|
-
Object.assign(overlay.style, {
|
|
2443
|
-
position: 'fixed',
|
|
2444
|
-
inset: '0',
|
|
2445
|
-
zIndex: '99999',
|
|
2446
|
-
pointerEvents: 'none',
|
|
2447
|
-
backgroundColor: oldBg,
|
|
2448
|
-
clipPath: `circle(${maxRadius}px at ${x}px ${y}px)`,
|
|
2449
|
-
willChange: 'clip-path'
|
|
2450
|
-
});
|
|
2451
|
-
document.body.appendChild(overlay);
|
|
2452
|
-
// Switch theme behind the overlay
|
|
2453
|
-
setTheme(newTheme);
|
|
2454
|
-
requestAnimationFrame(() => {
|
|
2455
|
-
requestAnimationFrame(() => {
|
|
2456
|
-
overlay.style.transition =
|
|
2457
|
-
'clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)';
|
|
2458
|
-
overlay.style.clipPath = `circle(0px at ${x}px ${y}px)`;
|
|
2459
|
-
const done = () => {
|
|
2460
|
-
overlay.remove();
|
|
2461
|
-
};
|
|
2462
|
-
overlay.addEventListener('transitionend', done, { once: true });
|
|
2463
|
-
setTimeout(done, 750);
|
|
2464
|
-
});
|
|
2465
|
-
});
|
|
2466
|
-
};
|
|
2467
|
-
return { theme, toggleTheme, setTheme };
|
|
2468
|
-
};
|
|
1
|
+
"use client";import{useCallback,useEffect,useId,useMemo,useRef,useState}from"react";import{coerceThemeBuilderExportFormat,composeClassName,createLumenKanbanMoveDetail,createThemeBuilderTokens,exportThemeBuilderValue,getLumenDirectionalKey,getLumenLocalePair,normalizeLumenLocales,scrollLumenTabIntoView}from"@santi020k/lumen-core";import{isLumenDateBoundsValid as isCalendarBoundsValid,parseLumenDate as parseCalendarDate,resolveLumenDateLabels as resolveDateControlLabels,resolveLumenDateLocale as getCalendarLocale}from"@santi020k/lumen-core";import{useDialogLifecycle}from"./dialog-lifecycle.js";import{useFloatingPanel}from"./floating-panel.js";import{isLumenFormControl}from"./form-workflow.js";import{useSelectFormReset}from"./select-form.js";import{useToast}from"./toast-context.js";const setRefValue=(ref,value)=>{typeof ref=="function"?ref(value):ref&&(ref.current=value)};import{useRichTextEditor}from"./rich-text-editor.js";const focusableSelector=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(","),composeHandlers=(userHandler,lumenHandler)=>event=>{userHandler?.(event),lumenHandler(event)},isElementVisible=element=>{if(typeof element.checkVisibility=="function")return element.checkVisibility({visibilityProperty:!0});const visibility=getComputedStyle(element).visibility;return visibility==="hidden"||visibility==="collapse"?!1:element.offsetParent!==null||element.getClientRects().length>0},getFocusable=root=>root?[...root.querySelectorAll(focusableSelector)].filter(element=>!element.matches(":disabled")&&!element.closest("[hidden], [inert]")&&isElementVisible(element)):[],getLoopedIndex=(key,currentIndex,itemCount,forwardKeys)=>{const lastIndex=itemCount-1;return key==="Home"?0:key==="End"?lastIndex:forwardKeys.includes(key)?(currentIndex+1)%itemCount:(currentIndex-1+itemCount)%itemCount},useSafeId=(prefix,id)=>{const reactId=useId().replaceAll(":","");return id??`${prefix}-${reactId}`},useControllableState=({defaultValue,onChange,value})=>{const[uncontrolledValue,setUncontrolledValue]=useState(defaultValue),pendingValueRef=useRef(defaultValue),currentValue=value??uncontrolledValue,setValue=useCallback(next=>{const previousValue=value===void 0?pendingValueRef.current:currentValue,resolvedValue=typeof next=="function"?next(previousValue):next;value===void 0&&(pendingValueRef.current=resolvedValue,setUncontrolledValue(resolvedValue)),onChange?.(resolvedValue)},[currentValue,onChange,value]),restoreValue=useCallback(next=>{value===void 0&&(pendingValueRef.current=next,setUncontrolledValue(next))},[value]);return[currentValue,setValue,restoreValue]},focusFirstIn=root=>{globalThis.setTimeout(()=>{getFocusable(root)[0]?.focus()})},focusTrigger=trigger=>{trigger?.focus({preventScroll:!0})},clampViewportPosition=(value,size,viewportSize)=>Math.max(8,Math.min(value,viewportSize-size-8)),getContextMenuPosition=(x,y,rect)=>{const width=rect?.width??0,height=rect?.height??0,viewportWidth=typeof window>"u"?x+width+16:window.innerWidth,viewportHeight=typeof window>"u"?y+height+16:window.innerHeight;return{left:clampViewportPosition(x,width,viewportWidth),top:clampViewportPosition(y,height,viewportHeight)}},isDisclosureEditableTarget=(target,panel)=>{const ElementType=panel.ownerDocument.defaultView?.Element;if(!ElementType||!(target instanceof ElementType))return!1;if(target.closest("input, textarea"))return!0;const editable=target.closest("[contenteditable]")?.getAttribute("contenteditable")?.toLowerCase();return editable===""||editable==="true"||editable==="plaintext-only"},useOutsideClose=(open,refs,onClose)=>{useEffect(()=>{const owner=refs.find(ref=>ref.current)?.current?.ownerDocument,NodeType=owner?.defaultView?.Node;if(!open||!owner||!NodeType)return;const pointerDown=event=>{const target=event.target;target instanceof NodeType&&!refs.some(ref=>ref.current?.contains(target))&&onClose()};return owner.addEventListener("pointerdown",pointerDown),()=>{owner.removeEventListener("pointerdown",pointerDown)}},[onClose,open,refs])},useDisclosureController=(options,hasPopup)=>{const rootRef=useRef(null),triggerRef=useRef(null),panelRef=useRef(null),panelId=useSafeId(`ui-${hasPopup}`,options.id),[open,setOpen]=useControllableState({defaultValue:options.defaultOpen??!1,onChange:options.onOpenChange,value:options.open}),close=useCallback(()=>{setOpen(!1)},[setOpen]),toggle=useCallback(()=>{setOpen(current=>!current)},[setOpen]);return useOutsideClose(open,[rootRef,triggerRef,panelRef],close),useFloatingPanel(open,triggerRef,panelRef,close,options),{close,open,panelProps:{"data-state":open?"open":"closed",hidden:!open,id:panelId,onKeyDown:event=>{if(event.defaultPrevented||event.nativeEvent.isComposing)return;if(event.key==="Escape"){event.preventDefault(),close(),focusTrigger(triggerRef.current);return}if(isDisclosureEditableTarget(event.target,event.currentTarget)||!["ArrowDown","ArrowUp","Home","End"].includes(event.key))return;const items=getFocusable(panelRef.current);if(!items.length)return;event.preventDefault();const currentIndex=items.indexOf(document.activeElement);items[getLoopedIndex(getLumenDirectionalKey(event.currentTarget,event.key),Math.max(0,currentIndex),items.length,["ArrowDown"])]?.focus()},ref:panelRef},panelRef,rootProps:{ref:rootRef},rootRef,setOpen,toggle,triggerProps:{"aria-controls":panelId,"aria-expanded":open,"aria-haspopup":hasPopup,"data-ui-trigger":!0,onClick:event=>{event.defaultPrevented||(focusTrigger(triggerRef.current),toggle())},onKeyDown:event=>{if(!(event.defaultPrevented||event.nativeEvent.isComposing)){if(event.key==="Escape"&&open){event.preventDefault(),close();return}event.key!=="ArrowDown"&&event.key!=="Enter"&&event.key!==" "||(event.preventDefault(),setOpen(!0),focusFirstIn(panelRef.current))}},ref:triggerRef,type:"button"},triggerRef}},isDialogBackdropPoint=(dialog,event)=>{if(!dialog||event.target!==dialog)return!1;const bounds=dialog.getBoundingClientRect();return event.clientX<bounds.left||event.clientX>=bounds.right||event.clientY<bounds.top||event.clientY>=bounds.bottom},useDialog=(options={})=>{const dialogRef=useRef(null),triggerRef=useRef(null),outsidePointerRef=useRef(void 0),[open,setOpen]=useControllableState({defaultValue:options.defaultOpen??!1,onChange:options.onOpenChange,value:options.open}),consumeProgrammaticClose=useDialogLifecycle(open,dialogRef,triggerRef),close=useCallback(()=>{setOpen(!1)},[setOpen]),dialogProps={"aria-modal":!0,"data-ui-alert-dialog":options.alert?!0:void 0,"data-ui-bound":!0,"data-ui-dialog":options.alert?void 0:!0,onCancel:event=>{event.target!==dialogRef.current||event.defaultPrevented||(event.preventDefault(),event.stopPropagation(),options.dismissOnEscape!==!1&&close())},onClick:event=>{const startedOutside=outsidePointerRef.current;outsidePointerRef.current=void 0,!(event.defaultPrevented||startedOutside===!1||event.detail===0)&&(options.dismissOnOutsidePress??!options.alert)&&isDialogBackdropPoint(dialogRef.current,event)&&close()},onClose:event=>{event.target!==dialogRef.current||consumeProgrammaticClose()||close()},onPointerDown:event=>{outsidePointerRef.current=isDialogBackdropPoint(dialogRef.current,event)},ref:dialogRef,role:options.alert?"alertdialog":"dialog"};return{close,closeProps:{onClick:close,type:"button"},dialogProps,dialogRef,open,setOpen,triggerProps:{"aria-haspopup":"dialog",onClick:()=>{setOpen(!0)},ref:element=>{triggerRef.current=element},type:"button"},triggerRef}},usePopover=(options={})=>useDisclosureController(options,"menu"),useDropdownMenu=(options={})=>useDisclosureController(options,"menu"),readLanguageStorage=storageKey=>{if(!storageKey)return null;try{return localStorage.getItem(storageKey)}catch{return null}},useLanguageToggle=({defaultValue,locales:localeOptions,onValueChange,storageKey,value:controlledValue}={})=>{const locales=useMemo(()=>normalizeLumenLocales(localeOptions),[localeOptions]),defaultPair=getLumenLocalePair(locales,defaultValue),[internalValue,setInternalValue]=useState(defaultPair.current.value),value=controlledValue??internalValue,pair=getLumenLocalePair(locales,value),initializedRef=useRef(!1);return useEffect(()=>{if(controlledValue===void 0){if(!initializedRef.current){initializedRef.current=!0;const preferredValue=readLanguageStorage(storageKey)??defaultValue??document.documentElement.lang;if(preferredValue&&preferredValue!==value&&locales.some(locale=>locale.value===preferredValue)){setInternalValue(preferredValue);return}}if(document.documentElement.lang=value,storageKey)try{localStorage.setItem(storageKey,value)}catch{}}},[controlledValue,defaultValue,locales,storageKey,value]),{currentLocale:pair.current,nextLocale:pair.next,selectNext:()=>{controlledValue===void 0&&setInternalValue(pair.next.value),onValueChange?.(pair.next.value)},value:pair.current.value}},useContextMenu=({defaultOpen=!1,id,onOpenChange,open:controlledOpen}={})=>{const menuRef=useRef(null),triggerRef=useRef(null),menuId=useSafeId("ui-context-menu",id),[position,setPosition]=useState({left:0,top:0}),[open,setOpen]=useControllableState({defaultValue:defaultOpen,onChange:onOpenChange,value:controlledOpen}),close=useCallback(()=>{setOpen(!1)},[setOpen]);useOutsideClose(open,[triggerRef,menuRef],close);const openAt=useCallback((x,y)=>{const rect=menuRef.current?.getBoundingClientRect();setPosition(getContextMenuPosition(x,y,rect)),setOpen(!0),focusFirstIn(menuRef.current)},[setOpen]),triggerProps={"aria-controls":menuId,"aria-haspopup":"menu","data-ui-context-menu-trigger":menuId,onContextMenu:event=>{event.preventDefault(),openAt(event.clientX,event.clientY)},onKeyDown:event=>{if(event.key!=="ContextMenu"&&!(event.shiftKey&&event.key==="F10"))return;event.preventDefault();const rect=event.currentTarget.getBoundingClientRect();openAt(rect.left+16,rect.top+16)},ref:triggerRef,type:"button"},menuProps={"data-state":open?"open":"closed","data-ui-context-menu":!0,hidden:!open,id:menuId,onClick:event=>{const ElementType=event.currentTarget.ownerDocument.defaultView?.Element;if(!ElementType)return;let item;try{item=ElementType.prototype.closest.call(event.target,'[role="menuitem"]')}catch{return}item&&close()},onKeyDown:event=>{if(event.defaultPrevented||event.nativeEvent.isComposing)return;if(event.key==="Escape"){event.preventDefault(),close(),focusTrigger(triggerRef.current);return}if(!["ArrowDown","ArrowUp","End","Home"].includes(event.key))return;const items=getFocusable(menuRef.current);if(items.length===0)return;event.preventDefault();const activeElement=event.currentTarget.ownerDocument.activeElement,currentIndex=items.findIndex(item=>item===activeElement);items[getLoopedIndex(getLumenDirectionalKey(event.currentTarget,event.key),currentIndex,items.length,["ArrowDown"])]?.focus()},ref:menuRef,role:"menu",style:{left:position.left,position:"fixed",top:position.top,zIndex:60}};return{close,menuProps,menuRef,open,openAt,setOpen,triggerProps,triggerRef}},useTabs=({defaultValue="",id,onValueChange,orientation="horizontal",value}={})=>{const rootRef=useRef(null),tabsId=useSafeId("ui-tabs",id),[selectedValue,setSelectedValue]=useControllableState({defaultValue,onChange:onValueChange,value}),activate=useCallback(nextValue=>{setSelectedValue(nextValue)},[setSelectedValue]),getTriggerProps=useCallback((triggerValue,props={})=>{const selected=selectedValue===triggerValue,triggerId=`${tabsId}-tab-${triggerValue}`,panelId=`${tabsId}-panel-${triggerValue}`;return{...props,"aria-controls":panelId,"aria-selected":selected,id:props.id??triggerId,onClick:composeHandlers(props.onClick,event=>{activate(triggerValue),scrollLumenTabIntoView(event.currentTarget)}),onKeyDown:composeHandlers(props.onKeyDown,event=>{if(!(orientation==="vertical"?["ArrowUp","ArrowDown","Home","End"]:["ArrowLeft","ArrowRight","Home","End"]).includes(event.key))return;const tabs=rootRef.current?[...rootRef.current.querySelectorAll('[role="tab"]')].filter(tab=>tab.closest("[data-ui-tabs]")===rootRef.current&&!tab.matches(':disabled, [aria-disabled="true"]')):[];if(!tabs.length)return;event.preventDefault();const currentIndex=tabs.indexOf(event.currentTarget),nextTab=tabs[getLoopedIndex(event.key,Math.max(0,currentIndex),tabs.length,orientation==="vertical"?["ArrowDown"]:[getLumenDirectionalKey(event.currentTarget,"ArrowRight")])],nextValue=nextTab?.dataset.value;!nextTab||!nextValue||(activate(nextValue),scrollLumenTabIntoView(nextTab),nextTab.focus())}),role:"tab",tabIndex:selected?0:-1,type:props.type??"button","data-value":triggerValue}},[activate,orientation,selectedValue,tabsId]);return{getPanelProps:useCallback((panelValue,props={})=>{const selected=selectedValue===panelValue;return{...props,"aria-labelledby":`${tabsId}-tab-${panelValue}`,hidden:!selected,id:props.id??`${tabsId}-panel-${panelValue}`,role:"tabpanel",tabIndex:props.tabIndex??0}},[selectedValue,tabsId]),getTriggerProps,listProps:{"aria-orientation":orientation,role:"tablist"},rootProps:{"data-ui-tabs":!0,ref:rootRef},rootRef,setValue:setSelectedValue,value:selectedValue}},normalizeOption=option=>typeof option=="string"?{label:option,value:option}:option,isPrintableKey=event=>event.key.length===1&&!event.altKey&&!event.ctrlKey&&!event.metaKey,useSelect=({defaultValue="",disabled=!1,id,name,onValueChange,options=[],placeholder,required=!1,value}={})=>{const rootRef=useRef(null),triggerRef=useRef(null),listRef=useRef(null),typeaheadRef=useRef(""),typeaheadTimerRef=useRef(void 0),selectId=useSafeId("ui-select",id),listId=`${selectId}-listbox`,normalizedOptions=useMemo(()=>options.map(normalizeOption),[options]),[open,setOpen]=useState(!1),[selectedValue,setSelectedValue,restoreSelectedValue]=useControllableState({defaultValue,onChange:onValueChange,value}),selectedOption=normalizedOptions.find(option=>option.value===selectedValue),triggerText=selectedOption?.label??placeholder??"Select an option",hasSelection=!!(selectedOption&&selectedValue!==""),close=useCallback(()=>{setOpen(!1)},[setOpen]),openList=useCallback(()=>{setOpen(!0)},[setOpen]);useSelectFormReset(rootRef,defaultValue,value,restoreSelectedValue,close),useOutsideClose(open,[rootRef,triggerRef,listRef],close),useEffect(()=>()=>{typeaheadTimerRef.current&&clearTimeout(typeaheadTimerRef.current)},[]);const getEnabledItems=useCallback(()=>listRef.current?[...listRef.current.querySelectorAll("[data-ui-select-option]")].filter(item=>!item.hasAttribute("disabled")&&item.getAttribute("aria-disabled")!=="true"):[],[]),focusOption=useCallback((key,currentItem)=>{const items=getEnabledItems();if(!items.length)return;const selectedItem=items.find(item=>item.getAttribute("aria-selected")==="true"),current=currentItem??selectedItem??items[0];if(!current)return;const currentIndex=items.indexOf(current);items[getLoopedIndex(key,Math.max(0,currentIndex),items.length,["ArrowDown"])]?.focus()},[getEnabledItems]),focusTypeaheadOption=useCallback(currentItem=>{const items=getEnabledItems(),typeahead=typeaheadRef.current;if(!items.length||!typeahead)return;const selectedItem=items.find(item=>item.getAttribute("aria-selected")==="true"),current=currentItem??selectedItem??items[0];if(!current)return;const currentIndex=items.indexOf(current),startIndex=Math.max(0,currentIndex+1);[...items.slice(startIndex),...items.slice(0,startIndex)].find(item=>item.textContent.trim().toLowerCase().startsWith(typeahead))?.focus()},[getEnabledItems]),handleTypeahead=useCallback((event,currentItem)=>isPrintableKey(event)?(event.preventDefault(),typeaheadRef.current+=event.key.toLowerCase(),typeaheadTimerRef.current&&clearTimeout(typeaheadTimerRef.current),typeaheadTimerRef.current=setTimeout(()=>{typeaheadRef.current=""},700),openList(),focusTypeaheadOption(currentItem),!0):!1,[focusTypeaheadOption,openList]),selectOption=useCallback(nextValue=>{const option=normalizedOptions.find(item=>item.value===nextValue);if(!option||option.disabled)return;const native=rootRef.current?.querySelector("[data-ui-select-native]");native?(native.value=nextValue,native.dispatchEvent(new Event("change",{bubbles:!0}))):setSelectedValue(nextValue),close(),focusTrigger(triggerRef.current)},[close,normalizedOptions,setSelectedValue]),rootProps={"data-placeholder":hasSelection?"false":"true","data-ui-select":!0,ref:rootRef},controlProps={"data-ui-select-control":!0},nativeSelectProps={"aria-hidden":!0,"data-ui-enhanced":!0,"data-ui-select-native":!0,disabled,id:selectId,name,onChange:event=>{setSelectedValue(event.currentTarget.value)},required,tabIndex:-1,value:selectedValue},triggerProps={"aria-controls":listId,"aria-expanded":open,"aria-haspopup":"listbox","aria-required":required||void 0,"data-ui-select-trigger":!0,disabled,id:`${selectId}-trigger`,onClick:()=>{setOpen(current=>!current)},onKeyDown:event=>{if(event.defaultPrevented||event.nativeEvent.isComposing||handleTypeahead(event))return;if(event.key==="Escape"&&open){event.preventDefault(),close();return}if(["ArrowDown","ArrowUp","Home","End","Enter"," "].includes(event.key)){if(event.preventDefault(),openList(),event.key==="Enter"||event.key===" "){const selectedItem=getEnabledItems().find(item=>item.getAttribute("aria-selected")==="true"),firstItem=getEnabledItems()[0];(selectedItem??firstItem)?.focus();return}focusOption(event.key)}},ref:triggerRef,role:"combobox",type:"button"},listProps={"data-state":open?"open":"closed","data-ui-select-list":!0,hidden:!open,id:listId,ref:listRef,role:"listbox"},getOptionProps=useCallback((option,props={})=>({...props,"aria-disabled":option.disabled?"true":void 0,"aria-selected":selectedValue===option.value,"data-ui-select-option":!0,"data-value":option.value,disabled:option.disabled,onClick:composeHandlers(props.onClick,()=>{selectOption(option.value)}),onKeyDown:composeHandlers(props.onKeyDown,event=>{if(event.defaultPrevented||event.nativeEvent.isComposing||handleTypeahead(event,event.currentTarget))return;if(event.key==="Escape"&&open){event.preventDefault(),close(),focusTrigger(triggerRef.current);return}if(event.key==="Enter"||event.key===" "){event.preventDefault(),selectOption(option.value);return}["ArrowDown","ArrowUp","Home","End"].includes(event.key)&&(event.preventDefault(),focusOption(event.key,event.currentTarget))}),role:"option",tabIndex:-1,type:props.type??"button"}),[close,focusOption,handleTypeahead,open,selectOption,selectedValue]);return{close,controlProps,getOptionProps,listProps,nativeSelectProps,open,options:normalizedOptions,rootProps,rootRef,selectOption,selectedOption,setOpen,triggerProps,triggerText,value:selectedValue}},fieldControlSelector=['input:not([type="hidden"])',"select","textarea",'[contenteditable="true"]','[role="combobox"]','[role="listbox"]','[role="spinbutton"]','[role="textbox"]'].join(","),fieldDescriptionSelector="[data-ui-field-hint], [data-ui-field-error], .ui-field__hint, .ui-field__error",fieldErrorSelector="[data-ui-field-error], .ui-field__error",formControlSelector=['input:not([type="hidden"])',"select","textarea"].join(","),validityMessageAttributes=[["valueMissing","data-error-required"],["typeMismatch","data-error-type"],["patternMismatch","data-error-pattern"],["tooShort","data-error-too-short"],["tooLong","data-error-too-long"],["rangeUnderflow","data-error-min"],["rangeOverflow","data-error-max"],["stepMismatch","data-error-step"],["badInput","data-error-bad-input"],["customError","data-error-custom"]],isNativeFormControl=element=>element!==null&&isLumenFormControl(element),getFieldRoot=control=>control.closest(".ui-field, [data-ui-field]"),getValidationMessage=(control,field)=>{for(const[validityKey,attributeName]of validityMessageAttributes)if(control.validity[validityKey])return control.getAttribute(attributeName)??field?.getAttribute(attributeName)??control.validationMessage;return control.validationMessage},getFieldDescriptionId=element=>(element.id||(element.id=`ui-field-description-${Math.random().toString(36).slice(2)}`),element.id),syncFieldDescription=field=>{const controlId=field.dataset.uiFieldControl,control=controlId?document.getElementById(controlId):field.querySelector(fieldControlSelector);if(!(control instanceof HTMLElement))return;const existingIds=control.getAttribute("aria-describedby")?.trim().split(/\s+/)??[],configuredIds=field.dataset.uiFieldDescribedby?.trim().split(/\s+/)??[],descriptionIds=[...field.querySelectorAll(fieldDescriptionSelector)].map(element=>getFieldDescriptionId(element)),describedBy=[...new Set([...existingIds,...configuredIds,...descriptionIds].filter(Boolean))];describedBy.length&&control.setAttribute("aria-describedby",describedBy.join(" "))},useFormValidation=({onInvalid,onValid,onValidate}={})=>{const formRef=useRef(null),setFieldValidity=useCallback((control,invalid,message="")=>{const field=getFieldRoot(control);if(invalid?(control.setAttribute("aria-invalid","true"),control.dataset.uiValidationInvalid="true"):control.dataset.uiValidationInvalid==="true"&&(control.removeAttribute("aria-invalid"),delete control.dataset.uiValidationInvalid),!!field){field.dataset.invalid=String(invalid);for(const error of field.querySelectorAll(fieldErrorSelector))error.dataset.uiValidationErrorBound="true",error.hidden=!invalid,error.textContent=invalid?message:""}},[]),getControls=useCallback((form=formRef.current)=>form?[...form.elements].filter(isNativeFormControl).filter(control=>control.matches(formControlSelector)&&control.form===form&&!control.matches(":disabled")):[],[]),validateControl=useCallback((control,form=formRef.current??control.form)=>{if(!form)return!0;const validateDetail={control,form,value:control.value};form.dispatchEvent(new CustomEvent("ui:validate",{bubbles:!0,detail:validateDetail})),onValidate?.(validateDetail);const invalid=!control.validity.valid,message=invalid?getValidationMessage(control,getFieldRoot(control)):"";return setFieldValidity(control,invalid,message),!invalid},[onValidate,setFieldValidity]),validateForm=useCallback((form=formRef.current)=>form?getControls(form).filter(control=>!validateControl(control,form)):[],[getControls,validateControl]),emitState=useCallback((name,detail)=>{detail.form.dispatchEvent(new CustomEvent(name,{bubbles:!0,detail})),name==="ui:invalid"?onInvalid?.(detail):onValid?.(detail)},[onInvalid,onValid]),syncFields=useCallback(form=>{for(const field of form.querySelectorAll(".ui-field, [data-ui-field]"))syncFieldDescription(field)},[]);return{formProps:{"data-ui-form":!0,noValidate:!0,onBlur:event=>{const form=event.currentTarget;if(syncFields(form),!isNativeFormControl(event.target)||event.target.form!==form)return;const valid=validateControl(event.target,form);emitState(valid?"ui:valid":"ui:invalid",valid?{control:event.target,form}:{control:event.target,controls:[event.target],form})},onSubmit:event=>{const form=event.currentTarget;syncFields(form);const invalidControls=validateForm(form);if(invalidControls.length){event.preventDefault();const firstInvalid=invalidControls[0];if(!firstInvalid)return;emitState("ui:invalid",{control:firstInvalid,controls:invalidControls,form}),firstInvalid.focus({preventScroll:!0}),firstInvalid.reportValidity();return}emitState("ui:valid",{controls:getControls(form),form})},ref:formRef},formRef,getControls,setFieldValidity,validateControl,validateForm}},calendarMonthPattern=/^\d{4}-\d{2}$/,parseCalendarMonth=value=>!value||!calendarMonthPattern.test(value)?null:parseCalendarDate(`${value}-01`),createCalendarDate=(year,month,day)=>{const date=new Date(0);return date.setUTCFullYear(year,month,day),date},formatCalendarDate=date=>date.toISOString().split("T")[0]??"",formatCalendarMonth=date=>formatCalendarDate(date).slice(0,-3),startOfCalendarMonth=date=>createCalendarDate(date.getUTCFullYear(),date.getUTCMonth(),1),addCalendarDays=(date,days)=>createCalendarDate(date.getUTCFullYear(),date.getUTCMonth(),date.getUTCDate()+days),getCalendarDaysInMonth=date=>createCalendarDate(date.getUTCFullYear(),date.getUTCMonth()+1,0).getUTCDate(),addCalendarMonths=(date,months)=>{const targetMonth=createCalendarDate(date.getUTCFullYear(),date.getUTCMonth()+months,1),day=Math.min(date.getUTCDate(),getCalendarDaysInMonth(targetMonth));return createCalendarDate(targetMonth.getUTCFullYear(),targetMonth.getUTCMonth(),day)},getCalendarToday=()=>{const today=new Date;return createCalendarDate(today.getFullYear(),today.getMonth(),today.getDate())},compareCalendarDates=(date,other)=>other?Math.sign(date.getTime()-other.getTime()):0,isCalendarDateDisabled=(disabled,date,min,max)=>disabled||compareCalendarDates(date,min)<0||compareCalendarDates(date,max)>0,clampCalendarDate=(date,min,max)=>min&&compareCalendarDates(date,min)<0?min:max&&compareCalendarDates(date,max)>0?max:date,getCalendarGridStart=month=>addCalendarDays(month,-((month.getUTCDay()+6)%7)),coerceCalendarDate=value=>value instanceof Date?value:parseCalendarDate(value),getCalendarFocusDate=(disabled,month,min,max,selectedDate,requestedDate)=>{const today=getCalendarToday(),candidates=[requestedDate,selectedDate,today,month];for(const candidate of candidates)if(candidate&&formatCalendarMonth(candidate)===formatCalendarMonth(month)&&!isCalendarDateDisabled(disabled,candidate,min,max))return candidate;for(let index=0;index<getCalendarDaysInMonth(month);index+=1){const date=addCalendarDays(month,index);if(!isCalendarDateDisabled(disabled,date,min,max))return date}return month},formatSelectedCalendarDate=value=>value?formatCalendarDate(value):"",useCalendar=({defaultValue,disabled:requestedDisabled=!1,labels,locale,max,min,month,name,onValueChange,readOnly=!1,value:controlledValue}={})=>{const disabled=requestedDisabled||!isCalendarBoundsValid(min,max),rootRef=useRef(null),labelId=`${`ui-calendar-${useId()}`}-label`,minDate=parseCalendarDate(min??"0001-01-01"),maxDate=parseCalendarDate(max??"9999-12-31"),defaultDate=parseCalendarDate(defaultValue),controlledDate=parseCalendarDate(controlledValue),[uncontrolledValue,setUncontrolledValue]=useState(()=>defaultDate?formatCalendarDate(defaultDate):""),selectedValue=controlledValue===void 0?uncontrolledValue:formatSelectedCalendarDate(controlledDate),selectedDate=parseCalendarDate(selectedValue),initialMonth=parseCalendarMonth(month)??(selectedDate?startOfCalendarMonth(selectedDate):null)??startOfCalendarMonth(getCalendarToday()),[visibleMonthValue,setVisibleMonthValue]=useState(()=>formatCalendarMonth(initialMonth)),visibleMonth=parseCalendarMonth(month??visibleMonthValue)??initialMonth,[focusValue,setFocusValue]=useState(null);useEffect(()=>{const root=rootRef.current,owner=root?.closest("form");let active=!0,resetTimer;const reset=event=>{globalThis.clearTimeout(resetTimer),resetTimer=globalThis.setTimeout(()=>{if(!active||event.defaultPrevented||!root?.isConnected)return;const date=parseCalendarDate(defaultValue);controlledValue===void 0&&setUncontrolledValue(date?formatCalendarDate(date):"");const resetMonth=parseCalendarMonth(month)??startOfCalendarMonth(date??getCalendarToday());setVisibleMonthValue(formatCalendarMonth(resetMonth)),setFocusValue(null)})};return owner?.addEventListener("reset",reset),()=>{active=!1,globalThis.clearTimeout(resetTimer),owner?.removeEventListener("reset",reset)}},[controlledValue,defaultValue,month]);const focusDate=getCalendarFocusDate(disabled,visibleMonth,minDate,maxDate,selectedDate,parseCalendarDate(focusValue)),focusIso=formatCalendarDate(focusDate);useEffect(()=>{const root=rootRef.current;root?.contains(root.ownerDocument.activeElement)&&root.ownerDocument.activeElement?.getAttribute("role")==="gridcell"&&root.querySelector('[role="gridcell"][tabindex="0"]')?.focus({preventScroll:!0})},[focusIso]);const selectedIso=selectedDate?formatCalendarDate(selectedDate):"",currentLocale=getCalendarLocale(locale||"en"),navigationLabels={...resolveDateControlLabels(currentLocale),...labels},monthFormatter=useMemo(()=>new Intl.DateTimeFormat(currentLocale,{month:"long",timeZone:"UTC",year:"numeric"}),[currentLocale]),dayFormatter=useMemo(()=>new Intl.DateTimeFormat(currentLocale,{day:"numeric",month:"long",timeZone:"UTC",weekday:"long",year:"numeric"}),[currentLocale]),weekdayFormatter=useMemo(()=>new Intl.DateTimeFormat(currentLocale,{timeZone:"UTC",weekday:"short"}),[currentLocale]),todayIso=formatCalendarDate(getCalendarToday()),label=monthFormatter.format(visibleMonth),weekdays=useMemo(()=>Array.from({length:7},(_,index)=>weekdayFormatter.format(new Date(Date.UTC(2026,0,5+index)))),[weekdayFormatter]),weeks=useMemo(()=>{const firstCell=getCalendarGridStart(visibleMonth);return Array.from({length:6},(_,rowIndex)=>Array.from({length:7},(_2,columnIndex)=>{const date=addCalendarDays(firstCell,rowIndex*7+columnIndex),dateIso=formatCalendarDate(date),unavailable=isCalendarDateDisabled(disabled,date,minDate,maxDate);return{date:dateIso,day:date.getUTCDate(),disabled:unavailable,label:dayFormatter.format(date),outside:formatCalendarMonth(date)!==formatCalendarMonth(visibleMonth),selected:selectedIso===dateIso,tabIndex:!unavailable&&focusIso===dateIso?0:-1,today:todayIso===dateIso}}))},[dayFormatter,disabled,focusIso,maxDate,minDate,selectedIso,todayIso,visibleMonth]),focusCalendarDate=dateInput=>{const date=coerceCalendarDate(dateInput);if(!date)return;const nextDate=clampCalendarDate(date,minDate,maxDate);setVisibleMonthValue(formatCalendarMonth(startOfCalendarMonth(nextDate))),setFocusValue(formatCalendarDate(nextDate))},selectDate=dateInput=>{if(disabled||readOnly)return;const date=coerceCalendarDate(dateInput);if(!date)return;const nextDate=clampCalendarDate(date,minDate,maxDate);if(isCalendarDateDisabled(disabled,nextDate,minDate,maxDate))return;const nextValue=formatCalendarDate(nextDate);controlledValue===void 0&&setUncontrolledValue(nextValue),setVisibleMonthValue(formatCalendarMonth(startOfCalendarMonth(nextDate))),setFocusValue(nextValue),onValueChange?.(nextValue)},moveFocus=(currentDate,key)=>{const column=(currentDate.getUTCDay()+6)%7,keyOffsets={ArrowDown:7,ArrowLeft:-1,ArrowRight:1,ArrowUp:-7,End:6-column,Home:-column};if(key==="PageDown"||key==="PageUp"){focusCalendarDate(addCalendarMonths(currentDate,key==="PageDown"?1:-1));return}const directionRoot=rootRef.current,offset=keyOffsets[directionRoot?getLumenDirectionalKey(directionRoot,key):key];offset!==void 0&&focusCalendarDate(addCalendarDays(currentDate,offset))},previousMonthLastDay=addCalendarDays(visibleMonth,-1),nextMonthFirstDay=addCalendarMonths(visibleMonth,1),previousDisabled=disabled||compareCalendarDates(previousMonthLastDay,minDate)<0,nextDisabled=disabled||compareCalendarDates(nextMonthFirstDay,maxDate)>0;return{focusDate:focusCalendarDate,getDayProps:(day,props={})=>({...props,"aria-disabled":day.disabled?!0:void 0,"aria-label":props["aria-label"]??day.label,"aria-selected":day.selected?"true":"false","data-date":day.date,"data-outside":day.outside?"true":void 0,"data-selected":day.selected?"true":void 0,"data-today":day.today?"true":void 0,"data-ui-calendar-day":!0,onClick:composeHandlers(props.onClick,()=>{day.disabled||selectDate(day.date)}),onKeyDown:composeHandlers(props.onKeyDown,event=>{if(day.disabled)return;if(event.key==="Enter"||event.key===" "){event.preventDefault(),selectDate(day.date);return}if(event.key!=="ArrowDown"&&event.key!=="ArrowLeft"&&event.key!=="ArrowRight"&&event.key!=="ArrowUp"&&event.key!=="End"&&event.key!=="Home"&&event.key!=="PageDown"&&event.key!=="PageUp")return;const date=parseCalendarDate(day.date);date&&(event.preventDefault(),moveFocus(date,event.key))}),role:"gridcell",tabIndex:props.tabIndex??day.tabIndex}),gridProps:{"aria-labelledby":labelId,"aria-readonly":readOnly||void 0,className:"ui-calendar__grid","data-ui-calendar-grid":!0,role:"grid"},inputProps:{"data-ui-calendar-input":!0,disabled,name,type:"hidden",value:selectedValue},label,labelProps:{className:"ui-calendar__label","data-ui-calendar-label":!0,id:labelId},month:formatCalendarMonth(visibleMonth),nextProps:{"aria-label":navigationLabels.nextMonth,className:"ui-calendar__nav","data-ui-calendar-next":!0,disabled:nextDisabled,onClick:()=>{if(nextDisabled)return;const nextMonth=startOfCalendarMonth(addCalendarMonths(visibleMonth,1));setVisibleMonthValue(formatCalendarMonth(nextMonth)),setFocusValue(formatCalendarDate(getCalendarFocusDate(disabled,nextMonth,minDate,maxDate,selectedDate)))},type:"button"},previousProps:{"aria-label":navigationLabels.previousMonth,className:"ui-calendar__nav","data-ui-calendar-prev":!0,disabled:previousDisabled,onClick:()=>{if(previousDisabled)return;const previousMonth=startOfCalendarMonth(addCalendarMonths(visibleMonth,-1));setVisibleMonthValue(formatCalendarMonth(previousMonth)),setFocusValue(formatCalendarDate(getCalendarFocusDate(disabled,previousMonth,minDate,maxDate,selectedDate)))},type:"button"},rootProps:{"aria-disabled":disabled?!0:void 0,className:"ui-calendar","data-disabled":disabled?"true":void 0,"data-ui-calendar":!0,"data-ui-calendar-initial-month":formatCalendarMonth(initialMonth),"data-ui-calendar-max":maxDate?formatCalendarDate(maxDate):void 0,"data-ui-calendar-min":minDate?formatCalendarDate(minDate):void 0,"data-ui-calendar-month":formatCalendarMonth(visibleMonth),"data-ui-calendar-value":selectedValue||void 0,ref:rootRef},rootRef,selectDate,value:selectedValue,weekdays,weeks}},defaultInputOtpLength=6,defaultInputOtpPattern="[0-9]*",coerceInputOtpLength=length=>Number(length)||defaultInputOtpLength,clampInputOtpLength=length=>Math.max(1,Math.floor(length)),normalizeInputOtpLength=length=>clampInputOtpLength(coerceInputOtpLength(length)),isNumericInputOtp=(inputMode,pattern)=>inputMode==="numeric"||pattern===defaultInputOtpPattern,sanitizeInputOtpValue=(value,length,inputMode,pattern)=>(isNumericInputOtp(inputMode,pattern)?value.replaceAll(/\D/g,""):value.replaceAll(/\s/g,"")).slice(0,length),getInputOtpActiveIndex=(length,value,selectionStart)=>Math.min(length-1,Math.max(0,selectionStart??value.length)),useInputOTP=({defaultValue,disabled=!1,inputMode="numeric",inputRef:forwardedInputRef,invalid=!1,length:lengthOption,onValueChange,pattern=defaultInputOtpPattern,value:controlledValue}={})=>{const length=normalizeInputOtpLength(lengthOption),rootRef=useRef(null),inputRef=useRef(null),[focused,setFocused]=useState(!1),[activeIndex,setActiveIndex]=useState(0),[uncontrolledValue,setUncontrolledValue]=useState(()=>sanitizeInputOtpValue(defaultValue??"",length,inputMode,pattern)),value=sanitizeInputOtpValue(controlledValue??uncontrolledValue,length,inputMode,pattern),segmentIndexes=useMemo(()=>Array.from({length},(_,index)=>index),[length]),syncValue=useCallback((input=inputRef.current)=>{if(!input)return value;const nextValue=sanitizeInputOtpValue(input.value,length,inputMode,pattern);return input.value!==nextValue&&(input.value=nextValue),controlledValue===void 0&&setUncontrolledValue(nextValue),setActiveIndex(getInputOtpActiveIndex(length,nextValue,input.selectionStart)),nextValue},[controlledValue,inputMode,length,pattern,value]),setSelection=useCallback(index=>{const input=inputRef.current,position=Math.min(value.length,Math.max(0,index));setActiveIndex(getInputOtpActiveIndex(length,value,position)),input?.focus({preventScroll:!0}),input?.setSelectionRange(position,position)},[length,value]),setValue=useCallback(nextValue=>{const sanitizedValue=sanitizeInputOtpValue(nextValue,length,inputMode,pattern),input=inputRef.current;return controlledValue===void 0&&setUncontrolledValue(sanitizedValue),input&&(input.value=sanitizedValue,input.setSelectionRange(sanitizedValue.length,sanitizedValue.length)),setActiveIndex(getInputOtpActiveIndex(length,sanitizedValue,sanitizedValue.length)),onValueChange?.(sanitizedValue),sanitizedValue},[controlledValue,inputMode,length,onValueChange,pattern]),commitInputValue=useCallback(input=>{onValueChange?.(syncValue(input))},[onValueChange,syncValue]);useEffect(()=>{const form=inputRef.current?.form;let resetTimer;if(!form)return;const handleReset=()=>{resetTimer=globalThis.setTimeout(()=>{syncValue(inputRef.current)})};return form.addEventListener("reset",handleReset),()=>{resetTimer&&globalThis.clearTimeout(resetTimer),form.removeEventListener("reset",handleReset)}},[syncValue]);const getInputProps=useCallback((props={})=>({...props,"aria-invalid":props["aria-invalid"]??(invalid?!0:void 0),autoComplete:props.autoComplete??"one-time-code",className:composeClassName("ui-input-otp ui-input-otp__native",props.className),"data-ui-enhanced":!0,"data-ui-input-otp-native":!0,disabled:props.disabled??disabled,inputMode:props.inputMode??inputMode,maxLength:props.maxLength??length,onBlur:composeHandlers(props.onBlur,event=>{setFocused(!1),syncValue(event.currentTarget)}),onChange:composeHandlers(props.onChange,event=>{commitInputValue(event.currentTarget)}),onClick:composeHandlers(props.onClick,event=>{syncValue(event.currentTarget)}),onFocus:composeHandlers(props.onFocus,event=>{setFocused(!0),syncValue(event.currentTarget)}),onInput:composeHandlers(props.onInput,event=>{commitInputValue(event.currentTarget)}),onKeyDown:composeHandlers(props.onKeyDown,event=>{if(event.key==="ArrowLeft"){event.preventDefault(),setSelection((event.currentTarget.selectionStart??event.currentTarget.value.length)-1);return}if(event.key==="ArrowRight"){event.preventDefault(),setSelection((event.currentTarget.selectionStart??event.currentTarget.value.length)+1);return}if(event.key==="Home"){event.preventDefault(),setSelection(0);return}event.key==="End"&&(event.preventDefault(),setSelection(event.currentTarget.value.length))}),onKeyUp:composeHandlers(props.onKeyUp,event=>{syncValue(event.currentTarget)}),onPaste:composeHandlers(props.onPaste,event=>{const pasted=event.clipboardData.getData("text");pasted&&(event.preventDefault(),setValue(pasted))}),pattern:props.pattern??pattern,ref:node=>{inputRef.current=node,setRefValue(forwardedInputRef,node)},type:"text",value}),[commitInputValue,disabled,forwardedInputRef,inputMode,invalid,length,pattern,setSelection,setValue,syncValue,value]),getSegmentChar=useCallback(index=>value[index]??"\xA0",[value]),getSegmentProps=useCallback((index,props={})=>({...props,"aria-hidden":!0,className:composeClassName("ui-input-otp__segment",props.className),"data-active":String(focused&&index===activeIndex),"data-index":index,"data-ui-input-otp-segment":!0,disabled:props.disabled??disabled,onClick:composeHandlers(props.onClick,()=>{setSelection(index)}),tabIndex:props.tabIndex??-1,type:"button"}),[activeIndex,disabled,focused,setSelection]);return{activeIndex,getInputProps,getSegmentChar,getSegmentProps,inputRef,rootProps:{className:"ui-input-otp-field","data-disabled":disabled?"true":"false","data-invalid":invalid?"true":"false","data-ui-input-otp":!0,"data-ui-input-otp-length":length,ref:rootRef},rootRef,segmentIndexes,segmentsProps:{className:"ui-input-otp__segments","data-ui-input-otp-segments":!0},setSelection,setValue,syncValue,value}},getDateRangeRoot=(control,fallback)=>control.closest("[data-ui-date-range-picker]")??fallback,getDateRangeInputs=(root,start,end)=>{if(start&&end&&start!==end)return[start,end];const inputs=root?[...root.querySelectorAll('input[type="date"]')]:[];return[start??inputs[0]??null,end??inputs.at(-1)??null]},syncDateInputConstraint=(input,property,value)=>{if(value){input[property]=value;return}input.removeAttribute(property)},syncDateRangeInputs=(start,end)=>!start||!end||start===end?{}:(start.value&&end.value&&end.value<start.value&&(end.value=start.value),syncDateInputConstraint(end,"min",start.value),syncDateInputConstraint(start,"max",end.value),{...end.value?{end:end.value}:{},...start.value?{start:start.value}:{}}),useDateRangePicker=({onRangeChange}={})=>{const rootRef=useRef(null),startRef=useRef(null),endRef=useRef(null),syncRange=useCallback((root=rootRef.current)=>{const[start,end]=getDateRangeInputs(root,startRef.current,endRef.current),detail=syncDateRangeInputs(start,end);return onRangeChange?.(detail),detail},[onRangeChange]),getStartProps=useCallback((props={})=>({...props,onChange:composeHandlers(props.onChange,event=>{startRef.current=event.currentTarget,syncRange(getDateRangeRoot(event.currentTarget,rootRef.current))}),ref:startRef,type:props.type??"date"}),[syncRange]),getEndProps=useCallback((props={})=>({...props,onChange:composeHandlers(props.onChange,event=>{endRef.current=event.currentTarget,syncRange(getDateRangeRoot(event.currentTarget,rootRef.current))}),ref:endRef,type:props.type??"date"}),[syncRange]);return{endRef,getEndProps,getStartProps,rootProps:{"data-ui-date-range-picker":!0,ref:rootRef},rootRef,startRef,syncRange}},getScheduleRoot=(element,fallback)=>element.closest("[data-ui-schedule]")??fallback,useSchedule=({onChange}={})=>{const rootRef=useRef(null),emitChange=useCallback((detail,root=rootRef.current)=>{root&&typeof CustomEvent<"u"&&root.dispatchEvent(new CustomEvent("ui:schedule-change",{bubbles:!0,detail})),onChange?.(detail)},[onChange]),getEventProps=useCallback((eventId,props={})=>({...props,...eventId&&!props.id?{id:eventId}:{},"data-ui-draggable":!0,"data-ui-schedule-event":!0,draggable:props.draggable??!0,onDragEnd:composeHandlers(props.onDragEnd,event=>{const root=getScheduleRoot(event.currentTarget,rootRef.current);root&&delete root.dataset.uiDragging}),onDragStart:composeHandlers(props.onDragStart,event=>{const transferValue=eventId||event.currentTarget.id||event.currentTarget.textContent.trim(),root=getScheduleRoot(event.currentTarget,rootRef.current);event.dataTransfer.setData("text/plain",transferValue),root&&(root.dataset.uiDragging="true")})}),[]),getSlotProps=useCallback((slot,props={})=>({...props,"data-ui-schedule-slot":slot,onDragLeave:composeHandlers(props.onDragLeave,event=>{delete event.currentTarget.dataset.state}),onDragOver:composeHandlers(props.onDragOver,event=>{event.preventDefault(),event.currentTarget.dataset.state="drag-over"}),onDrop:composeHandlers(props.onDrop,event=>{event.preventDefault(),delete event.currentTarget.dataset.state;const draggedId=event.dataTransfer.getData("text/plain"),dragged=typeof document<"u"&&draggedId?document.getElementById(draggedId):null;typeof HTMLElement<"u"&&dragged instanceof HTMLElement&&event.currentTarget.append(dragged),emitChange({...draggedId?{eventId:draggedId}:{},...slot?{slot}:{}},getScheduleRoot(event.currentTarget,rootRef.current))})}),[emitChange]);return{emitChange,getEventProps,getSlotProps,rootProps:{"data-ui-schedule":!0,ref:rootRef},rootRef}},getKanbanRoot=(element,fallback)=>element.closest("[data-ui-kanban]")??fallback,getKanbanItem=(root,itemId)=>[...root?.querySelectorAll("[data-ui-kanban-item]")??[]].find(item=>item.dataset.uiKanbanItem===itemId)??null,getKanbanColumnValues=root=>[...root?.querySelectorAll("[data-ui-kanban-column]")??[]].map(column=>column.dataset.uiKanbanColumn??"").filter(Boolean),isKanbanArrowKey=key=>["ArrowLeft","ArrowRight"].includes(key),clearKanbanInteractionState=(root,item)=>{item?.dataset.state==="dragging"&&delete item.dataset.state;for(const column of root?.querySelectorAll("[data-ui-kanban-column]")??[])delete column.dataset.state},setKanbanDropTarget=(root,target)=>{for(const column of root?.querySelectorAll("[data-ui-kanban-column]")??[])column===target?column.dataset.state="drop-target":delete column.dataset.state},getKanbanColumnAtPoint=(root,clientX,clientY)=>{if(!root)return null;const column=document.elementFromPoint(clientX,clientY)?.closest("[data-ui-kanban-column]")??null;return column?.closest("[data-ui-kanban]")===root?column:null},requestKeyboardKanbanMove=(event,itemId,root,requestMove)=>{if(!isKanbanArrowKey(event.key)||!root)return;const item=getKanbanItem(root,itemId);if(!item||item.getAttribute("aria-busy")==="true")return;const fromColumn=item.closest("[data-ui-kanban-column]")?.dataset.uiKanbanColumn;if(!fromColumn)return;const columns=getKanbanColumnValues(root),currentIndex=columns.indexOf(fromColumn),visualDirection=event.key==="ArrowRight"?1:-1,direction=getComputedStyle(root).direction==="rtl"?-visualDirection:visualDirection,toColumn=columns[currentIndex+direction];toColumn&&(event.preventDefault(),requestMove({fromColumn,input:"keyboard",itemId,toColumn}))},updateKanbanPointer=(event,pointer,root)=>{if(pointer?.pointerId!==event.pointerId||Math.hypot(event.clientX-pointer.startX,event.clientY-pointer.startY)<8&&!pointer.active)return;pointer.active=!0,event.currentTarget.setPointerCapture(event.pointerId);const item=getKanbanItem(root,pointer.itemId),target=getKanbanColumnAtPoint(root,event.clientX,event.clientY);item&&(item.dataset.state="dragging"),setKanbanDropTarget(root,target)},finishKanbanPointer=(event,pointer,root,requestMove)=>{if(pointer?.pointerId!==event.pointerId||!pointer.active)return;const item=getKanbanItem(root,pointer.itemId),fromColumn=item?.closest("[data-ui-kanban-column]")?.dataset.uiKanbanColumn,toColumn=getKanbanColumnAtPoint(root,event.clientX,event.clientY)?.dataset.uiKanbanColumn;clearKanbanInteractionState(root,item),fromColumn&&toColumn&&requestMove({fromColumn,input:"pointer",itemId:pointer.itemId,toColumn})},useKanban=({onMoveRequest}={})=>{const rootRef=useRef(null),pointerRef=useRef(null),requestMove=useCallback(detail=>{const normalized=createLumenKanbanMoveDetail(detail);if(!normalized)return!1;const accepted=rootRef.current&&typeof CustomEvent<"u"?rootRef.current.dispatchEvent(new CustomEvent("ui:kanban-move-request",{bubbles:!0,cancelable:!0,detail:normalized})):!0;return accepted&&onMoveRequest?.(normalized),accepted},[onMoveRequest]),getItemProps=useCallback((itemId,props={})=>({...props,"data-ui-kanban-item":itemId,id:props.id??itemId}),[]),getHandleProps=useCallback((itemId,props={})=>({...props,"aria-keyshortcuts":props["aria-keyshortcuts"]??"ArrowLeft ArrowRight","data-ui-kanban-handle":itemId,draggable:props.draggable??!0,onDragEnd:composeHandlers(props.onDragEnd,event=>{const root=getKanbanRoot(event.currentTarget,rootRef.current),item=getKanbanItem(root,itemId);item?.dataset.state==="dragging"&&delete item.dataset.state}),onDragStart:composeHandlers(props.onDragStart,event=>{const root=getKanbanRoot(event.currentTarget,rootRef.current),item=getKanbanItem(root,itemId);if(!item||item.getAttribute("aria-busy")==="true"){event.preventDefault();return}event.dataTransfer.setData("text/plain",itemId),event.dataTransfer.effectAllowed="move",item.dataset.state="dragging"}),onKeyDown:composeHandlers(props.onKeyDown,event=>{const root=getKanbanRoot(event.currentTarget,rootRef.current);requestKeyboardKanbanMove(event,itemId,root,requestMove)}),onPointerDown:composeHandlers(props.onPointerDown,event=>{event.pointerType!=="mouse"&&(pointerRef.current={active:!1,itemId,pointerId:event.pointerId,startX:event.clientX,startY:event.clientY})}),onPointerMove:composeHandlers(props.onPointerMove,event=>{const root=getKanbanRoot(event.currentTarget,rootRef.current);updateKanbanPointer(event,pointerRef.current,root)}),onPointerCancel:composeHandlers(props.onPointerCancel,event=>{const pointer=pointerRef.current;if(pointer?.pointerId!==event.pointerId)return;pointerRef.current=null;const root=getKanbanRoot(event.currentTarget,rootRef.current),item=getKanbanItem(root,pointer.itemId);clearKanbanInteractionState(root,item)}),onPointerUp:composeHandlers(props.onPointerUp,event=>{const pointer=pointerRef.current;pointerRef.current=null;const root=getKanbanRoot(event.currentTarget,rootRef.current);finishKanbanPointer(event,pointer,root,requestMove)}),type:props.type??"button"}),[requestMove]);return{getColumnProps:useCallback((column,props={})=>({...props,"data-ui-kanban-column":column,onDragLeave:composeHandlers(props.onDragLeave,event=>{delete event.currentTarget.dataset.state}),onDragOver:composeHandlers(props.onDragOver,event=>{event.preventDefault(),event.dataTransfer.dropEffect="move",event.currentTarget.dataset.state="drop-target"}),onDrop:composeHandlers(props.onDrop,event=>{event.preventDefault(),delete event.currentTarget.dataset.state;const itemId=event.dataTransfer.getData("text/plain"),root=getKanbanRoot(event.currentTarget,rootRef.current),item=getKanbanItem(root,itemId),fromColumn=item?.closest("[data-ui-kanban-column]")?.dataset.uiKanbanColumn;item?.dataset.state==="dragging"&&delete item.dataset.state,fromColumn&&requestMove({fromColumn,input:"pointer",itemId,toColumn:column})})}),[requestMove]),getHandleProps,getItemProps,requestMove,rootProps:{"aria-orientation":"horizontal","data-ui-kanban":!0,ref:rootRef,tabIndex:0},rootRef}},parseResizableNumberList=(value,count,fallback)=>{const values=typeof value=="number"?[value]:value??[];return Array.from({length:count},(_,index)=>values[index]??values[0]??fallback)},normalizeResizableSizes=(sizes,count)=>{const fallbackSize=100/Math.max(1,count),usableSizes=Array.from({length:count},(_,index)=>{const size=sizes[index]??fallbackSize;return Number.isFinite(size)&&size>0?size:fallbackSize}),total=usableSizes.reduce((sum,size)=>sum+size,0);return total<=0?Array.from({length:count},()=>fallbackSize):usableSizes.map(size=>size/total*100)},serializeResizableSize=value=>{if(value!==void 0)return Array.isArray(value)?value.join(","):String(value)},useResizable=({defaultSizes,direction="horizontal",maxSize,minSize,onSizesChange,panelCount:panelCountOption=2,resetOnDoubleClick=!0}={})=>{if(!Number.isFinite(panelCountOption)||panelCountOption>1e3)throw new RangeError("Resizable panelCount must be finite and at most 1000");const panelCount=Math.max(0,Math.floor(panelCountOption)),rootRef=useRef(null),dragRef=useRef(null),minSizes=useMemo(()=>parseResizableNumberList(minSize,panelCount,12),[minSize,panelCount]),maxSizes=useMemo(()=>parseResizableNumberList(maxSize,panelCount,88),[maxSize,panelCount]),initialSizes=useMemo(()=>normalizeResizableSizes(parseResizableNumberList(defaultSizes,panelCount,100/Math.max(1,panelCount)),panelCount),[defaultSizes,panelCount]),[sizes,setSizes]=useState(initialSizes),axis=direction==="horizontal"?"clientX":"clientY",sizeProperty=direction==="horizontal"?"width":"height",separatorOrientation=direction==="horizontal"?"vertical":"horizontal",panelIndexes=useMemo(()=>Array.from({length:panelCount},(_,index)=>index),[panelCount]),handleIndexes=useMemo(()=>Array.from({length:Math.max(0,panelCount-1)},(_,index)=>index),[panelCount]);useEffect(()=>{setSizes(initialSizes)},[initialSizes]);const updateSizes=useCallback(updater=>{setSizes(currentSizes=>{const nextSizes=updater(currentSizes);return onSizesChange?.(nextSizes),nextSizes})},[onSizesChange]),resizePair=useCallback((index,nextSize)=>{updateSizes(currentSizes=>{const nextSizes=[...currentSizes],nextIndex=index+1,total=(nextSizes[index]??0)+(nextSizes[nextIndex]??0),min=Math.max(minSizes[index]??0,total-(maxSizes[nextIndex]??100)),max=Math.min(maxSizes[index]??100,total-(minSizes[nextIndex]??0)),paneSize=Math.min(max,Math.max(min,nextSize));return nextSizes[index]=paneSize,nextSizes[nextIndex]=total-paneSize,nextSizes})},[maxSizes,minSizes,updateSizes]),reset=useCallback(()=>{updateSizes(()=>[...initialSizes])},[initialSizes,updateSizes]),getPanelProps=useCallback((index,props={})=>({...props,"data-ui-resizable-panel":!0,style:{...props.style,"--ui-resizable-size":`${sizes[index]??0}%`}}),[sizes]),getHandleProps=useCallback((index,props={})=>({...props,"aria-label":props["aria-label"]??`Resize panel ${index+1}`,"aria-orientation":separatorOrientation,"aria-valuemax":Math.round(maxSizes[index]??100),"aria-valuemin":Math.round(minSizes[index]??0),"aria-valuenow":Math.round(sizes[index]??0),className:composeClassName("ui-resizable__handle",props.className),"data-index":index,"data-ui-resizable-handle":!0,onDoubleClick:composeHandlers(props.onDoubleClick,()=>{resetOnDoubleClick&&reset()}),onKeyDown:composeHandlers(props.onKeyDown,event=>{const step=event.shiftKey?10:2,keyDeltas=direction==="horizontal"?{ArrowLeft:-step,ArrowRight:step}:{ArrowDown:step,ArrowUp:-step};if(event.key==="Home"){event.preventDefault(),resizePair(index,minSizes[index]??0);return}if(event.key==="End"){event.preventDefault(),resizePair(index,maxSizes[index]??100);return}const delta=keyDeltas[getLumenDirectionalKey(event.currentTarget,event.key)];delta!==void 0&&(event.preventDefault(),resizePair(index,(sizes[index]??0)+delta))}),onPointerDown:composeHandlers(props.onPointerDown,event=>{event.button===0&&(event.preventDefault(),dragRef.current={containerSize:Math.max(1,rootRef.current?.getBoundingClientRect()[sizeProperty]??1),index,startPosition:event[axis],startSize:sizes[index]??0},rootRef.current&&(rootRef.current.dataset.resizing="true"),event.currentTarget.dataset.active="true",event.currentTarget.setPointerCapture(event.pointerId))}),onPointerMove:composeHandlers(props.onPointerMove,event=>{const drag=dragRef.current;if(drag?.index!==index||event.currentTarget.dataset.active!=="true")return;const delta=(event[axis]-drag.startPosition)/drag.containerSize*100,multiplier=direction==="horizontal"&&getLumenDirectionalKey(event.currentTarget,"ArrowRight")==="ArrowLeft"?-1:1;resizePair(index,drag.startSize+delta*multiplier)}),onPointerUp:composeHandlers(props.onPointerUp,event=>{event.currentTarget.dataset.active==="true"&&(delete event.currentTarget.dataset.active,rootRef.current&&delete rootRef.current.dataset.resizing,dragRef.current=null,event.currentTarget.releasePointerCapture(event.pointerId))}),role:"separator",tabIndex:props.tabIndex??0,type:props.type??"button"}),[axis,direction,maxSizes,minSizes,reset,resetOnDoubleClick,resizePair,separatorOrientation,sizeProperty,sizes]);return{direction,getHandleProps,getPanelProps,handleIndexes,panelIndexes,reset,resizePair,rootProps:{className:composeClassName("ui-resizable",direction==="vertical"&&"ui-resizable--vertical"),"data-orientation":direction,"data-ui-resizable":!0,"data-ui-resizable-default-sizes":defaultSizes?.join(","),"data-ui-resizable-enhanced":!0,"data-ui-resizable-max-size":serializeResizableSize(maxSize),"data-ui-resizable-min-size":serializeResizableSize(minSize),"data-ui-resizable-reset":resetOnDoubleClick?"true":void 0,ref:rootRef},rootRef,sizes}},useThemeBuilder=({radiusScale,spacingScale,borderWidth,preset,defaultPreset,onPresetChange,accentHue,defaultAccentHue=54,defaultExportFormat="css",defaultHue=264,defaultMode="generated",defaultPrimaryColor="#6f20f0",defaultScheme="light",defaultSecondaryColor="#14b8a6",exportFormat,hue,mode,onAccentHueChange,onExportFormatChange,onHueChange,onModeChange,onPrimaryColorChange,onSchemeChange,onSecondaryColorChange,onThemeChange,onThemeExport,primaryColor,scheme,secondaryColor}={})=>{const[currentPreset,setPreset]=useControllableState({defaultValue:defaultPreset,onChange:onPresetChange,value:preset}),[currentHue,setHue]=useControllableState({defaultValue:defaultHue,onChange:onHueChange,value:hue}),[currentAccentHue,setAccentHue]=useControllableState({defaultValue:defaultAccentHue,onChange:onAccentHueChange,value:accentHue}),[currentMode,setMode]=useControllableState({defaultValue:defaultMode,onChange:onModeChange,value:mode}),[currentScheme,setScheme]=useControllableState({defaultValue:defaultScheme,onChange:onSchemeChange,value:scheme}),[currentPrimaryColor,setPrimaryColor]=useControllableState({defaultValue:defaultPrimaryColor,onChange:onPrimaryColorChange,value:primaryColor}),[currentSecondaryColor,setSecondaryColor]=useControllableState({defaultValue:defaultSecondaryColor,onChange:onSecondaryColorChange,value:secondaryColor}),[currentExportFormat,setExportFormat]=useControllableState({defaultValue:defaultExportFormat,onChange:onExportFormatChange,value:exportFormat}),result=useMemo(()=>createThemeBuilderTokens({radiusScale:radiusScale??null,spacingScale:spacingScale??null,borderWidth:borderWidth??null,preset:currentPreset??null,accentHue:currentAccentHue,hue:currentHue,mode:currentMode,primaryColor:currentPrimaryColor,scheme:currentScheme,secondaryColor:currentSecondaryColor}),[radiusScale,spacingScale,borderWidth,currentPreset,currentAccentHue,currentHue,currentMode,currentPrimaryColor,currentScheme,currentSecondaryColor]),exportValue=useMemo(()=>exportThemeBuilderValue(result.tokens,result.scheme,currentExportFormat),[currentExportFormat,result.scheme,result.tokens]),previewStyle=useMemo(()=>Object.fromEntries(Object.entries(result.tokens).map(([token,value])=>[`--${token}`,value])),[result.tokens]);useEffect(()=>{onThemeChange?.(result)},[onThemeChange,result]);const exportDetail=useMemo(()=>({...currentExportFormat==="css"?{css:exportValue}:{},format:currentExportFormat,tokens:result.tokens,value:exportValue}),[currentExportFormat,exportValue,result.tokens]),copyExport=useCallback(async()=>(typeof navigator<"u"&&await navigator.clipboard.writeText(exportValue).catch(()=>null),onThemeExport?.(exportDetail),exportValue),[exportDetail,exportValue,onThemeExport]),getPresetProps=useCallback((nextPreset,props={})=>({...props,"aria-pressed":currentPreset===nextPreset,"data-ui-theme-preset":nextPreset,onClick:composeHandlers(props.onClick,()=>{setPreset(nextPreset)}),type:props.type??"button"}),[currentPreset,setPreset]),getModeProps=useCallback((nextMode,props={})=>({...props,"aria-pressed":currentMode===nextMode,"data-ui-theme-mode":nextMode,onClick:composeHandlers(props.onClick,()=>{setMode(nextMode)}),type:props.type??"button"}),[currentMode,setMode]),getSchemeProps=useCallback((nextScheme,props={})=>({...props,"aria-pressed":currentScheme===nextScheme,"data-ui-theme-scheme":nextScheme,onClick:composeHandlers(props.onClick,()=>{setScheme(nextScheme)}),type:props.type??"button"}),[currentScheme,setScheme]),getExportFormatProps=useCallback((format,props={})=>({...props,"aria-pressed":currentExportFormat===format,"data-ui-theme-export-format":format,onClick:composeHandlers(props.onClick,()=>{setExportFormat(coerceThemeBuilderExportFormat(format))}),type:props.type??"button"}),[currentExportFormat,setExportFormat]);return{...result,setPreset,getPresetProps,accentHueProps:{"data-ui-theme-accent-hue":!0,max:359,min:0,onChange:event=>{setAccentHue(Number(event.currentTarget.value))},type:"range",value:currentAccentHue},copyExport,exportButtonProps:{"data-ui-theme-export":!0,onClick:()=>{copyExport().catch(()=>null)},type:"button"},exportFormat:currentExportFormat,exportValue,getExportFormatProps,getModeProps,getSchemeProps,hueProps:{"data-ui-theme-brand-hue":!0,max:359,min:0,onChange:event=>{setHue(Number(event.currentTarget.value))},type:"range",value:currentHue},outputProps:{"data-ui-theme-output":!0,readOnly:!0,value:exportValue},previewStyle,primaryColorProps:{"data-ui-theme-primary-color":!0,onChange:event=>{setPrimaryColor(event.currentTarget.value)},type:"color",value:currentPrimaryColor},rootProps:{"data-ui-theme-builder":!0},secondaryColorProps:{"data-ui-theme-secondary-color":!0,onChange:event=>{setSecondaryColor(event.currentTarget.value)},type:"color",value:currentSecondaryColor},setAccentHue,setExportFormat,setHue,setMode,setPrimaryColor,setScheme,setSecondaryColor}},useTooltip=({defaultOpen=!1,delay=250,id,onOpenChange,open}={})=>{const tooltipId=useSafeId("ui-tooltip",id),timerRef=useRef(void 0),[isOpen,setIsOpen]=useControllableState({defaultValue:defaultOpen,onChange:onOpenChange,value:open}),close=useCallback(()=>{clearTimeout(timerRef.current),setIsOpen(!1)},[setIsOpen]),scheduleOpen=useCallback(nextDelay=>{clearTimeout(timerRef.current),timerRef.current=setTimeout(()=>{setIsOpen(!0)},nextDelay)},[setIsOpen]);return useEffect(()=>()=>{clearTimeout(timerRef.current)},[]),{close,open:isOpen,rootProps:{"aria-describedby":isOpen?tooltipId:void 0,"data-ui-tooltip":!0,onBlur:event=>{event.currentTarget.contains(event.relatedTarget)||close()},onFocus:()=>{scheduleOpen(0)},onKeyDown:event=>{event.defaultPrevented||event.nativeEvent.isComposing||event.key==="Escape"&&isOpen&&(event.preventDefault(),close())},onMouseEnter:()=>{scheduleOpen(delay)},onMouseLeave:close},setOpen:setIsOpen,tooltipProps:{hidden:!isOpen,id:tooltipId,role:"tooltip"}}},useThemeToggle=(defaultTheme="light")=>{const[theme,setTheme]=useState(()=>typeof window<"u"?localStorage.getItem("theme")??(document.documentElement.classList.contains("dark")?"dark":"light"):defaultTheme);return useEffect(()=>{const doc=document.documentElement;doc.setAttribute("data-theme",theme),theme==="dark"?doc.classList.add("dark"):doc.classList.remove("dark"),localStorage.setItem("theme",theme),window.dispatchEvent(new CustomEvent("theme-change",{detail:theme}))},[theme]),{theme,toggleTheme:event=>{const isDark=theme==="dark",newTheme=isDark?"light":"dark",prefersReducedMotion=window.matchMedia("(prefers-reduced-motion: reduce)").matches,isTouchDevice=window.matchMedia("(hover: none) and (pointer: coarse)").matches;if(prefersReducedMotion||isTouchDevice||!event){setTheme(newTheme);return}const rect=event.currentTarget.getBoundingClientRect(),x=Math.round(rect.left+rect.width/2),y=Math.round(rect.top+rect.height/2),maxRadius=Math.hypot(Math.max(x,window.innerWidth-x),Math.max(y,window.innerHeight-y)),oldBg=isDark?"hsl(277 20% 10%)":"hsl(268 20% 98%)",overlay=document.createElement("div");overlay.setAttribute("aria-hidden","true"),Object.assign(overlay.style,{position:"fixed",inset:"0",zIndex:"99999",pointerEvents:"none",backgroundColor:oldBg,clipPath:`circle(${maxRadius}px at ${x}px ${y}px)`,willChange:"clip-path"}),document.body.appendChild(overlay),setTheme(newTheme),requestAnimationFrame(()=>{requestAnimationFrame(()=>{overlay.style.transition="clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)",overlay.style.clipPath=`circle(0px at ${x}px ${y}px)`;const done=()=>{overlay.remove()};overlay.addEventListener("transitionend",done,{once:!0}),setTimeout(done,750)})})},setTheme}};export{useCalendar,useContextMenu,useDateRangePicker,useDialog,useDropdownMenu,useFormValidation,useInputOTP,useKanban,useLanguageToggle,usePopover,useResizable,useRichTextEditor,useSchedule,useSelect,useTabs,useThemeBuilder,useThemeToggle,useToast,useTooltip};
|