@santi020k/lumen-react 0.4.0 → 1.2.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, createLumenKanbanMoveDetail, 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,216 @@ 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
+ const getKanbanRoot = (element, fallback) => element.closest('[data-ui-kanban]') ?? fallback;
1591
+ const getKanbanItem = (root, itemId) => [...(root?.querySelectorAll('[data-ui-kanban-item]') ?? [])]
1592
+ .find(item => item.dataset.uiKanbanItem === itemId) ?? null;
1593
+ const getKanbanColumnValues = (root) => [...(root?.querySelectorAll('[data-ui-kanban-column]') ?? [])]
1594
+ .map(column => column.dataset.uiKanbanColumn ?? '')
1595
+ .filter(Boolean);
1596
+ const isKanbanArrowKey = (key) => ['ArrowLeft', 'ArrowRight'].includes(key);
1597
+ const clearKanbanInteractionState = (root, item) => {
1598
+ if (item?.dataset.state === 'dragging')
1599
+ delete item.dataset.state;
1600
+ for (const column of root?.querySelectorAll('[data-ui-kanban-column]') ?? [])
1601
+ delete column.dataset.state;
1602
+ };
1603
+ const setKanbanDropTarget = (root, target) => {
1604
+ for (const column of root?.querySelectorAll('[data-ui-kanban-column]') ?? []) {
1605
+ if (column === target)
1606
+ column.dataset.state = 'drop-target';
1607
+ else
1608
+ delete column.dataset.state;
1609
+ }
1610
+ };
1611
+ const requestKeyboardKanbanMove = (event, itemId, root, requestMove) => {
1612
+ if (!isKanbanArrowKey(event.key))
1613
+ return;
1614
+ if (!root)
1615
+ return;
1616
+ const item = getKanbanItem(root, itemId);
1617
+ if (!item)
1618
+ return;
1619
+ if (item.getAttribute('aria-busy') === 'true')
1620
+ return;
1621
+ const fromColumn = item.closest('[data-ui-kanban-column]')
1622
+ ?.dataset.uiKanbanColumn;
1623
+ if (!fromColumn)
1624
+ return;
1625
+ const columns = getKanbanColumnValues(root);
1626
+ const currentIndex = columns.indexOf(fromColumn);
1627
+ const visualDirection = event.key === 'ArrowRight' ? 1 : -1;
1628
+ const direction = getComputedStyle(root).direction === 'rtl' ?
1629
+ -visualDirection :
1630
+ visualDirection;
1631
+ const toColumn = columns[currentIndex + direction];
1632
+ if (!toColumn)
1633
+ return;
1634
+ event.preventDefault();
1635
+ requestMove({ fromColumn, input: 'keyboard', itemId, toColumn });
1636
+ };
1637
+ const updateKanbanPointer = (event, pointer, root) => {
1638
+ if (pointer?.pointerId !== event.pointerId)
1639
+ return;
1640
+ const distance = Math.hypot(event.clientX - pointer.startX, event.clientY - pointer.startY);
1641
+ if (distance < 8) {
1642
+ if (!pointer.active)
1643
+ return;
1644
+ }
1645
+ pointer.active = true;
1646
+ event.currentTarget.setPointerCapture(event.pointerId);
1647
+ const item = getKanbanItem(root, pointer.itemId);
1648
+ const target = document.elementFromPoint(event.clientX, event.clientY)
1649
+ ?.closest('[data-ui-kanban-column]');
1650
+ if (item)
1651
+ item.dataset.state = 'dragging';
1652
+ setKanbanDropTarget(root, target);
1653
+ };
1654
+ const finishKanbanPointer = (event, pointer, root, requestMove) => {
1655
+ if (pointer?.pointerId !== event.pointerId)
1656
+ return;
1657
+ const item = getKanbanItem(root, pointer.itemId);
1658
+ if (!pointer.active)
1659
+ return;
1660
+ if (!item)
1661
+ return;
1662
+ const fromColumn = item.closest('[data-ui-kanban-column]')
1663
+ ?.dataset.uiKanbanColumn;
1664
+ const toColumn = document.elementFromPoint(event.clientX, event.clientY)
1665
+ ?.closest('[data-ui-kanban-column]')
1666
+ ?.dataset.uiKanbanColumn;
1667
+ clearKanbanInteractionState(root, item);
1668
+ if (fromColumn && toColumn)
1669
+ requestMove({ fromColumn, input: 'pointer', itemId: pointer.itemId, toColumn });
1670
+ };
1671
+ export const useKanban = ({ onMoveRequest } = {}) => {
1672
+ const rootRef = useRef(null);
1673
+ const pointerRef = useRef(null);
1674
+ const requestMove = useCallback(detail => {
1675
+ const normalized = createLumenKanbanMoveDetail(detail);
1676
+ if (!normalized)
1677
+ return false;
1678
+ const accepted = rootRef.current && typeof CustomEvent !== 'undefined' ?
1679
+ rootRef.current.dispatchEvent(new CustomEvent('ui:kanban-move-request', {
1680
+ bubbles: true,
1681
+ cancelable: true,
1682
+ detail: normalized
1683
+ })) :
1684
+ true;
1685
+ if (accepted)
1686
+ onMoveRequest?.(normalized);
1687
+ return accepted;
1688
+ }, [onMoveRequest]);
1689
+ const getItemProps = useCallback((itemId, props = {}) => ({
1690
+ ...props,
1691
+ 'data-ui-kanban-item': itemId,
1692
+ id: props.id ?? itemId
1693
+ }), []);
1694
+ const getHandleProps = useCallback((itemId, props = {}) => ({
1695
+ ...props,
1696
+ 'aria-keyshortcuts': props['aria-keyshortcuts'] ?? 'ArrowLeft ArrowRight',
1697
+ 'data-ui-kanban-handle': itemId,
1698
+ draggable: props.draggable ?? true,
1699
+ onDragEnd: composeHandlers(props.onDragEnd, event => {
1700
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1701
+ const item = getKanbanItem(root, itemId);
1702
+ if (item?.dataset.state === 'dragging')
1703
+ delete item.dataset.state;
1704
+ }),
1705
+ onDragStart: composeHandlers(props.onDragStart, event => {
1706
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1707
+ const item = getKanbanItem(root, itemId);
1708
+ if (!item || item.getAttribute('aria-busy') === 'true') {
1709
+ event.preventDefault();
1710
+ return;
1711
+ }
1712
+ event.dataTransfer.setData('text/plain', itemId);
1713
+ event.dataTransfer.effectAllowed = 'move';
1714
+ item.dataset.state = 'dragging';
1715
+ }),
1716
+ onKeyDown: composeHandlers(props.onKeyDown, event => {
1717
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1718
+ requestKeyboardKanbanMove(event, itemId, root, requestMove);
1719
+ }),
1720
+ onPointerDown: composeHandlers(props.onPointerDown, (event) => {
1721
+ if (event.pointerType === 'mouse')
1722
+ return;
1723
+ pointerRef.current = {
1724
+ active: false,
1725
+ itemId,
1726
+ pointerId: event.pointerId,
1727
+ startX: event.clientX,
1728
+ startY: event.clientY
1729
+ };
1730
+ }),
1731
+ onPointerCancel: composeHandlers(props.onPointerCancel, (event) => {
1732
+ const pointer = pointerRef.current;
1733
+ if (pointer?.pointerId !== event.pointerId)
1734
+ return;
1735
+ pointerRef.current = null;
1736
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1737
+ const item = getKanbanItem(root, pointer.itemId);
1738
+ clearKanbanInteractionState(root, item);
1739
+ }),
1740
+ onPointerMove: composeHandlers(props.onPointerMove, (event) => {
1741
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1742
+ updateKanbanPointer(event, pointerRef.current, root);
1743
+ }),
1744
+ onPointerUp: composeHandlers(props.onPointerUp, (event) => {
1745
+ const pointer = pointerRef.current;
1746
+ pointerRef.current = null;
1747
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1748
+ finishKanbanPointer(event, pointer, root, requestMove);
1749
+ }),
1750
+ type: props.type ?? 'button'
1751
+ }), [requestMove]);
1752
+ const getColumnProps = useCallback((column, props = {}) => ({
1753
+ ...props,
1754
+ 'data-ui-kanban-column': column,
1755
+ onDragLeave: composeHandlers(props.onDragLeave, event => {
1756
+ delete event.currentTarget.dataset.state;
1757
+ }),
1758
+ onDragOver: composeHandlers(props.onDragOver, event => {
1759
+ event.preventDefault();
1760
+ event.dataTransfer.dropEffect = 'move';
1761
+ event.currentTarget.dataset.state = 'drop-target';
1762
+ }),
1763
+ onDrop: composeHandlers(props.onDrop, event => {
1764
+ event.preventDefault();
1765
+ delete event.currentTarget.dataset.state;
1766
+ const itemId = event.dataTransfer.getData('text/plain');
1767
+ const root = getKanbanRoot(event.currentTarget, rootRef.current);
1768
+ const item = getKanbanItem(root, itemId);
1769
+ const fromColumn = item?.closest('[data-ui-kanban-column]')
1770
+ ?.dataset.uiKanbanColumn;
1771
+ if (item?.dataset.state === 'dragging')
1772
+ delete item.dataset.state;
1773
+ if (fromColumn)
1774
+ requestMove({ fromColumn, input: 'pointer', itemId, toColumn: column });
1775
+ })
1776
+ }), [requestMove]);
1777
+ return {
1778
+ getColumnProps,
1779
+ getHandleProps,
1780
+ getItemProps,
1781
+ requestMove,
1782
+ rootProps: {
1783
+ 'aria-orientation': 'horizontal',
1784
+ 'data-ui-kanban': true,
1785
+ ref: rootRef,
1786
+ tabIndex: 0
1787
+ },
1788
+ rootRef
1789
+ };
1790
+ };
1791
+ /* eslint-disable @stylistic/padding-line-between-statements, @eslint-react/set-state-in-effect */
1792
+ /* eslint-disable @typescript-eslint/prefer-optional-chain, complexity, no-nested-ternary */
1793
+ /* Resizable mirrors Astro's compact pane sizing runtime. */
1488
1794
  const parseResizableNumberList = (value, count, fallback) => {
1489
- const values = Array.isArray(value) ? value : value === undefined ? [] : [value];
1795
+ const values = Array.isArray(value) ?
1796
+ value :
1797
+ value === undefined ?
1798
+ [] :
1799
+ [value];
1490
1800
  return Array.from({ length: count }, (_, index) => values[index] ?? values[0] ?? fallback);
1491
1801
  };
