@santi020k/lumen-react 3.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +692 -13
  2. package/dist/ai-surfaces.d.ts +49 -0
  3. package/dist/ai-surfaces.d.ts.map +1 -0
  4. package/dist/ai-surfaces.js +88 -0
  5. package/dist/amount-field.d.ts +11 -0
  6. package/dist/amount-field.d.ts.map +1 -0
  7. package/dist/amount-field.js +37 -0
  8. package/dist/attachments.d.ts +17 -0
  9. package/dist/attachments.d.ts.map +1 -0
  10. package/dist/attachments.js +23 -0
  11. package/dist/bullet-chart.d.ts +14 -0
  12. package/dist/bullet-chart.d.ts.map +1 -0
  13. package/dist/bullet-chart.js +22 -0
  14. package/dist/change-summary.d.ts +14 -0
  15. package/dist/change-summary.d.ts.map +1 -0
  16. package/dist/change-summary.js +4 -0
  17. package/dist/chart-inspection.d.ts +10 -0
  18. package/dist/chart-inspection.d.ts.map +1 -0
  19. package/dist/chart-inspection.js +11 -0
  20. package/dist/chart-interaction.d.ts +17 -0
  21. package/dist/chart-interaction.d.ts.map +1 -0
  22. package/dist/chart-interaction.js +50 -0
  23. package/dist/chart-label.d.ts +3 -0
  24. package/dist/chart-label.d.ts.map +1 -0
  25. package/dist/chart-label.js +1 -0
  26. package/dist/chart-recipes.d.ts +8 -0
  27. package/dist/chart-recipes.d.ts.map +1 -1
  28. package/dist/chart-recipes.js +35 -0
  29. package/dist/combobox.d.ts +9 -0
  30. package/dist/combobox.d.ts.map +1 -0
  31. package/dist/combobox.js +137 -0
  32. package/dist/comparison-chart.d.ts +13 -0
  33. package/dist/comparison-chart.d.ts.map +1 -0
  34. package/dist/comparison-chart.js +18 -0
  35. package/dist/components.d.ts +106 -70
  36. package/dist/components.d.ts.map +1 -1
  37. package/dist/components.js +4 -1949
  38. package/dist/dashboard.d.ts +20 -0
  39. package/dist/dashboard.d.ts.map +1 -0
  40. package/dist/dashboard.js +19 -0
  41. package/dist/data-table-range-model.d.ts +8 -0
  42. package/dist/data-table-range-model.d.ts.map +1 -0
  43. package/dist/data-table-range-model.js +31 -0
  44. package/dist/data-table-range.d.ts +17 -0
  45. package/dist/data-table-range.d.ts.map +1 -0
  46. package/dist/data-table-range.js +8 -0
  47. package/dist/data-table-saved-views.d.ts +30 -0
  48. package/dist/data-table-saved-views.d.ts.map +1 -0
  49. package/dist/data-table-saved-views.js +49 -0
  50. package/dist/data-table-selection-model.d.ts +23 -0
  51. package/dist/data-table-selection-model.d.ts.map +1 -0
  52. package/dist/data-table-selection-model.js +36 -0
  53. package/dist/data-table-selection.d.ts +11 -0
  54. package/dist/data-table-selection.d.ts.map +1 -0
  55. package/dist/data-table-selection.js +10 -0
  56. package/dist/data-table-state.d.ts +6 -0
  57. package/dist/data-table-state.d.ts.map +1 -0
  58. package/dist/data-table-state.js +38 -0
  59. package/dist/data-table-view.d.ts +88 -0
  60. package/dist/data-table-view.d.ts.map +1 -0
  61. package/dist/data-table-view.js +1 -0
  62. package/dist/data-table.d.ts +60 -0
  63. package/dist/data-table.d.ts.map +1 -0
  64. package/dist/data-table.js +1 -0
  65. package/dist/date-range-calendar.d.ts +29 -0
  66. package/dist/date-range-calendar.d.ts.map +1 -0
  67. package/dist/date-range-calendar.js +69 -0
  68. package/dist/date-range-input.d.ts +25 -0
  69. package/dist/date-range-input.d.ts.map +1 -0
  70. package/dist/date-range-input.js +195 -0
  71. package/dist/device-frame.d.ts +13 -0
  72. package/dist/device-frame.d.ts.map +1 -0
  73. package/dist/device-frame.js +23 -0
  74. package/dist/dialog-lifecycle.d.ts +3 -0
  75. package/dist/dialog-lifecycle.d.ts.map +1 -0
  76. package/dist/dialog-lifecycle.js +70 -0
  77. package/dist/expanded-charts.d.ts +26 -0
  78. package/dist/expanded-charts.d.ts.map +1 -0
  79. package/dist/expanded-charts.js +46 -0
  80. package/dist/floating-panel.d.ts +12 -0
  81. package/dist/floating-panel.d.ts.map +1 -0
  82. package/dist/floating-panel.js +1 -0
  83. package/dist/form-workflow.d.ts +77 -0
  84. package/dist/form-workflow.d.ts.map +1 -0
  85. package/dist/form-workflow.js +337 -0
  86. package/dist/hooks.d.ts +24 -21
  87. package/dist/hooks.d.ts.map +1 -1
  88. package/dist/hooks.js +1 -2468
  89. package/dist/image-comparison.d.ts +20 -0
  90. package/dist/image-comparison.d.ts.map +1 -0
  91. package/dist/image-comparison.js +55 -0
  92. package/dist/index.d.ts +25 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +24 -0
  95. package/dist/interval-charts.d.ts +21 -0
  96. package/dist/interval-charts.d.ts.map +1 -0
  97. package/dist/interval-charts.js +23 -0
  98. package/dist/media-selection.d.ts +18 -0
  99. package/dist/media-selection.d.ts.map +1 -0
  100. package/dist/media-selection.js +12 -0
  101. package/dist/media-viewport.d.ts +16 -0
  102. package/dist/media-viewport.d.ts.map +1 -0
  103. package/dist/media-viewport.js +37 -0
  104. package/dist/motion.d.ts +597 -0
  105. package/dist/motion.d.ts.map +1 -0
  106. package/dist/motion.js +22 -0
  107. package/dist/review-workflow.d.ts +32 -0
  108. package/dist/review-workflow.d.ts.map +1 -0
  109. package/dist/review-workflow.js +76 -0
  110. package/dist/rich-text-editor.d.ts +21 -0
  111. package/dist/rich-text-editor.d.ts.map +1 -0
  112. package/dist/rich-text-editor.js +143 -0
  113. package/dist/select-form.d.ts +3 -0
  114. package/dist/select-form.d.ts.map +1 -0
  115. package/dist/select-form.js +1 -0
  116. package/dist/server-components.d.ts +33 -6
  117. package/dist/server-components.d.ts.map +1 -1
  118. package/dist/server-components.js +26 -10
  119. package/dist/server.d.ts +1 -0
  120. package/dist/server.d.ts.map +1 -1
  121. package/dist/server.js +1 -0
  122. package/dist/toast-context.d.ts +4 -0
  123. package/dist/toast-context.d.ts.map +1 -0
  124. package/dist/toast-context.js +10 -0
  125. package/dist/toast-provider.d.ts +3 -0
  126. package/dist/toast-provider.d.ts.map +1 -0
  127. package/dist/toast-provider.js +3 -0
  128. package/dist/use-copy-feedback.d.ts +17 -0
  129. package/dist/use-copy-feedback.d.ts.map +1 -0
  130. package/dist/use-copy-feedback.js +54 -0
  131. package/dist/virtual-list-data.d.ts +13 -0
  132. package/dist/virtual-list-data.d.ts.map +1 -0
  133. package/dist/virtual-list-data.js +87 -0
  134. package/dist/virtual-list.d.ts +14 -0
  135. package/dist/virtual-list.d.ts.map +1 -0
  136. package/dist/virtual-list.js +24 -0
  137. package/dist/visual-interactions.d.ts +16 -0
  138. package/dist/visual-interactions.d.ts.map +1 -0
  139. package/dist/visual-interactions.js +30 -0
  140. package/dist/world-map.d.ts +26 -0
  141. package/dist/world-map.d.ts.map +1 -0
  142. package/dist/world-map.js +81 -0
  143. package/package.json +99 -11
  144. package/styles/critical.css +696 -66
  145. 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,useLayoutEffect,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!==""),pendingFocusRef=useRef(void 0),[focusRequest,setFocusRequest]=useState(0),close=useCallback(()=>{pendingFocusRef.current=void 0,setOpen(!1)},[setOpen]),openAndFocus=useCallback(focus=>{pendingFocusRef.current=focus,setOpen(!0),setFocusRequest(request=>request+1)},[setOpen]);useLayoutEffect(()=>{const focus=pendingFocusRef.current;!open||!focus||(pendingFocusRef.current=void 0,focus())},[focusRequest,open]),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((typeahead,currentItem)=>{const items=getEnabledItems();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]),resetTypeahead=useCallback(()=>{typeaheadRef.current="",typeaheadTimerRef.current&&(clearTimeout(typeaheadTimerRef.current),typeaheadTimerRef.current=void 0)},[]),handleTypeahead=useCallback((event,currentItem)=>{if(!isPrintableKey(event)||event.key===" "&&(!typeaheadRef.current||!open))return!1;event.preventDefault(),typeaheadRef.current+=event.key.toLowerCase(),typeaheadTimerRef.current&&clearTimeout(typeaheadTimerRef.current),typeaheadTimerRef.current=setTimeout(()=>{typeaheadRef.current=""},700);const query=typeaheadRef.current;return openAndFocus(()=>{focusTypeaheadOption(query,currentItem)}),!0},[focusTypeaheadOption,open,openAndFocus]),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,onBlur:event=>{const next=event.relatedTarget,NodeType=event.currentTarget.ownerDocument.defaultView?.Node;NodeType&&next instanceof NodeType&&event.currentTarget.contains(next)||(resetTypeahead(),close())},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:()=>{resetTypeahead(),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))return;event.preventDefault(),resetTypeahead();const{key}=event;openAndFocus(()=>{if(key==="Enter"||key===" "){const items=getEnabledItems();(items.find(item=>item.getAttribute("aria-selected")==="true")??items[0])?.focus();return}focusOption(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(),resetTypeahead(),focusOption(event.key,event.currentTarget))}),role:"option",tabIndex:-1,type:props.type??"button"}),[close,focusOption,handleTypeahead,open,resetTypeahead,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-owner":"react","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]),committedInputEventRef=useRef(null),commitInputValue=useCallback((input,nativeEvent)=>{if(committedInputEventRef.current===nativeEvent)return;committedInputEventRef.current=nativeEvent;const nextValue=syncValue(input);onValueChange?.(nextValue)},[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,event.nativeEvent)}),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,event.nativeEvent)}),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=>{if(event.defaultPrevented)return;const pasted=event.clipboardData.getData("text");if(!pasted)return;event.preventDefault();const input=event.currentTarget,sanitizedValue=sanitizeInputOtpValue(pasted,length,inputMode,pattern);Reflect.set(HTMLInputElement.prototype,"value",sanitizedValue,input),input.setSelectionRange(sanitizedValue.length,sanitizedValue.length),input.dispatchEvent(new Event("input",{bubbles:!0}))}),pattern:props.pattern??pattern,ref:node=>{inputRef.current=node,setRefValue(forwardedInputRef,node)},type:"text",value}),[commitInputValue,disabled,forwardedInputRef,inputMode,invalid,length,pattern,setSelection,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,"data-ui-owner":"react",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};