@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/README.md +77 -7
- package/dist/components.d.ts +474 -64
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +810 -164
- package/dist/hooks.d.ts +17 -4
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +490 -139
- package/package.json +14 -6
package/dist/hooks.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
/* eslint-disable @eslint-react/no-context-provider, @eslint-react/no-use-context
|
|
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(
|
|
53
|
-
const resolvedValue = typeof next === 'function'
|
|
54
|
-
|
|
55
|
-
|
|
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(() => {
|
|
111
|
-
|
|
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: () => {
|
|
119
|
-
|
|
120
|
-
|
|
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:
|
|
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, [
|
|
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(() => {
|
|
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:
|
|
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: () => {
|
|
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(() => {
|
|
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' &&
|
|
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 &&
|
|
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 ?
|
|
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, () => {
|
|
336
|
-
|
|
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
|
-
|
|
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 &&
|
|
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(() => {
|
|
409
|
-
|
|
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 [
|
|
420
|
-
.
|
|
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, [
|
|
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 = [
|
|
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 => {
|
|
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:
|
|
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, () => {
|
|
543
|
-
|
|
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
|
-
|
|
638
|
-
|
|
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 = [
|
|
644
|
-
.
|
|
645
|
-
|
|
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 [
|
|
675
|
-
.
|
|
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 ?
|
|
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:
|
|
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
|
-
|
|
724
|
-
|
|
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:
|
|
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', {
|
|
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
|
|
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
|
-
|
|
765
|
-
|
|
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 ?
|
|
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
|
-
|
|
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) =>
|
|
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 =
|
|
834
|
-
const maxDate =
|
|
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 ?
|
|
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
|
-
}, [
|
|
887
|
-
|
|
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
|
-
}
|
|
895
|
-
const selectDate =
|
|
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
|
-
}
|
|
912
|
-
const moveFocus =
|
|
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
|
-
}
|
|
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 =
|
|
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
|
-
})
|
|
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
|
|
1047
|
-
const
|
|
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
|
-
|
|
1051
|
-
|
|
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(
|
|
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(
|
|
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 ??
|
|
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 ??
|
|
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:
|
|
1263
|
+
ref: node => {
|
|
1264
|
+
inputRef.current = node;
|
|
1265
|
+
setRefValue(forwardedInputRef, node);
|
|
1266
|
+
},
|
|
1179
1267
|
type: 'text',
|
|
1180
1268
|
value
|
|
1181
|
-
}), [
|
|
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 ?
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
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
|
-
|
|
1309
|
-
|
|
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
|
|
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
|
-
|
|
1352
|
-
|
|
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 ||
|
|
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
|
-
|
|
1466
|
-
|
|
1467
|
-
if (typeof HTMLElement !== 'undefined' &&
|
|
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
|
-
|
|
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) ?
|
|
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) ?
|
|
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
|
-
|
|
1570
|
-
|
|
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 ||
|
|
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]) => [
|
|
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, () => {
|
|
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, () => {
|
|
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, () => {
|
|
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 => {
|
|
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 => {
|
|
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 => {
|
|
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 => {
|
|
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(() => {
|
|
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: () => {
|
|
1842
|
-
|
|
2176
|
+
onFocus: () => {
|
|
2177
|
+
scheduleOpen(0);
|
|
2178
|
+
},
|
|
2179
|
+
onKeyDown: event => {
|
|
1843
2180
|
if (event.key === 'Escape') {
|
|
1844
2181
|
close();
|
|
1845
2182
|
}
|
|
1846
2183
|
},
|
|
1847
|
-
onMouseEnter: () => {
|
|
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' &&
|
|
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
|
-
|
|
1899
|
-
|
|
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 = [
|
|
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: () => {
|
|
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(() => {
|
|
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 () => {
|
|
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:
|
|
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:
|
|
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: () => {
|
|
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 =
|
|
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();
|