1492
1802
  const normalizeResizableSizes = (sizes, count) => {
@@ -1500,7 +1810,11 @@ const normalizeResizableSizes = (sizes, count) => {
1500
1810
  return Array.from({ length: count }, () => fallbackSize);
1501
1811
  return usableSizes.map(size => (size / total) * 100);
1502
1812
  };
1503
- const serializeResizableSize = (value) => Array.isArray(value) ? value.join(',') : value === undefined ? undefined : String(value);
1813
+ const serializeResizableSize = (value) => Array.isArray(value) ?
1814
+ value.join(',') :
1815
+ value === undefined ?
1816
+ undefined :
1817
+ String(value);
1504
1818
  export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize, minSize, onSizesChange, panelCount: panelCountOption = 2, resetOnDoubleClick = true } = {}) => {
1505
1819
  const panelCount = Math.max(0, Math.floor(panelCountOption));
1506
1820
  const rootRef = useRef(null);
@@ -1565,9 +1879,9 @@ export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize,
1565
1879
  }),
1566
1880
  onKeyDown: composeHandlers(props.onKeyDown, event => {
1567
1881
  const step = event.shiftKey ? 10 : 2;
1568
- const keyDeltas = direction === 'horizontal'
1569
- ? { ArrowLeft: -step, ArrowRight: step }
1570
- : { ArrowDown: step, ArrowUp: -step };
1882
+ const keyDeltas = direction === 'horizontal' ?
1883
+ { ArrowLeft: -step, ArrowRight: step } :
1884
+ { ArrowDown: step, ArrowUp: -step };
1571
1885
  if (event.key === 'Home') {
1572
1886
  event.preventDefault();
1573
1887
  resizePair(index, minSizes[index] ?? 0);
@@ -1602,7 +1916,9 @@ export const useResizable = ({ defaultSizes, direction = 'horizontal', maxSize,
1602
1916
  }),
1603
1917
  onPointerMove: composeHandlers(props.onPointerMove, event => {
1604
1918
  const drag = dragRef.current;
1605
- if (!drag || drag.index !== index || event.currentTarget.dataset.active !== 'true')
1919
+ if (!drag ||
1920
+ drag.index !== index ||
1921
+ event.currentTarget.dataset.active !== 'true')
1606
1922
  return;
1607
1923
  const delta = ((event[axis] - drag.startPosition) / drag.containerSize) * 100;
1608
1924
  resizePair(index, drag.startSize + delta);
@@ -1708,7 +2024,10 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1708
2024
  currentSecondaryColor
1709
2025
  ]);
1710
2026
  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]);
2027
+ const previewStyle = useMemo(() => Object.fromEntries(Object.entries(result.tokens).map(([token, value]) => [
2028
+ `--${token}`,
2029
+ value
2030
+ ])), [result.tokens]);
1712
2031
  useEffect(() => {
1713
2032
  onThemeChange?.(result);
1714
2033
  }, [onThemeChange, result]);
@@ -1729,21 +2048,27 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1729
2048
  ...props,
1730
2049
  'aria-pressed': currentMode === nextMode,
1731
2050
  'data-ui-theme-mode': nextMode,
1732
- onClick: composeHandlers(props.onClick, () => { setMode(nextMode); }),
2051
+ onClick: composeHandlers(props.onClick, () => {
2052
+ setMode(nextMode);
2053
+ }),
1733
2054
  type: props.type ?? 'button'
1734
2055
  }), [currentMode, setMode]);
