@santi020k/lumen-react 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hooks.js CHANGED
@@ -1,8 +1,18 @@
1
- /* eslint-disable @eslint-react/no-context-provider, @eslint-react/no-use-context, react-refresh/only-export-components -- The package intentionally keeps public hooks, explicit context providers, and small helper components together in one library module. */
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. */
2
4
  'use client';
3
5
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
- import { coerceThemeBuilderExportFormat, composeClassName, createThemeBuilderTokens, exportThemeBuilderValue, getLumenRichTextShortcut, isLumenRichTextToggleCommand } from '@santi020k/lumen-core';
5
6
  import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
7
+ import { coerceThemeBuilderExportFormat, composeClassName, createThemeBuilderTokens, exportThemeBuilderValue, getLumenRichTextShortcut, isLumenRichTextToggleCommand } 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
+ };
6
16
  const defaultToastDuration = 5000;
7
17
  const defaultToastMax = 5;
8
18
  const closeDelay = 240;
@@ -28,8 +38,7 @@ const isElementVisible = (element) => {
28
38
  const getFocusable = (root) => {
29
39
  if (!root)
30
40
  return [];
31
- return [...root.querySelectorAll(focusableSelector)]
32
- .filter(element => !element.hasAttribute('hidden') && isElementVisible(element));
41
+ return [...root.querySelectorAll(focusableSelector)].filter(element => !element.hasAttribute('hidden') && isElementVisible(element));
33
42
  };
34
43
  const getLoopedIndex = (key, currentIndex, itemCount, forwardKeys) => {
35
44
  const lastIndex = itemCount - 1;
@@ -49,10 +58,10 @@ const useSafeId = (prefix, id) => {
49
58
  const useControllableState = ({ defaultValue, onChange, value }) => {
50
59
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
51
60
  const currentValue = value ?? uncontrolledValue;
52
- const setValue = useCallback((next) => {
53
- const resolvedValue = typeof next === 'function'
54
- ? next(currentValue)
55
- : next;
61
+ const setValue = useCallback(next => {
62
+ const resolvedValue = typeof next === 'function' ?
63
+ next(currentValue) :
64
+ next;
56
65
  if (value === undefined) {
57
66
  setUncontrolledValue(resolvedValue);
58
67
  }
@@ -107,17 +116,25 @@ const useDisclosureController = (options, hasPopup) => {
107
116
  onChange: options.onOpenChange,
108
117
  value: options.open
109
118
  });
110
- const close = useCallback(() => { setOpen(false); }, [setOpen]);
111
- const toggle = useCallback(() => { setOpen(current => !current); }, [setOpen]);
119
+ const close = useCallback(() => {
120
+ setOpen(false);
121
+ }, [setOpen]);
122
+ const toggle = useCallback(() => {
123
+ setOpen(current => !current);
124
+ }, [setOpen]);
112
125
  useOutsideClose(open, [rootRef, triggerRef, panelRef], close);
113
126
  const triggerProps = {
114
127
  'aria-controls': panelId,
115
128
  'aria-expanded': open,
116
129
  'aria-haspopup': hasPopup,
117
130
  'data-ui-trigger': true,
118
- onClick: () => { toggle(); },
119
- onKeyDown: (event) => {
120
- if (event.key !== 'ArrowDown' && event.key !== 'Enter' && event.key !== ' ')
131
+ onClick: () => {
132
+ toggle();
133
+ },
134
+ onKeyDown: event => {
135
+ if (event.key !== 'ArrowDown' &&
136
+ event.key !== 'Enter' &&
137
+ event.key !== ' ')
121
138
  return;
122
139
  event.preventDefault();
123
140
  setOpen(true);
@@ -130,7 +147,7 @@ const useDisclosureController = (options, hasPopup) => {
130
147
  'data-state': open ? 'open' : 'closed',
131
148
  hidden: !open,
132
149
  id: panelId,
133
- onKeyDown: (event) => {
150
+ onKeyDown: event => {
134
151
  if (event.key === 'Escape') {
135
152
  event.preventDefault();
136
153
  close();
@@ -145,7 +162,9 @@ const useDisclosureController = (options, hasPopup) => {
145
162
  return;
146
163
  event.preventDefault();
147
164
  const currentIndex = items.indexOf(document.activeElement);
148
- const nextItem = items[getLoopedIndex(event.key, Math.max(0, currentIndex), items.length, ['ArrowDown'])];
165
+ const nextItem = items[getLoopedIndex(event.key, Math.max(0, currentIndex), items.length, [
166
+ 'ArrowDown'
167
+ ])];
149
168
  nextItem?.focus();
150
169
  },
151
170
  ref: panelRef
@@ -173,7 +192,9 @@ export const useDialog = (options = {}) => {
173
192
  onChange: options.onOpenChange,
174
193
  value: options.open
175
194
  });
176
- const close = useCallback(() => { setOpen(false); }, [setOpen]);
195
+ const close = useCallback(() => {
196
+ setOpen(false);
197
+ }, [setOpen]);
177
198
  useEffect(() => {
178
199
  const dialog = dialogRef.current;
179
200
  if (!dialog)
@@ -196,7 +217,7 @@ export const useDialog = (options = {}) => {
196
217
  'aria-modal': true,
197
218
  'data-ui-alert-dialog': options.alert ? true : undefined,
198
219
  'data-ui-dialog': options.alert ? undefined : true,
199
- onClick: (event) => {
220
+ onClick: event => {
200
221
  if (event.target === dialogRef.current && !options.alert) {
201
222
  close();
202
223
  }
@@ -210,7 +231,9 @@ export const useDialog = (options = {}) => {
210
231
  };
211
232
  const triggerProps = {
212
233
  'aria-haspopup': 'dialog',
213
- onClick: () => { setOpen(true); },
234
+ onClick: () => {
235
+ setOpen(true);
236
+ },
214
237
  ref: triggerRef,
215
238
  type: 'button'
216
239
  };
@@ -240,7 +263,9 @@ export const useContextMenu = ({ defaultOpen = false, id, onOpenChange, open: co
240
263
  onChange: onOpenChange,
241
264
  value: controlledOpen
242
265
  });
243
- const close = useCallback(() => { setOpen(false); }, [setOpen]);
266
+ const close = useCallback(() => {
267
+ setOpen(false);
268
+ }, [setOpen]);
244
269
  const openAt = useCallback((x, y) => {
245
270
  const rect = menuRef.current?.getBoundingClientRect();
246
271
  setPosition(getContextMenuPosition(x, y, rect));
@@ -256,7 +281,8 @@ export const useContextMenu = ({ defaultOpen = false, id, onOpenChange, open: co
256
281
  openAt(event.clientX, event.clientY);
257
282
  },
258
283
  onKeyDown: event => {
259
- if (event.key !== 'ContextMenu' && !(event.shiftKey && event.key === 'F10'))
284
+ if (event.key !== 'ContextMenu' &&
285
+ !(event.shiftKey && event.key === 'F10'))
260
286
  return;
261
287
  event.preventDefault();
262
288
  const rect = event.currentTarget.getBoundingClientRect();
@@ -271,7 +297,8 @@ export const useContextMenu = ({ defaultOpen = false, id, onOpenChange, open: co
271
297
  hidden: !open,
272
298
  id: menuId,
273
299
  onClick: event => {
274
- if (event.target instanceof HTMLElement && event.target.closest('[role="menuitem"]')) {
300
+ if (event.target instanceof HTMLElement &&
301
+ event.target.closest('[role="menuitem"]')) {
275
302
  close();
276
303
  }
277
304
  },
@@ -289,7 +316,9 @@ export const useContextMenu = ({ defaultOpen = false, id, onOpenChange, open: co
289
316
  return;
290
317
  event.preventDefault();
291
318
  const activeElement = typeof document === 'undefined' ? null : document.activeElement;
292
- const currentIndex = activeElement instanceof HTMLElement ? items.indexOf(activeElement) : -1;
319
+ const currentIndex = activeElement instanceof HTMLElement ?
320
+ items.indexOf(activeElement) :
321
+ -1;
293
322
  items[getLoopedIndex(event.key, currentIndex, items.length, ['ArrowDown'])]?.focus();
294
323
  },
295
324
  ref: menuRef,
@@ -332,14 +361,16 @@ export const useTabs = ({ defaultValue = '', id, onValueChange, orientation = 'h
332
361
  'aria-controls': panelId,
333
362
  'aria-selected': selected,
334
363
  id: props.id ?? triggerId,
335
- onClick: composeHandlers(props.onClick, () => { activate(triggerValue); }),
336
- onKeyDown: composeHandlers(props.onKeyDown, (event) => {
364
+ onClick: composeHandlers(props.onClick, () => {
365
+ activate(triggerValue);
366
+ }),
367
+ onKeyDown: composeHandlers(props.onKeyDown, event => {
337
368
  const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End'];
338
369
  if (!keys.includes(event.key))
339
370
  return;
340
- const tabs = rootRef.current
341
- ? [...rootRef.current.querySelectorAll('[role="tab"]')]
342
- : [];
371
+ const tabs = rootRef.current ?
372
+ [...rootRef.current.querySelectorAll('[role="tab"]')] :
373
+ [];
343
374
  if (!tabs.length)
344
375
  return;
345
376
  event.preventDefault();
@@ -385,7 +416,10 @@ export const useTabs = ({ defaultValue = '', id, onValueChange, orientation = 'h
385
416
  };
386
417
  };
387
418
  const normalizeOption = (option) => typeof option === 'string' ? { label: option, value: option } : option;
388
- const isPrintableKey = (event) => event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey;
419
+ const isPrintableKey = (event) => (event.key.length === 1 &&
420
+ !event.altKey &&
421
+ !event.ctrlKey &&
422
+ !event.metaKey);
389
423
  /* eslint-disable complexity -- Select owns the native select, ARIA listbox props, and keyboard/typeahead behavior as one public hook contract. */
390
424
  export const useSelect = ({ defaultValue = '', disabled = false, id, name, onValueChange, options = [], placeholder, required = false, value } = {}) => {
391
425
  const rootRef = useRef(null);
@@ -405,8 +439,12 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
405
439
  const selectedOption = normalizedOptions.find(option => option.value === selectedValue);
406
440
  const triggerText = selectedOption?.label ?? placeholder ?? 'Select an option';
407
441
  const hasSelection = Boolean(selectedOption && selectedValue !== '');
408
- const close = useCallback(() => { setOpen(false); }, [setOpen]);
409
- const openList = useCallback(() => { setOpen(true); }, [setOpen]);
442
+ const close = useCallback(() => {
443
+ setOpen(false);
444
+ }, [setOpen]);
445
+ const openList = useCallback(() => {
446
+ setOpen(true);
447
+ }, [setOpen]);
410
448
  useOutsideClose(open, [rootRef, triggerRef, listRef], close);
411
449
  useEffect(() => () => {
412
450
  if (typeaheadTimerRef.current) {
@@ -416,8 +454,10 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
416
454
  const getEnabledItems = useCallback(() => {
417
455
  if (!listRef.current)
418
456
  return [];
419
- return [...listRef.current.querySelectorAll('[data-ui-select-option]')]
420
- .filter(item => !item.hasAttribute('disabled') && item.getAttribute('aria-disabled') !== 'true');
457
+ return [
458
+ ...listRef.current.querySelectorAll('[data-ui-select-option]')
459
+ ].filter(item => !item.hasAttribute('disabled') &&
460
+ item.getAttribute('aria-disabled') !== 'true');
421
461
  }, []);
422
462
  const focusOption = useCallback((key, currentItem) => {
423
463
  const items = getEnabledItems();
@@ -428,7 +468,9 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
428
468
  if (!current)
429
469
  return;
430
470
  const currentIndex = items.indexOf(current);
431
- const nextItem = items[getLoopedIndex(key, Math.max(0, currentIndex), items.length, ['ArrowDown'])];
471
+ const nextItem = items[getLoopedIndex(key, Math.max(0, currentIndex), items.length, [
472
+ 'ArrowDown'
473
+ ])];
432
474
  nextItem?.focus();
433
475
  }, [getEnabledItems]);
434
476
  const focusTypeaheadOption = useCallback((currentItem) => {
@@ -442,7 +484,10 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
442
484
  return;
443
485
  const currentIndex = items.indexOf(current);
444
486
  const startIndex = Math.max(0, currentIndex + 1);
445
- const orderedItems = [...items.slice(startIndex), ...items.slice(0, startIndex)];
487
+ const orderedItems = [
488
+ ...items.slice(startIndex),
489
+ ...items.slice(0, startIndex)
490
+ ];
446
491
  const nextItem = orderedItems.find(item => item.textContent.trim().toLowerCase().startsWith(typeahead));
447
492
  nextItem?.focus();
448
493
  }, [getEnabledItems]);
@@ -484,7 +529,9 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
484
529
  disabled,
485
530
  id: selectId,
486
531
  name,
487
- onChange: event => { setSelectedValue(event.currentTarget.value); },
532
+ onChange: event => {
533
+ setSelectedValue(event.currentTarget.value);
534
+ },
488
535
  required,
489
536
  tabIndex: -1,
490
537
  value: selectedValue
@@ -500,7 +547,7 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
500
547
  onClick: () => {
501
548
  setOpen(current => !current);
502
549
  },
503
- onKeyDown: (event) => {
550
+ onKeyDown: event => {
504
551
  if (handleTypeahead(event))
505
552
  return;
506
553
  if (event.key === 'Escape') {
@@ -539,8 +586,10 @@ export const useSelect = ({ defaultValue = '', disabled = false, id, name, onVal
539
586
  'data-ui-select-option': true,
540
587
  'data-value': option.value,
541
588
  disabled: option.disabled,
542
- onClick: composeHandlers(props.onClick, () => { selectOption(option.value); }),
543
- onKeyDown: composeHandlers(props.onKeyDown, (event) => {
589
+ onClick: composeHandlers(props.onClick, () => {
590
+ selectOption(option.value);
591
+ }),
592
+ onKeyDown: composeHandlers(props.onKeyDown, event => {
544
593
  if (handleTypeahead(event, event.currentTarget))
545
594
  return;
546
595
  if (event.key === 'Escape') {
@@ -619,9 +668,9 @@ const getValidationMessage = (control, field) => {
619
668
  for (const [validityKey, attributeName] of validityMessageAttributes) {
620
669
  if (!control.validity[validityKey])
621
670
  continue;
622
- return control.getAttribute(attributeName) ??
671
+ return (control.getAttribute(attributeName) ??
623
672
  field?.getAttribute(attributeName) ??
624
- control.validationMessage;
673
+ control.validationMessage);
625
674
  }
626
675
  return control.validationMessage;
627
676
  };
@@ -633,16 +682,19 @@ const getFieldDescriptionId = (element) => {
633
682
  };
634
683
  const syncFieldDescription = (field) => {
635
684
  const controlId = field.dataset.uiFieldControl;
636
- const control = controlId
637
- ? document.getElementById(controlId)
638
- : field.querySelector(fieldControlSelector);
685
+ const control = controlId ?
686
+ document.getElementById(controlId) :
687
+ field.querySelector(fieldControlSelector);
639
688
  if (!(control instanceof HTMLElement))
640
689
  return;
641
690
  const existingIds = control.getAttribute('aria-describedby')?.trim().split(/\s+/) ?? [];
642
691
  const configuredIds = field.dataset.uiFieldDescribedby?.trim().split(/\s+/) ?? [];
643
- const descriptionIds = [...field.querySelectorAll(fieldDescriptionSelector)]
644
- .map(element => getFieldDescriptionId(element));
645
- const describedBy = [...new Set([...existingIds, ...configuredIds, ...descriptionIds].filter(Boolean))];
692
+ const descriptionIds = [
693
+ ...field.querySelectorAll(fieldDescriptionSelector)
694
+ ].map(element => getFieldDescriptionId(element));
695
+ const describedBy = [
696
+ ...new Set([...existingIds, ...configuredIds, ...descriptionIds].filter(Boolean))
697
+ ];
646
698
  if (describedBy.length) {
647
699
  control.setAttribute('aria-describedby', describedBy.join(' '));
648
700
  }
@@ -671,8 +723,9 @@ export const useFormValidation = ({ onInvalid, onValid, onValidate } = {}) => {
671
723
  const getControls = useCallback((form = formRef.current) => {
672
724
  if (!form)
673
725
  return [];
674
- return [...form.querySelectorAll(formControlSelector)]
675
- .filter(control => control.form === form && !control.disabled);
726
+ return [
727
+ ...form.querySelectorAll(formControlSelector)
728
+ ].filter(control => control.form === form && !control.disabled);
676
729
  }, []);
677
730
  const validateControl = useCallback((control, form = formRef.current ?? control.form) => {
678
731
  if (!form)
@@ -684,7 +737,9 @@ export const useFormValidation = ({ onInvalid, onValid, onValidate } = {}) => {
684
737
  }));
685
738
  onValidate?.(validateDetail);
686
739
  const invalid = !control.validity.valid;
687
- const message = invalid ? getValidationMessage(control, getFieldRoot(control)) : '';
740
+ const message = invalid ?
741
+ getValidationMessage(control, getFieldRoot(control)) :
742
+ '';
688
743
  setFieldValidity(control, invalid, message);
689
744
  return !invalid;
690
745
  }, [onValidate, setFieldValidity]);
@@ -713,17 +768,17 @@ export const useFormValidation = ({ onInvalid, onValid, onValidate } = {}) => {
713
768
  const formProps = {
714
769
  'data-ui-form': true,
715
770
  noValidate: true,
716
- onBlur: (event) => {
771
+ onBlur: event => {
717
772
  const form = event.currentTarget;
718
773
  syncFields(form);
719
774
  if (!isNativeFormControl(event.target) || event.target.form !== form)
720
775
  return;
721
776
  const valid = validateControl(event.target, form);
722
- emitState(valid ? 'ui:valid' : 'ui:invalid', valid
723
- ? { control: event.target, form }
724
- : { control: event.target, controls: [event.target], form });
777
+ emitState(valid ? 'ui:valid' : 'ui:invalid', valid ?
778
+ { control: event.target, form } :
779
+ { control: event.target, controls: [event.target], form });
725
780
  },
726
- onSubmit: (event) => {
781
+ onSubmit: event => {
727
782
  const form = event.currentTarget;
728
783
  syncFields(form);
729
784
  const invalidControls = validateForm(form);
@@ -732,7 +787,11 @@ export const useFormValidation = ({ onInvalid, onValid, onValidate } = {}) => {
732
787
  const firstInvalid = invalidControls[0];
733
788
  if (!firstInvalid)
734
789
  return;
735
- emitState('ui:invalid', { control: firstInvalid, controls: invalidControls, form });
790
+ emitState('ui:invalid', {
791
+ control: firstInvalid,
792
+ controls: invalidControls,
793
+ form
794
+ });
736
795
  firstInvalid.focus({ preventScroll: true });
737
796
  firstInvalid.reportValidity();
738
797
  return;
@@ -756,20 +815,24 @@ const calendarMonthPattern = /^\d{4}-\d{2}$/;
756
815
  const parseCalendarDate = (value) => {
757
816
  if (!value || !calendarDatePattern.test(value))
758
817
  return null;
759
- const [year = Number.NaN, month = Number.NaN, day = Number.NaN] = value.split('-').map(Number);
818
+ const [year = Number.NaN, month = Number.NaN, day = Number.NaN] = value
819
+ .split('-')
820
+ .map(Number);
760
821
  const date = new Date(Date.UTC(year, month - 1, day));
761
822
  return date.getUTCFullYear() === year &&
762
823
  date.getUTCMonth() === month - 1 &&
763
- date.getUTCDate() === day
764
- ? date
765
- : null;
824
+ date.getUTCDate() === day ?
825
+ date :
826
+ null;
766
827
  };
767
828
  const parseCalendarMonth = (value) => {
768
829
  if (!value || !calendarMonthPattern.test(value))
769
830
  return null;
770
831
  const [year = Number.NaN, month = Number.NaN] = value.split('-').map(Number);
771
832
  const date = new Date(Date.UTC(year, month - 1, 1));
772
- return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 ? date : null;
833
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 ?
834
+ date :
835
+ null;
773
836
  };
774
837
  const formatCalendarDate = (date) => date.toISOString().slice(0, 10);
775
838
  const formatCalendarMonth = (date) => date.toISOString().slice(0, 7);
@@ -778,13 +841,18 @@ const addCalendarDays = (date, days) => new Date(Date.UTC(date.getUTCFullYear(),
778
841
  const getCalendarDaysInMonth = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
779
842
  const addCalendarMonths = (date, months) => {
780
843
  const targetMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
781
- return new Date(Date.UTC(targetMonth.getUTCFullYear(), targetMonth.getUTCMonth(), Math.min(date.getUTCDate(), getCalendarDaysInMonth(targetMonth))));
844
+ const day = Math.min(date.getUTCDate(), getCalendarDaysInMonth(targetMonth));
845
+ return new Date(Date.UTC(targetMonth.getUTCFullYear(), targetMonth.getUTCMonth(), day));
782
846
  };
783
847
  const getCalendarToday = () => {
784
848
  const today = new Date();
785
849
  return new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()));
786
850
  };
787
- const compareCalendarDates = (date, other) => other ? formatCalendarDate(date).localeCompare(formatCalendarDate(other)) : 0;
851
+ const compareCalendarDates = (date, other) => {
852
+ if (!other)
853
+ return 0;
854
+ return formatCalendarDate(date).localeCompare(formatCalendarDate(other));
855
+ };
788
856
  const isCalendarDateDisabled = (disabled, date, min, max) => disabled ||
789
857
  compareCalendarDates(date, min) < 0 ||
790
858
  compareCalendarDates(date, max) > 0;
@@ -807,7 +875,7 @@ const getCalendarLocale = (locale) => {
807
875
  }
808
876
  return 'en';
809
877
  };
810
- const coerceCalendarDate = (value) => value instanceof Date ? value : parseCalendarDate(value);
878
+ const coerceCalendarDate = (value) => (value instanceof Date ? value : parseCalendarDate(value));
811
879
  const getCalendarFocusDate = (disabled, month, min, max, selectedDate, requestedDate) => {
812
880
  const today = getCalendarToday();
813
881
  const candidates = [requestedDate, selectedDate, today, month];
@@ -830,12 +898,16 @@ export const useCalendar = ({ defaultValue, disabled = false, locale, max, min,
830
898
  const generatedId = useId();
831
899
  const calendarId = `ui-calendar-${generatedId}`;
832
900
  const labelId = `${calendarId}-label`;
833
- const minDate = useMemo(() => parseCalendarDate(min), [min]);
834
- const maxDate = useMemo(() => parseCalendarDate(max), [max]);
901
+ const minDate = parseCalendarDate(min);
902
+ const maxDate = parseCalendarDate(max);
835
903
  const defaultDate = parseCalendarDate(defaultValue);
836
904
  const controlledDate = parseCalendarDate(controlledValue);
837
905
  const [uncontrolledValue, setUncontrolledValue] = useState(() => defaultDate ? formatCalendarDate(defaultDate) : '');
838
- const selectedValue = controlledValue === undefined ? uncontrolledValue : controlledDate ? formatCalendarDate(controlledDate) : '';
906
+ const selectedValue = controlledValue === undefined ?
907
+ uncontrolledValue :
908
+ controlledDate ?
909
+ formatCalendarDate(controlledDate) :
910
+ '';
839
911
  const selectedDate = parseCalendarDate(selectedValue);
840
912
  const initialMonth = parseCalendarMonth(month) ??
841
913
  (selectedDate ? startOfCalendarMonth(selectedDate) : null) ??
@@ -883,16 +955,25 @@ export const useCalendar = ({ defaultValue, disabled = false, locale, max, min,
883
955
  today: todayIso === dateIso
884
956
  };
885
957
  }));
886
- }, [dayFormatter, disabled, focusIso, maxDate, minDate, selectedIso, todayIso, visibleMonth]);
887
- const focusCalendarDate = useCallback((dateInput) => {
958
+ }, [
959
+ dayFormatter,
960
+ disabled,
961
+ focusIso,
962
+ maxDate,
963
+ minDate,
964
+ selectedIso,
965
+ todayIso,
966
+ visibleMonth
967
+ ]);
968
+ const focusCalendarDate = dateInput => {
888
969
  const date = coerceCalendarDate(dateInput);
889
970
  if (!date)
890
971
  return;
891
972
  const nextDate = clampCalendarDate(date, minDate, maxDate);
892
973
  setVisibleMonthValue(formatCalendarMonth(startOfCalendarMonth(nextDate)));
893
974
  setFocusValue(formatCalendarDate(nextDate));
894
- }, [maxDate, minDate]);
895
- const selectDate = useCallback((dateInput) => {
975
+ };
976
+ const selectDate = dateInput => {
896
977
  if (disabled)
897
978
  return;
898
979
  const date = coerceCalendarDate(dateInput);
@@ -908,8 +989,8 @@ export const useCalendar = ({ defaultValue, disabled = false, locale, max, min,
908
989
  setVisibleMonthValue(formatCalendarMonth(startOfCalendarMonth(nextDate)));
909
990
  setFocusValue(nextValue);
910
991
  onValueChange?.(nextValue);
911
- }, [controlledValue, disabled, maxDate, minDate, onValueChange]);
912
- const moveFocus = useCallback((currentDate, key) => {
992
+ };
993
+ const moveFocus = (currentDate, key) => {
913
994
  const column = (currentDate.getUTCDay() + 6) % 7;
914
995
  const keyOffsets = {
915
996
  ArrowDown: 7,
@@ -927,12 +1008,12 @@ export const useCalendar = ({ defaultValue, disabled = false, locale, max, min,
927
1008
  if (offset !== undefined) {
928
1009
  focusCalendarDate(addCalendarDays(currentDate, offset));
929
1010
  }
930
- }, [focusCalendarDate]);
1011
+ };
931
1012
  const previousMonthLastDay = addCalendarDays(visibleMonth, -1);
932
1013
  const nextMonthFirstDay = addCalendarMonths(visibleMonth, 1);
933
1014
  const previousDisabled = disabled || compareCalendarDates(previousMonthLastDay, minDate) < 0;
934
1015
  const nextDisabled = disabled || compareCalendarDates(nextMonthFirstDay, maxDate) > 0;
935
- const getDayProps = useCallback((day, props = {}) => ({
1016
+ const getDayProps = (day, props = {}) => ({
936
1017
  ...props,
937
1018
  'aria-disabled': day.disabled ? true : undefined,
938
1019
  'aria-label': props['aria-label'] ?? day.label,
@@ -973,7 +1054,7 @@ export const useCalendar = ({ defaultValue, disabled = false, locale, max, min,
973
1054
  }),
974
1055
  role: 'gridcell',
975
1056
  tabIndex: props.tabIndex ?? day.tabIndex
976
- }), [moveFocus, selectDate]);
1057
+ });
977
1058
  return {
978
1059
  focusDate: focusCalendarDate,
979
1060
  getDayProps,
@@ -1043,16 +1124,18 @@ export const useCalendar = ({ defaultValue, disabled = false, locale, max, min,
1043
1124
  /* eslint-enable @stylistic/padding-line-between-statements, complexity, no-nested-ternary */
1044
1125
  const defaultInputOtpLength = 6;
1045
1126
  const defaultInputOtpPattern = '[0-9]*';
1046
- const normalizeInputOtpLength = (length) => Math.max(1, Math.floor(Number(length) || defaultInputOtpLength));
1047
- const isNumericInputOtp = (inputMode, pattern) => inputMode === 'numeric' || pattern === defaultInputOtpPattern;
1127
+ const coerceInputOtpLength = (length) => Number(length) || defaultInputOtpLength;
1128
+ const clampInputOtpLength = (length) => Math.max(1, Math.floor(length));
1129
+ const normalizeInputOtpLength = (length) => (clampInputOtpLength(coerceInputOtpLength(length)));
1130
+ const isNumericInputOtp = (inputMode, pattern) => (inputMode === 'numeric' || pattern === defaultInputOtpPattern);
1048
1131
  const sanitizeInputOtpValue = (value, length, inputMode, pattern) => {
1049
- const normalized = isNumericInputOtp(inputMode, pattern)
1050
- ? value.replaceAll(/\D/g, '')
1051
- : value.replaceAll(/\s/g, '');
1132
+ const normalized = isNumericInputOtp(inputMode, pattern) ?
1133
+ value.replaceAll(/\D/g, '') :
1134
+ value.replaceAll(/\s/g, '');
1052
1135
  return normalized.slice(0, length);
1053
1136
  };
1054
1137
  const getInputOtpActiveIndex = (length, value, selectionStart) => Math.min(length - 1, Math.max(0, selectionStart ?? value.length));
1055
- export const useInputOTP = ({ defaultValue, disabled = false, inputMode = 'numeric', invalid = false, length: lengthOption, onValueChange, pattern = defaultInputOtpPattern, value: controlledValue } = {}) => {
1138
+ export const useInputOTP = ({ defaultValue, disabled = false, inputMode = 'numeric', inputRef: forwardedInputRef, invalid = false, length: lengthOption, onValueChange, pattern = defaultInputOtpPattern, value: controlledValue } = {}) => {
1056
1139
  const length = normalizeInputOtpLength(lengthOption);
1057
1140
  const rootRef = useRef(null);
1058
1141
  const inputRef = useRef(null);
@@ -1074,14 +1157,14 @@ export const useInputOTP = ({ defaultValue, disabled = false, inputMode = 'numer
1074
1157
  setActiveIndex(getInputOtpActiveIndex(length, nextValue, input.selectionStart));
1075
1158
  return nextValue;
1076
1159
  }, [controlledValue, inputMode, length, pattern, value]);
1077
- const setSelection = useCallback((index) => {
1160
+ const setSelection = useCallback(index => {
1078
1161
  const input = inputRef.current;
1079
1162
  const position = Math.min(value.length, Math.max(0, index));
1080
1163
  setActiveIndex(getInputOtpActiveIndex(length, value, position));
1081
1164
  input?.focus({ preventScroll: true });
1082
1165
  input?.setSelectionRange(position, position);
1083
1166
  }, [length, value]);
1084
- const setValue = useCallback((nextValue) => {
1167
+ const setValue = useCallback(nextValue => {
1085
1168
  const sanitizedValue = sanitizeInputOtpValue(nextValue, length, inputMode, pattern);
1086
1169
  const input = inputRef.current;
1087
1170
  if (controlledValue === undefined) {
@@ -1146,12 +1229,14 @@ export const useInputOTP = ({ defaultValue, disabled = false, inputMode = 'numer
1146
1229
  onKeyDown: composeHandlers(props.onKeyDown, event => {
1147
1230
  if (event.key === 'ArrowLeft') {
1148
1231
  event.preventDefault();
1149
- setSelection((event.currentTarget.selectionStart ?? event.currentTarget.value.length) - 1);
1232
+ setSelection((event.currentTarget.selectionStart ??
1233
+ event.currentTarget.value.length) - 1);
1150
1234
  return;
1151
1235
  }
1152
1236
  if (event.key === 'ArrowRight') {
1153
1237
  event.preventDefault();
1154
- setSelection((event.currentTarget.selectionStart ?? event.currentTarget.value.length) + 1);
1238
+ setSelection((event.currentTarget.selectionStart ??
1239
+ event.currentTarget.value.length) + 1);
1155
1240
  return;
1156
1241
  }
1157
1242
  if (event.key === 'Home') {
@@ -1175,10 +1260,25 @@ export const useInputOTP = ({ defaultValue, disabled = false, inputMode = 'numer
1175
1260
  setValue(pasted);
1176
1261
  }),
1177
1262
  pattern: props.pattern ?? pattern,
1178
- ref: inputRef,
1263
+ ref: node => {
1264
+ inputRef.current = node;
1265
+ setRefValue(forwardedInputRef, node);
1266
+ },
1179
1267
  type: 'text',
1180
1268
  value
1181
- }), [commitInputValue, disabled, inputMode, invalid, length, pattern, setSelection, setValue, syncValue, value]);
1269
+ }), [
1270
+ commitInputValue,
1271
+ disabled,
1272
+ forwardedInputRef,
1273
+ inputMode,
1274
+ invalid,
1275
+ length,
1276
+ pattern,
1277
+ setSelection,
1278
+ setValue,
1279
+ syncValue,
1280
+ value
1281
+ ]);
1182
1282
  const getSegmentChar = useCallback(index => value[index] ?? '\u00a0', [value]);
1183
1283
  const getSegmentProps = useCallback((index, props = {}) => ({
1184
1284
  ...props,
@@ -1224,11 +1324,10 @@ const getDateRangeRoot = (control, fallback) => control.closest('[data-ui-date-r
1224
1324
  const getDateRangeInputs = (root, start, end) => {
1225
1325
  if (start && end && start !== end)
1226
1326
  return [start, end];
1227
- const inputs = root ? [...root.querySelectorAll('input[type="date"]')] : [];
1228
- return [
1229
- start ?? inputs[0] ?? null,
1230
- end ?? inputs.at(-1) ?? null
1231
- ];
1327
+ const inputs = root ?
1328
+ [...root.querySelectorAll('input[type="date"]')] :
1329
+ [];
1330
+ return [start ?? inputs[0] ?? null, end ?? inputs.at(-1) ?? null];
1232
1331
  };
1233
1332
  const syncDateInputConstraint = (input, property, value) => {
1234
1333
  if (value) {
@@ -1304,9 +1403,9 @@ const getRichTextChangeDetail = (root) => {
1304
1403
  const executeRichTextDocumentCommand = (commandDocument, command, value) => {
1305
1404
  if (typeof commandDocument?.execCommand !== 'function')
1306
1405
  return false;
1307
- return value === undefined
1308
- ? commandDocument.execCommand(command)
1309
- : commandDocument.execCommand(command, false, value);
1406
+ return value === undefined ?
1407
+ commandDocument.execCommand(command) :
1408
+ commandDocument.execCommand(command, false, value);
1310
1409
  };
1311
1410
  // eslint-disable-next-line complexity -- Command-state normalization covers toggle and value-bearing controls together.
1312
1411
  const syncRichTextCommandStates = (root) => {
@@ -1322,7 +1421,8 @@ const syncRichTextCommandStates = (root) => {
1322
1421
  control.setAttribute('aria-pressed', String(active));
1323
1422
  }
1324
1423
  else if (command === 'formatBlock' && value) {
1325
- const currentValue = commandDocument.queryCommandValue?.(command)
1424
+ const currentValue = commandDocument
1425
+ .queryCommandValue?.(command)
1326
1426
  .replaceAll(/[<>]/g, '')
1327
1427
  .toLowerCase();
1328
1428
  active = currentValue === value.replaceAll(/[<>]/g, '').toLowerCase();
@@ -1347,9 +1447,9 @@ export const useRichTextEditor = ({ onChange, onCommand } = {}) => {
1347
1447
  const executeCommand = useCallback((command, root = rootRef.current, value) => {
1348
1448
  if (!command)
1349
1449
  return false;
1350
- const commandDocument = typeof document === 'undefined'
1351
- ? undefined
1352
- : document;
1450
+ const commandDocument = typeof document === 'undefined' ?
1451
+ undefined :
1452
+ document;
1353
1453
  const executed = executeRichTextDocumentCommand(commandDocument, command, value);
1354
1454
  const detail = {
1355
1455
  command,
@@ -1439,7 +1539,9 @@ export const useSchedule = ({ onChange } = {}) => {
1439
1539
  }
1440
1540
  }),
1441
1541
  onDragStart: composeHandlers(props.onDragStart, event => {
1442
- const transferValue = eventId || event.currentTarget.id || event.currentTarget.textContent.trim();
1542
+ const transferValue = eventId ||
1543
+ event.currentTarget.id ||
1544
+ event.currentTarget.textContent.trim();
1443
1545
  const root = getScheduleRoot(event.currentTarget, rootRef.current);
1444
1546
  event.dataTransfer.setData('text/plain', transferValue);
1445
1547
  if (root) {
@@ -1461,10 +1563,11 @@ export const useSchedule = ({ onChange } = {}) => {
1461
1563
  event.preventDefault();
1462
1564
  delete event.currentTarget.dataset.state;
1463
1565
  const draggedId = event.dataTransfer.getData('text/plain');
1464
- const dragged = typeof document !== 'undefined' && draggedId
1465
- ? document.getElementById(draggedId)
1466
- : null;
1467
- if (typeof HTMLElement !== 'undefined' && dragged instanceof HTMLElement) {
1566
+ const dragged = typeof document !== 'undefined' && draggedId ?
1567
+ document.getElementById(draggedId) :
1568
+ null;
1569
+ if (typeof HTMLElement !== 'undefined' &&
1570
+ dragged instanceof HTMLElement) {
1468
1571
  event.currentTarget.append(dragged);
1469
1572
  }
1470
1573
  emitChange({
@@ -1484,9 +1587,15 @@ export const useSchedule = ({ onChange } = {}) => {
1484
1587
  rootRef
1485
1588
  };
1486
1589
  };
1487
- /* eslint-disable @stylistic/padding-line-between-statements, @eslint-react/set-state-in-effect, @typescript-eslint/prefer-optional-chain, complexity, no-nested-ternary -- Resizable mirrors Astro's compact pane sizing runtime. */
1590
+ /* eslint-disable @stylistic/padding-line-between-statements, @eslint-react/set-state-in-effect */
1591
+ /* eslint-disable @typescript-eslint/prefer-optional-chain, complexity, no-nested-ternary */
1592
+ /* Resizable mirrors Astro's compact pane sizing runtime. */
1488
1593
  const parseResizableNumberList = (value, count, fallback) => {
1489
- const values = Array.isArray(value) ? value : value === undefined ? [] : [value];
1594
+ const values = Array.isArray(value) ?
1595
+ value :
1596
+ value === undefined ?
1597
+ [] :
1598
+ [value];
1490
1599
  return Array.from({ length: count }, (_, index) => values[index] ?? values[0] ?? fallback);
1491
1600
  };
1492
1601
  const normalizeResizableSizes = (sizes, count) => {
@@ -1500,7 +1609,11 @@ const normalizeResizableSizes = (sizes, count) => {
1500
1609
  return Array.from({ length: count }, () => fallbackSize);
1501
1610
  return usableSizes.map(size => (size / total) * 100);
1502
1611
  };
1503
- const serializeResizableSize = (value) => Array.isArray(value) ? value.join(',') : value === undefined ? undefined : String(value);
1612
+ const serializeResizableSize = (value) => Array.isArray(value) ?
1613
+ value.join(',') :
1614
+ value === undefined ?
1615
+ undefined :
1616
+ String(value);
1504
1617
  export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize, minSize, onSizesChange, panelCount: panelCountOption = 2, resetOnDoubleClick = true } = {}) => {
1505
1618
  const panelCount = Math.max(0, Math.floor(panelCountOption));
1506
1619
  const rootRef = useRef(null);
@@ -1565,9 +1678,9 @@ export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize,
1565
1678
  }),
1566
1679
  onKeyDown: composeHandlers(props.onKeyDown, event => {
1567
1680
  const step = event.shiftKey ? 10 : 2;
1568
- const keyDeltas = direction === 'horizontal'
1569
- ? { ArrowLeft: -step, ArrowRight: step }
1570
- : { ArrowDown: step, ArrowUp: -step };
1681
+ const keyDeltas = direction === 'horizontal' ?
1682
+ { ArrowLeft: -step, ArrowRight: step } :
1683
+ { ArrowDown: step, ArrowUp: -step };
1571
1684
  if (event.key === 'Home') {
1572
1685
  event.preventDefault();
1573
1686
  resizePair(index, minSizes[index] ?? 0);
@@ -1602,7 +1715,9 @@ export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize,
1602
1715
  }),
1603
1716
  onPointerMove: composeHandlers(props.onPointerMove, event => {
1604
1717
  const drag = dragRef.current;
1605
- if (!drag || drag.index !== index || event.currentTarget.dataset.active !== 'true')
1718
+ if (!drag ||
1719
+ drag.index !== index ||
1720
+ event.currentTarget.dataset.active !== 'true')
1606
1721
  return;
1607
1722
  const delta = ((event[axis] - drag.startPosition) / drag.containerSize) * 100;
1608
1723
  resizePair(index, drag.startSize + delta);
@@ -1708,7 +1823,10 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1708
1823
  currentSecondaryColor
1709
1824
  ]);
1710
1825
  const exportValue = useMemo(() => exportThemeBuilderValue(result.tokens, result.scheme, currentExportFormat), [currentExportFormat, result.scheme, result.tokens]);
1711
- const previewStyle = useMemo(() => Object.fromEntries(Object.entries(result.tokens).map(([token, value]) => [`--${token}`, value])), [result.tokens]);
1826
+ const previewStyle = useMemo(() => Object.fromEntries(Object.entries(result.tokens).map(([token, value]) => [
1827
+ `--${token}`,
1828
+ value
1829
+ ])), [result.tokens]);
1712
1830
  useEffect(() => {
1713
1831
  onThemeChange?.(result);
1714
1832
  }, [onThemeChange, result]);
@@ -1729,21 +1847,27 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1729
1847
  ...props,
1730
1848
  'aria-pressed': currentMode === nextMode,
1731
1849
  'data-ui-theme-mode': nextMode,
1732
- onClick: composeHandlers(props.onClick, () => { setMode(nextMode); }),
1850
+ onClick: composeHandlers(props.onClick, () => {
1851
+ setMode(nextMode);
1852
+ }),
1733
1853
  type: props.type ?? 'button'
1734
1854
  }), [currentMode, setMode]);
1735
1855
  const getSchemeProps = useCallback((nextScheme, props = {}) => ({
1736
1856
  ...props,
1737
1857
  'aria-pressed': currentScheme === nextScheme,
1738
1858
  'data-ui-theme-scheme': nextScheme,
1739
- onClick: composeHandlers(props.onClick, () => { setScheme(nextScheme); }),
1859
+ onClick: composeHandlers(props.onClick, () => {
1860
+ setScheme(nextScheme);
1861
+ }),
1740
1862
  type: props.type ?? 'button'
1741
1863
  }), [currentScheme, setScheme]);
1742
1864
  const getExportFormatProps = useCallback((format, props = {}) => ({
1743
1865
  ...props,
1744
1866
  'aria-pressed': currentExportFormat === format,
1745
1867
  'data-ui-theme-export-format': format,
1746
- onClick: composeHandlers(props.onClick, () => { setExportFormat(coerceThemeBuilderExportFormat(format)); }),
1868
+ onClick: composeHandlers(props.onClick, () => {
1869
+ setExportFormat(coerceThemeBuilderExportFormat(format));
1870
+ }),
1747
1871
  type: props.type ?? 'button'
1748
1872
  }), [currentExportFormat, setExportFormat]);
1749
1873
  return {
@@ -1752,7 +1876,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1752
1876
  'data-ui-theme-accent-hue': true,
1753
1877
  max: 359,
1754
1878
  min: 0,
1755
- onChange: event => { setAccentHue(Number(event.currentTarget.value)); },
1879
+ onChange: event => {
1880
+ setAccentHue(Number(event.currentTarget.value));
1881
+ },
1756
1882
  type: 'range',
1757
1883
  value: currentAccentHue
1758
1884
  },
@@ -1773,7 +1899,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1773
1899
  'data-ui-theme-brand-hue': true,
1774
1900
  max: 359,
1775
1901
  min: 0,
1776
- onChange: event => { setHue(Number(event.currentTarget.value)); },
1902
+ onChange: event => {
1903
+ setHue(Number(event.currentTarget.value));
1904
+ },
1777
1905
  type: 'range',
1778
1906
  value: currentHue
1779
1907
  },
@@ -1785,7 +1913,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1785
1913
  previewStyle,
1786
1914
  primaryColorProps: {
1787
1915
  'data-ui-theme-primary-color': true,
1788
- onChange: event => { setPrimaryColor(event.currentTarget.value); },
1916
+ onChange: event => {
1917
+ setPrimaryColor(event.currentTarget.value);
1918
+ },
1789
1919
  type: 'color',
1790
1920
  value: currentPrimaryColor
1791
1921
  },
@@ -1794,7 +1924,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1794
1924
  },
1795
1925
  secondaryColorProps: {
1796
1926
  'data-ui-theme-secondary-color': true,
1797
- onChange: event => { setSecondaryColor(event.currentTarget.value); },
1927
+ onChange: event => {
1928
+ setSecondaryColor(event.currentTarget.value);
1929
+ },
1798
1930
  type: 'color',
1799
1931
  value: currentSecondaryColor
1800
1932
  },
@@ -1807,7 +1939,7 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1807
1939
  setSecondaryColor
1808
1940
  };
1809
1941
  };
1810
- export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange, open, } = {}) => {
1942
+ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange, open } = {}) => {
1811
1943
  const tooltipId = useSafeId('ui-tooltip', id);
1812
1944
  const timerRef = useRef(undefined);
1813
1945
  const [isOpen, setIsOpen] = useControllableState({
@@ -1825,7 +1957,9 @@ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange,
1825
1957
  if (timerRef.current) {
1826
1958
  clearTimeout(timerRef.current);
1827
1959
  }
1828
- timerRef.current = setTimeout(() => { setIsOpen(true); }, nextDelay);
1960
+ timerRef.current = setTimeout(() => {
1961
+ setIsOpen(true);
1962
+ }, nextDelay);
1829
1963
  }, [setIsOpen]);
1830
1964
  useEffect(() => () => {
1831
1965
  if (timerRef.current) {
@@ -1838,13 +1972,17 @@ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange,
1838
1972
  rootProps: {
1839
1973
  'aria-describedby': isOpen ? tooltipId : undefined,
1840
1974
  'data-ui-tooltip': true,
1841
- onFocus: () => { scheduleOpen(0); },
1842
- onKeyDown: (event) => {
1975
+ onFocus: () => {
1976
+ scheduleOpen(0);
1977
+ },
1978
+ onKeyDown: event => {
1843
1979
  if (event.key === 'Escape') {
1844
1980
  close();
1845
1981
  }
1846
1982
  },
1847
- onMouseEnter: () => { scheduleOpen(delay); },
1983
+ onMouseEnter: () => {
1984
+ scheduleOpen(delay);
1985
+ },
1848
1986
  onMouseLeave: close
1849
1987
  },
1850
1988
  setOpen: setIsOpen,
@@ -1856,7 +1994,8 @@ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange,
1856
1994
  };
1857
1995
  };
1858
1996
  const createToastId = () => {
1859
- if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
1997
+ if (typeof crypto !== 'undefined' &&
1998
+ typeof crypto.randomUUID === 'function') {
1860
1999
  return `ui-toast-${crypto.randomUUID()}`;
1861
2000
  }
1862
2001
  return `ui-toast-${Math.random().toString(36).slice(2)}`;
@@ -1894,9 +2033,9 @@ export const ToastProvider = ({ children, maxCount = defaultToastMax, placement
1894
2033
  const record = createToastRecord(detail, placement);
1895
2034
  const stackMax = detail.max ?? maxCount;
1896
2035
  setToasts(current => {
1897
- const next = current.some(toast => toast.id === record.id)
1898
- ? current.map(toast => toast.id === record.id ? record : toast)
1899
- : [...current, record];
2036
+ const next = current.some(toast => toast.id === record.id) ?
2037
+ current.map(toast => (toast.id === record.id ? record : toast)) :
2038
+ [...current, record];
1900
2039
  const samePlacement = next.filter(toast => toast.placement === record.placement);
1901
2040
  const staleIds = samePlacement
1902
2041
  .slice(0, Math.max(0, samePlacement.length - stackMax))
@@ -1926,7 +2065,9 @@ export const ToastProvider = ({ children, maxCount = defaultToastMax, placement
1926
2065
  toasts,
1927
2066
  update
1928
2067
  }), [create, dismiss, toasts, update]);
1929
- const placements = [...new Set([placement, ...toasts.map(toast => toast.placement)])];
2068
+ const placements = [
2069
+ ...new Set([placement, ...toasts.map(toast => toast.placement)])
2070
+ ];
1930
2071
  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)))] }));
1931
2072
  };
1932
2073
  export const useToast = () => {
@@ -1936,7 +2077,9 @@ export const useToast = () => {
1936
2077
  }
1937
2078
  return api;
1938
2079
  };
1939
- const ToastViewport = ({ maxCount, placement, removeToast, toasts }) => (_jsx("div", { "aria-atomic": "false", "aria-label": "Notifications", "aria-live": "polite", className: "ui-sonner", "data-placement": placement, "data-ui-sonner": true, "data-ui-toast-max": maxCount, children: toasts.map(toast => (_jsx(ToastItem, { onDismiss: () => { removeToast(toast.id); }, toast: toast }, toast.id))) }));
2080
+ const ToastViewport = ({ maxCount, placement, removeToast, toasts }) => (_jsx("div", { "aria-atomic": "false", "aria-label": "Notifications", "aria-live": "polite", className: "ui-sonner", "data-placement": placement, "data-ui-sonner": true, "data-ui-toast-max": maxCount, children: toasts.map(toast => (_jsx(ToastItem, { onDismiss: () => {
2081
+ removeToast(toast.id);
2082
+ }, toast: toast }, toast.id))) }));
1940
2083
  const ToastItem = ({ onDismiss, toast }) => {
1941
2084
  const toastRef = useRef(null);
1942
2085
  const timerRef = useRef(undefined);
@@ -1954,7 +2097,9 @@ const ToastItem = ({ onDismiss, toast }) => {
1954
2097
  if (!Number.isFinite(remaining) || remaining <= 0)
1955
2098
  return;
1956
2099
  startedAtRef.current = Date.now();
1957
- timerRef.current = setTimeout(() => { dismiss(toast.id); }, remaining);
2100
+ timerRef.current = setTimeout(() => {
2101
+ dismiss(toast.id);
2102
+ }, remaining);
1958
2103
  }, [clearTimer, dismiss, toast.id]);
1959
2104
  const pauseTimer = useCallback(() => {
1960
2105
  clearTimer();
@@ -1973,31 +2118,35 @@ const ToastItem = ({ onDismiss, toast }) => {
1973
2118
  useEffect(() => {
1974
2119
  if (!toast.open) {
1975
2120
  const timer = setTimeout(onDismiss, closeDelay);
1976
- return () => { clearTimeout(timer); };
2121
+ return () => {
2122
+ clearTimeout(timer);
2123
+ };
1977
2124
  }
1978
2125
  }, [onDismiss, toast.open]);
1979
2126
  const action = toast.action;
1980
2127
  const description = toast.description ?? '';
1981
2128
  const variantClass = getToastVariantClass(toast.variant);
1982
- 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) => {
2129
+ 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 => {
1983
2130
  if (event.key !== 'Escape')
1984
2131
  return;
1985
2132
  event.preventDefault();
1986
2133
  dismiss(toast.id);
1987
- }, 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: "ui-button ui-button--secondary ui-button--sm ui-toast__action", onClick: (event) => {
2134
+ }, 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 => {
1988
2135
  action.onClick?.(event, toastRef.current);
1989
2136
  toastRef.current?.dispatchEvent(new CustomEvent(action.event ?? 'ui:toast-action', {
1990
2137
  bubbles: true,
1991
2138
  detail: { id: toast.id, value: action.value }
1992
2139
  }));
1993
2140
  dismiss(toast.id);
1994
- }, type: "button", children: action.label })), _jsx("button", { "aria-label": "Dismiss notification", className: "ui-toast__dismiss", onClick: () => { dismiss(toast.id); }, type: "button", children: "Dismiss" })] }));
2141
+ }, type: "button", children: action.label })), _jsx("button", { "aria-label": "Dismiss notification", className: "ui-toast__dismiss", onClick: () => {
2142
+ dismiss(toast.id);
2143
+ }, type: "button", children: "Dismiss" })] }));
1995
2144
  };
1996
2145
  export const useThemeToggle = (defaultTheme = 'light') => {
1997
2146
  const [theme, setTheme] = useState(() => {
1998
2147
  if (typeof window !== 'undefined') {
1999
- return localStorage.getItem('theme') ??
2000
- (document.documentElement.classList.contains('dark') ? 'dark' : 'light');
2148
+ return (localStorage.getItem('theme') ??
2149
+ (document.documentElement.classList.contains('dark') ? 'dark' : 'light'));
2001
2150
  }
2002
2151
  return defaultTheme;
2003
2152
  });
@@ -2036,14 +2185,15 @@ export const useThemeToggle = (defaultTheme = 'light') => {
2036
2185
  pointerEvents: 'none',
2037
2186
  backgroundColor: oldBg,
2038
2187
  clipPath: `circle(${maxRadius}px at ${x}px ${y}px)`,
2039
- willChange: 'clip-path',
2188
+ willChange: 'clip-path'
2040
2189
  });
2041
2190
  document.body.appendChild(overlay);
2042
2191
  // Switch theme behind the overlay
2043
2192
  setTheme(newTheme);
2044
2193
  requestAnimationFrame(() => {
2045
2194
  requestAnimationFrame(() => {
2046
- overlay.style.transition = 'clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)';
2195
+ overlay.style.transition =
2196
+ 'clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)';
2047
2197
  overlay.style.clipPath = `circle(0px at ${x}px ${y}px)`;
2048
2198
  const done = () => {
2049
2199
  overlay.remove();