1735
2056
  const getSchemeProps = useCallback((nextScheme, props = {}) => ({
1736
2057
  ...props,
1737
2058
  'aria-pressed': currentScheme === nextScheme,
1738
2059
  'data-ui-theme-scheme': nextScheme,
1739
- onClick: composeHandlers(props.onClick, () => { setScheme(nextScheme); }),
2060
+ onClick: composeHandlers(props.onClick, () => {
2061
+ setScheme(nextScheme);
2062
+ }),
1740
2063
  type: props.type ?? 'button'
1741
2064
  }), [currentScheme, setScheme]);
1742
2065
  const getExportFormatProps = useCallback((format, props = {}) => ({
1743
2066
  ...props,
1744
2067
  'aria-pressed': currentExportFormat === format,
1745
2068
  'data-ui-theme-export-format': format,
1746
- onClick: composeHandlers(props.onClick, () => { setExportFormat(coerceThemeBuilderExportFormat(format)); }),
2069
+ onClick: composeHandlers(props.onClick, () => {
2070
+ setExportFormat(coerceThemeBuilderExportFormat(format));
2071
+ }),
1747
2072
  type: props.type ?? 'button'
1748
2073
  }), [currentExportFormat, setExportFormat]);
1749
2074
  return {
@@ -1752,7 +2077,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1752
2077
  'data-ui-theme-accent-hue': true,
1753
2078
  max: 359,
1754
2079
  min: 0,
1755
- onChange: event => { setAccentHue(Number(event.currentTarget.value)); },
2080
+ onChange: event => {
2081
+ setAccentHue(Number(event.currentTarget.value));
2082
+ },
1756
2083
  type: 'range',
1757
2084
  value: currentAccentHue
1758
2085
  },
@@ -1773,7 +2100,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1773
2100
  'data-ui-theme-brand-hue': true,
1774
2101
  max: 359,
1775
2102
  min: 0,
1776
- onChange: event => { setHue(Number(event.currentTarget.value)); },
2103
+ onChange: event => {
2104
+ setHue(Number(event.currentTarget.value));
2105
+ },
1777
2106
  type: 'range',
1778
2107
  value: currentHue
1779
2108
  },
@@ -1785,7 +2114,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1785
2114
  previewStyle,
1786
2115
  primaryColorProps: {
1787
2116
  'data-ui-theme-primary-color': true,
1788
- onChange: event => { setPrimaryColor(event.currentTarget.value); },
2117
+ onChange: event => {
2118
+ setPrimaryColor(event.currentTarget.value);
2119
+ },
1789
2120
  type: 'color',
1790
2121
  value: currentPrimaryColor
1791
2122
  },
@@ -1794,7 +2125,9 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1794
2125
  },
1795
2126
  secondaryColorProps: {
1796
2127
  'data-ui-theme-secondary-color': true,
1797
- onChange: event => { setSecondaryColor(event.currentTarget.value); },
2128
+ onChange: event => {
2129
+ setSecondaryColor(event.currentTarget.value);
2130
+ },
1798
2131
  type: 'color',
1799
2132
  value: currentSecondaryColor
1800
2133
  },
@@ -1807,7 +2140,7 @@ export const useThemeBuilder = ({ accentHue, defaultAccentHue = 54, defaultExpor
1807
2140
  setSecondaryColor
1808
2141
  };
1809
2142
  };
1810
- export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange, open, } = {}) => {
2143
+ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange, open } = {}) => {
1811
2144
  const tooltipId = useSafeId('ui-tooltip', id);
1812
2145
  const timerRef = useRef(undefined);
1813
2146
  const [isOpen, setIsOpen] = useControllableState({
@@ -1825,7 +2158,9 @@ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange,
1825
2158
  if (timerRef.current) {
1826
2159
  clearTimeout(timerRef.current);
1827
2160
  }
1828
- timerRef.current = setTimeout(() => { setIsOpen(true); }, nextDelay);
2161
+ timerRef.current = setTimeout(() => {
2162
+ setIsOpen(true);
2163
+ }, nextDelay);
1829
2164
  }, [setIsOpen]);
1830
2165
  useEffect(() => () => {
1831
2166
  if (timerRef.current) {
@@ -1838,13 +2173,17 @@ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange,
1838
2173
  rootProps: {
1839
2174
  'aria-describedby': isOpen ? tooltipId : undefined,
1840
2175
  'data-ui-tooltip': true,
1841
- onFocus: () => { scheduleOpen(0); },
1842
- onKeyDown: (event) => {
2176
+ onFocus: () => {
2177
+ scheduleOpen(0);
2178
+ },
2179
+ onKeyDown: event => {
1843
2180
  if (event.key === 'Escape') {
1844
2181
  close();
1845
2182
  }
1846
2183
  },
1847
- onMouseEnter: () => { scheduleOpen(delay); },
2184
+ onMouseEnter: () => {
2185
+ scheduleOpen(delay);
2186
+ },
1848
2187
  onMouseLeave: close
1849
2188
  },
1850
2189
  setOpen: setIsOpen,
@@ -1856,7 +2195,8 @@ export const useTooltip = ({ defaultOpen = false, delay = 250, id, onOpenChange,
1856
2195
  };
1857
2196
  };
1858
2197
  const createToastId = () => {
1859
- if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
2198
+ if (typeof crypto !== 'undefined' &&
2199
+ typeof crypto.randomUUID === 'function') {
1860
2200
  return `ui-toast-${crypto.randomUUID()}`;
1861
2201
  }
1862
2202
  return `ui-toast-${Math.random().toString(36).slice(2)}`;
@@ -1894,9 +2234,9 @@ export const ToastProvider = ({ children, maxCount = defaultToastMax, placement
1894
2234
  const record = createToastRecord(detail, placement);
1895
2235
  const stackMax = detail.max ?? maxCount;
1896
2236
  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];
2237
+ const next = current.some(toast => toast.id === record.id) ?
2238
+ current.map(toast => (toast.id === record.id ? record : toast)) :
2239
+ [...current, record];
1900
2240
  const samePlacement = next.filter(toast => toast.placement === record.placement);
1901
2241
  const staleIds = samePlacement
1902
2242
  .slice(0, Math.max(0, samePlacement.length - stackMax))
@@ -1926,7 +2266,9 @@ export const ToastProvider = ({ children, maxCount = defaultToastMax, placement
1926
2266
  toasts,
1927
2267
  update
1928
2268
  }), [create, dismiss, toasts, update]);
1929
- const placements = [...new Set([placement, ...toasts.map(toast => toast.placement)])];
2269
+ const placements = [
2270
+ ...new Set([placement, ...toasts.map(toast => toast.placement)])
2271
+ ];
1930
2272
  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
2273
  };
1932
2274
  export const useToast = () => {
@@ -1936,7 +2278,9 @@ export const useToast = () => {
1936
2278
  }
1937
2279
  return api;
1938
2280
  };
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))) }));
2281
+ 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: () => {
2282
+ removeToast(toast.id);
2283
+ }, toast: toast }, toast.id))) }));
1940
2284
  const ToastItem = ({ onDismiss, toast }) => {
1941
2285
  const toastRef = useRef(null);
1942
2286
  const timerRef = useRef(undefined);
@@ -1954,7 +2298,9 @@ const ToastItem = ({ onDismiss, toast }) => {
1954
2298
  if (!Number.isFinite(remaining) || remaining <= 0)
1955
2299
  return;
1956
2300
  startedAtRef.current = Date.now();
1957
- timerRef.current = setTimeout(() => { dismiss(toast.id); }, remaining);
2301
+ timerRef.current = setTimeout(() => {
2302
+ dismiss(toast.id);
2303
+ }, remaining);
1958
2304
  }, [clearTimer, dismiss, toast.id]);
1959
2305
  const pauseTimer = useCallback(() => {
1960
2306
  clearTimer();
@@ -1973,31 +2319,35 @@ const ToastItem = ({ onDismiss, toast }) => {
1973
2319
  useEffect(() => {
1974
2320
  if (!toast.open) {
1975
2321
  const timer = setTimeout(onDismiss, closeDelay);
1976
- return () => { clearTimeout(timer); };
2322
+ return () => {
2323
+ clearTimeout(timer);
2324
+ };
1977
2325
  }
1978
2326
  }, [onDismiss, toast.open]);
1979
2327
  const action = toast.action;
1980
2328
  const description = toast.description ?? '';
1981
2329
  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) => {
2330
+ 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
2331
  if (event.key !== 'Escape')
1984
2332
  return;
1985
2333
  event.preventDefault();
1986
2334
  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) => {
2335
+ }, 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
2336
  action.onClick?.(event, toastRef.current);
1989
2337
  toastRef.current?.dispatchEvent(new CustomEvent(action.event ?? 'ui:toast-action', {
1990
2338
  bubbles: true,
1991
2339
  detail: { id: toast.id, value: action.value }
1992
2340
  }));
1993
2341
  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" })] }));
2342
+ }, type: "button", children: action.label })), _jsx("button", { "aria-label": "Dismiss notification", className: "ui-toast__dismiss", onClick: () => {
2343
+ dismiss(toast.id);
2344
+ }, type: "button", children: "Dismiss" })] }));
1995
2345
  };
1996
2346
  export const useThemeToggle = (defaultTheme = 'light') => {
1997
2347
  const [theme, setTheme] = useState(() => {
1998
2348
  if (typeof window !== 'undefined') {
1999
- return localStorage.getItem('theme') ??
2000
- (document.documentElement.classList.contains('dark') ? 'dark' : 'light');
2349
+ return (localStorage.getItem('theme') ??
2350
+ (document.documentElement.classList.contains('dark') ? 'dark' : 'light'));
2001
2351
  }
2002
2352
  return defaultTheme;
2003
2353
  });
@@ -2036,14 +2386,15 @@ export const useThemeToggle = (defaultTheme = 'light') => {
2036
2386
  pointerEvents: 'none',
2037
2387
  backgroundColor: oldBg,
2038
2388
  clipPath: `circle(${maxRadius}px at ${x}px ${y}px)`,
2039
- willChange: 'clip-path',
2389
+ willChange: 'clip-path'
2040
2390
  });
2041
2391
  document.body.appendChild(overlay);
2042
2392
  // Switch theme behind the overlay
2043
2393
  setTheme(newTheme);
2044
2394
  requestAnimationFrame(() => {
2045
2395
  requestAnimationFrame(() => {
2046
- overlay.style.transition = 'clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)';
2396
+ overlay.style.transition =
2397
+ 'clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)';
2047
2398
  overlay.style.clipPath = `circle(0px at ${x}px ${y}px)`;
2048
2399
  const done = () => {
2049
2400
  overlay.remove();