gridsmith-ui 0.14.0 → 0.15.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/charts.js +1 -1
- package/dist/chat.js +4 -4
- package/dist/{chunk-PKLVP5ZB.js → chunk-7Q46JXSK.js} +5 -4
- package/dist/chunk-7Q46JXSK.js.map +1 -0
- package/dist/{chunk-JSQPVRHQ.js → chunk-ATONRIEB.js} +6 -6
- package/dist/chunk-ATONRIEB.js.map +1 -0
- package/dist/{chunk-IYD6ZDM2.js → chunk-EMCSARQO.js} +7 -6
- package/dist/chunk-EMCSARQO.js.map +1 -0
- package/dist/{chunk-QJBCZ7BR.js → chunk-LU3YRNES.js} +3 -3
- package/dist/{chunk-QJBCZ7BR.js.map → chunk-LU3YRNES.js.map} +1 -1
- package/dist/{chunk-CR2KF73R.js → chunk-OENJCGTJ.js} +3 -3
- package/dist/{chunk-CR2KF73R.js.map → chunk-OENJCGTJ.js.map} +1 -1
- package/dist/chunk-VFAKME7Q.js +350 -0
- package/dist/chunk-VFAKME7Q.js.map +1 -0
- package/dist/{chunk-U2JT37GJ.js → chunk-XCSTU6IJ.js} +3 -3
- package/dist/{chunk-U2JT37GJ.js.map → chunk-XCSTU6IJ.js.map} +1 -1
- package/dist/data-grid.js +3 -3
- package/dist/editor.js +25 -7
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +502 -147
- package/dist/index.js.map +1 -1
- package/dist/kanban.js +3 -3
- package/dist/resizable.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-IYD6ZDM2.js.map +0 -1
- package/dist/chunk-JSQPVRHQ.js.map +0 -1
- package/dist/chunk-NHQCMOAN.js +0 -137
- package/dist/chunk-NHQCMOAN.js.map +0 -1
- package/dist/chunk-PKLVP5ZB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { useAppShell, ControlSizeContext, Button, Spinner, OverlayCloseCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, useDirtyGuard, DiscardConfirm, defaultMaxWidth } from './chunk-
|
|
2
|
-
export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell } from './chunk-
|
|
1
|
+
import { useAppShell, ControlSizeContext, Button, Spinner, OverlayCloseCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, useDirtyGuard, DiscardConfirm, defaultMaxWidth } from './chunk-ATONRIEB.js';
|
|
2
|
+
export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell } from './chunk-ATONRIEB.js';
|
|
3
3
|
import { Skeleton } from './chunk-M3AARGLI.js';
|
|
4
4
|
export { Skeleton } from './chunk-M3AARGLI.js';
|
|
5
|
-
import {
|
|
6
|
-
import { Link } from './chunk-
|
|
7
|
-
export { CodeBlock, Divider, Link } from './chunk-
|
|
8
|
-
import { Badge } from './chunk-
|
|
9
|
-
export { Badge } from './chunk-
|
|
10
|
-
import { inferColumnKinds, CardValue } from './chunk-
|
|
5
|
+
import { usePopupFocus, focusInitial, focusAfterRemoval, useOverlayLayer, rovingTabStop, useGridKeyboard, OVERLAY_CLOSE_ATTR, useNonModalDialog, useFocusTrap, useFocusBeforeOpen, rememberFocus, useReturnFocus } from './chunk-VFAKME7Q.js';
|
|
6
|
+
import { Link } from './chunk-LU3YRNES.js';
|
|
7
|
+
export { CodeBlock, Divider, Link } from './chunk-LU3YRNES.js';
|
|
8
|
+
import { Badge } from './chunk-XCSTU6IJ.js';
|
|
9
|
+
export { Badge } from './chunk-XCSTU6IJ.js';
|
|
10
|
+
import { inferColumnKinds, CardValue } from './chunk-OENJCGTJ.js';
|
|
11
11
|
import { SplitPaneCtx, useShellPreferences, TableCardCtx, PREFERENCE_HOUSE_DEFAULTS, preferenceActionKey } from './chunk-TKVTZWBN.js';
|
|
12
12
|
export { PreferencesContext, preferenceActionKey, usePreferences } from './chunk-TKVTZWBN.js';
|
|
13
13
|
import { useBreakpoint } from './chunk-CRQ6KCRD.js';
|
|
14
14
|
export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
|
|
15
|
-
import { useTextOverflow, Tooltip, usePosition } from './chunk-
|
|
16
|
-
export { Tooltip, useTextOverflow } from './chunk-
|
|
17
|
-
import { mergeRefs, Slot } from './chunk-
|
|
15
|
+
import { useTextOverflow, Tooltip, usePosition } from './chunk-EMCSARQO.js';
|
|
16
|
+
export { Tooltip, useTextOverflow } from './chunk-EMCSARQO.js';
|
|
17
|
+
import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
|
|
18
18
|
import { useLatest } from './chunk-WH2FUFAC.js';
|
|
19
19
|
import { Icon } from './chunk-X4CNZFWC.js';
|
|
20
20
|
import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, ERROR_ACTIVE_RING, ERROR_FOCUS_RING, FOCUS_RING, ERROR_HAS_FOCUS_RING, HAS_FOCUS_RING, ERROR_FOCUS_WITHIN_RING, FOCUS_WITHIN_RING, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY } from './chunk-4KVFH5KC.js';
|
|
@@ -40,14 +40,13 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
|
|
|
40
40
|
setPrevOpen(open);
|
|
41
41
|
if (!open) setFocusedIdx(-1);
|
|
42
42
|
}
|
|
43
|
-
const isTopLayer =
|
|
44
|
-
useCloseOnFocusOut(open, triggerRef, menuRef, () => setOpen(false), isTopLayer);
|
|
43
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: menuRef, close: () => setOpen(false), tabExits: "any" });
|
|
45
44
|
useEffect(() => {
|
|
46
45
|
if (!open) return;
|
|
47
46
|
const onKeyDown = (e) => {
|
|
48
47
|
if (e.key === "Escape" && isTopLayer()) {
|
|
48
|
+
returnFocus();
|
|
49
49
|
setOpen(false);
|
|
50
|
-
triggerRef.current?.focus();
|
|
51
50
|
}
|
|
52
51
|
};
|
|
53
52
|
const onClick = (e) => {
|
|
@@ -61,11 +60,10 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
|
|
|
61
60
|
document.removeEventListener("keydown", onKeyDown);
|
|
62
61
|
document.removeEventListener("mousedown", onClick);
|
|
63
62
|
};
|
|
64
|
-
}, [open, isTopLayer]);
|
|
63
|
+
}, [open, isTopLayer, returnFocus]);
|
|
65
64
|
useEffect(() => {
|
|
66
65
|
if (open && focusedIdx >= 0) {
|
|
67
|
-
|
|
68
|
-
buttons?.[focusedIdx]?.focus();
|
|
66
|
+
menuRef.current?.querySelector(`[data-index="${focusedIdx}"]`)?.focus();
|
|
69
67
|
}
|
|
70
68
|
}, [open, focusedIdx]);
|
|
71
69
|
function handleMenuKeyDown(e) {
|
|
@@ -108,6 +106,7 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
|
|
|
108
106
|
className
|
|
109
107
|
),
|
|
110
108
|
style: { top: pos.top, left: pos.left },
|
|
109
|
+
onClick: (e) => e.stopPropagation(),
|
|
111
110
|
children: items.map((item, i) => {
|
|
112
111
|
if (isDivider(item)) {
|
|
113
112
|
return /* @__PURE__ */ jsx("div", { role: "separator", className: "my-ds-1 h-px bg-[var(--ds-border-default)]" }, i);
|
|
@@ -115,11 +114,13 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
|
|
|
115
114
|
return /* @__PURE__ */ jsxs(
|
|
116
115
|
"button",
|
|
117
116
|
{
|
|
117
|
+
"data-index": i,
|
|
118
118
|
role: "menuitem",
|
|
119
119
|
disabled: item.disabled,
|
|
120
120
|
tabIndex: focusedIdx === i ? 0 : -1,
|
|
121
121
|
onClick: () => {
|
|
122
122
|
if (!item.disabled) {
|
|
123
|
+
returnFocus();
|
|
123
124
|
item.onClick();
|
|
124
125
|
setOpen(false);
|
|
125
126
|
}
|
|
@@ -465,13 +466,7 @@ function Dropdown({
|
|
|
465
466
|
useEffect(() => {
|
|
466
467
|
if (!open) return;
|
|
467
468
|
updatePos();
|
|
468
|
-
requestAnimationFrame(() => {
|
|
469
|
-
if (searchRef.current) {
|
|
470
|
-
searchRef.current.focus();
|
|
471
|
-
} else {
|
|
472
|
-
const els = Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
|
|
473
|
-
(els.find((el) => el.getAttribute("aria-selected") === "true") ?? els[0])?.focus();
|
|
474
|
-
}
|
|
469
|
+
const raf = requestAnimationFrame(() => {
|
|
475
470
|
updatePos();
|
|
476
471
|
setPositioned(true);
|
|
477
472
|
});
|
|
@@ -479,10 +474,22 @@ function Dropdown({
|
|
|
479
474
|
window.addEventListener("scroll", onScroll, true);
|
|
480
475
|
window.addEventListener("resize", updatePos);
|
|
481
476
|
return () => {
|
|
477
|
+
cancelAnimationFrame(raf);
|
|
482
478
|
window.removeEventListener("scroll", onScroll, true);
|
|
483
479
|
window.removeEventListener("resize", updatePos);
|
|
484
480
|
};
|
|
485
481
|
}, [open, updatePos]);
|
|
482
|
+
const moved = useRef(false);
|
|
483
|
+
useLayoutEffect(() => {
|
|
484
|
+
if (!open || !positioned) return;
|
|
485
|
+
moved.current = false;
|
|
486
|
+
if (searchRef.current) {
|
|
487
|
+
searchRef.current.focus();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const els = Array.from(menuRef.current?.querySelectorAll('[role="option"]') ?? []);
|
|
491
|
+
(els.find((el) => el.getAttribute("aria-selected") === "true") ?? els[0])?.focus();
|
|
492
|
+
}, [open, positioned]);
|
|
486
493
|
useEffect(() => {
|
|
487
494
|
if (!open) return;
|
|
488
495
|
const handler = (e) => {
|
|
@@ -493,7 +500,13 @@ function Dropdown({
|
|
|
493
500
|
document.addEventListener("mousedown", handler);
|
|
494
501
|
return () => document.removeEventListener("mousedown", handler);
|
|
495
502
|
}, [open]);
|
|
496
|
-
const
|
|
503
|
+
const commitMoved = () => {
|
|
504
|
+
if (multiple || !moved.current) return;
|
|
505
|
+
const el = document.activeElement;
|
|
506
|
+
const id = el instanceof HTMLElement && menuRef.current?.contains(el) ? el.getAttribute("data-option-id") : null;
|
|
507
|
+
if (id) onChange(id);
|
|
508
|
+
};
|
|
509
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: menuRef, close: () => setOpen(false), tabExits: "any", onTabOut: commitMoved });
|
|
497
510
|
const typeahead = useRef({ buffer: "", at: 0 });
|
|
498
511
|
useEffect(() => {
|
|
499
512
|
if (!open) return;
|
|
@@ -502,6 +515,7 @@ function Dropdown({
|
|
|
502
515
|
const els = optionButtons();
|
|
503
516
|
if (els.length === 0) return;
|
|
504
517
|
els[(idx % els.length + els.length) % els.length].focus();
|
|
518
|
+
moved.current = true;
|
|
505
519
|
};
|
|
506
520
|
const onKey = (e) => {
|
|
507
521
|
const inMenu = menuRef.current?.contains(document.activeElement) || triggerRef.current === document.activeElement;
|
|
@@ -511,8 +525,8 @@ function Dropdown({
|
|
|
511
525
|
if (e.key === "Escape") {
|
|
512
526
|
if (!isTopLayer()) return;
|
|
513
527
|
e.preventDefault();
|
|
528
|
+
returnFocus();
|
|
514
529
|
setOpen(false);
|
|
515
|
-
triggerRef.current?.focus();
|
|
516
530
|
return;
|
|
517
531
|
}
|
|
518
532
|
if (e.key === "ArrowDown") {
|
|
@@ -536,10 +550,6 @@ function Dropdown({
|
|
|
536
550
|
focusOption(els.length - 1);
|
|
537
551
|
return;
|
|
538
552
|
}
|
|
539
|
-
if (e.key === "Tab") {
|
|
540
|
-
setOpen(false);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
553
|
if (!searchable && e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
544
554
|
const now = Date.now();
|
|
545
555
|
typeahead.current.buffer = now - typeahead.current.at > 700 ? e.key : typeahead.current.buffer + e.key;
|
|
@@ -554,7 +564,7 @@ function Dropdown({
|
|
|
554
564
|
};
|
|
555
565
|
document.addEventListener("keydown", onKey);
|
|
556
566
|
return () => document.removeEventListener("keydown", onKey);
|
|
557
|
-
}, [open, searchable, isTopLayer]);
|
|
567
|
+
}, [open, searchable, isTopLayer, returnFocus]);
|
|
558
568
|
const filtered = searchable && search ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : options;
|
|
559
569
|
const groups = /* @__PURE__ */ new Map();
|
|
560
570
|
for (const opt of filtered) {
|
|
@@ -568,10 +578,21 @@ function Dropdown({
|
|
|
568
578
|
const arr = selectedValues.includes(id) ? selectedValues.filter((v) => v !== id) : [...selectedValues, id];
|
|
569
579
|
onChange(arr);
|
|
570
580
|
} else {
|
|
581
|
+
returnFocus();
|
|
571
582
|
onChange(id);
|
|
572
583
|
setOpen(false);
|
|
573
584
|
}
|
|
574
585
|
};
|
|
586
|
+
const chipsRef = useRef(null);
|
|
587
|
+
const pendingChipFocus = useRef(null);
|
|
588
|
+
const chipCount = selectedValues.length;
|
|
589
|
+
useLayoutEffect(() => {
|
|
590
|
+
const index = pendingChipFocus.current;
|
|
591
|
+
if (index === null) return;
|
|
592
|
+
pendingChipFocus.current = null;
|
|
593
|
+
const chips = Array.from(chipsRef.current?.querySelectorAll("[data-chip-remove]") ?? []);
|
|
594
|
+
focusAfterRemoval(chips, index, triggerRef.current);
|
|
595
|
+
}, [chipCount]);
|
|
575
596
|
const displayLabel = multiple ? selectedValues.length > 0 ? `${selectedValues.length} selected` : placeholder : options.find((o) => o.id === selectedValues[0])?.label ?? placeholder;
|
|
576
597
|
const { ref: overflowRef, isTruncated } = useTextOverflow({ lines: 1, content: displayLabel });
|
|
577
598
|
return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
|
|
@@ -620,6 +641,7 @@ function Dropdown({
|
|
|
620
641
|
"div",
|
|
621
642
|
{
|
|
622
643
|
ref: menuRef,
|
|
644
|
+
onClick: (e) => e.stopPropagation(),
|
|
623
645
|
className: cn(
|
|
624
646
|
"fixed z-[var(--ds-z-popover)]",
|
|
625
647
|
"bg-[var(--ds-bg-surface-elevated)]",
|
|
@@ -670,7 +692,7 @@ function Dropdown({
|
|
|
670
692
|
),
|
|
671
693
|
container ?? document.body
|
|
672
694
|
),
|
|
673
|
-
multiple && showChips && selectedValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-ds-1 mt-ds-1.5", children: selectedValues.map((v) => {
|
|
695
|
+
multiple && showChips && selectedValues.length > 0 && /* @__PURE__ */ jsx("div", { ref: chipsRef, className: "flex flex-wrap gap-ds-1 mt-ds-1.5", children: selectedValues.map((v, i) => {
|
|
674
696
|
const opt = options.find((o) => o.id === v);
|
|
675
697
|
return opt ? /* @__PURE__ */ jsxs(
|
|
676
698
|
"span",
|
|
@@ -681,7 +703,11 @@ function Dropdown({
|
|
|
681
703
|
/* @__PURE__ */ jsx(
|
|
682
704
|
"button",
|
|
683
705
|
{
|
|
684
|
-
|
|
706
|
+
"data-chip-remove": "",
|
|
707
|
+
onClick: () => {
|
|
708
|
+
pendingChipFocus.current = i;
|
|
709
|
+
handleSelect(v);
|
|
710
|
+
},
|
|
685
711
|
className: "hover:text-[var(--ds-text-primary)] cursor-pointer",
|
|
686
712
|
"aria-label": `Remove ${opt.label}`,
|
|
687
713
|
children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 10 })
|
|
@@ -706,6 +732,7 @@ function OptionButton({
|
|
|
706
732
|
{
|
|
707
733
|
type: "button",
|
|
708
734
|
role: "option",
|
|
735
|
+
"data-option-id": opt.id,
|
|
709
736
|
"aria-selected": isSelected,
|
|
710
737
|
tabIndex: -1,
|
|
711
738
|
onClick: () => onSelect(opt.id),
|
|
@@ -750,6 +777,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
750
777
|
useOverlayLayer(open);
|
|
751
778
|
const [query, setQuery] = useState("");
|
|
752
779
|
const [highlightIdx, setHighlightIdx] = useState(0);
|
|
780
|
+
const [moved, setMoved] = useState(false);
|
|
753
781
|
const inputRef = useRef(null);
|
|
754
782
|
const triggerRef = useRef(null);
|
|
755
783
|
const [triggerWidth, setTriggerWidth] = useState(void 0);
|
|
@@ -766,6 +794,14 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
766
794
|
setPrevQuery(query);
|
|
767
795
|
setHighlightIdx(0);
|
|
768
796
|
}
|
|
797
|
+
const [prevOpen, setPrevOpen] = useState(open);
|
|
798
|
+
if (open !== prevOpen) {
|
|
799
|
+
setPrevOpen(open);
|
|
800
|
+
if (open && query === "") {
|
|
801
|
+
setHighlightIdx(Math.max(0, filtered.findIndex((o) => o.value === value)));
|
|
802
|
+
setMoved(false);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
769
805
|
useEffect(() => {
|
|
770
806
|
if (!open) return;
|
|
771
807
|
const handler = (e) => {
|
|
@@ -781,7 +817,6 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
781
817
|
onChange(val);
|
|
782
818
|
setQuery("");
|
|
783
819
|
setOpen(false);
|
|
784
|
-
inputRef.current?.blur();
|
|
785
820
|
},
|
|
786
821
|
[onChange]
|
|
787
822
|
);
|
|
@@ -798,6 +833,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
798
833
|
return;
|
|
799
834
|
}
|
|
800
835
|
setHighlightIdx((prev) => Math.min(prev + 1, filtered.length - 1));
|
|
836
|
+
setMoved(true);
|
|
801
837
|
} else if (e.key === "ArrowUp") {
|
|
802
838
|
e.preventDefault();
|
|
803
839
|
if (!open) {
|
|
@@ -805,15 +841,18 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
805
841
|
return;
|
|
806
842
|
}
|
|
807
843
|
setHighlightIdx((prev) => Math.max(prev - 1, 0));
|
|
844
|
+
setMoved(true);
|
|
808
845
|
} else if (e.key === "Enter" && open && filtered[highlightIdx]) {
|
|
809
846
|
e.preventDefault();
|
|
810
847
|
handleSelect(filtered[highlightIdx].value);
|
|
848
|
+
} else if (e.key === "Tab" && open && moved && filtered[highlightIdx]) {
|
|
849
|
+
onChange(filtered[highlightIdx].value);
|
|
811
850
|
} else if (e.key === "Escape") {
|
|
812
851
|
setOpen(false);
|
|
813
852
|
setQuery("");
|
|
814
853
|
}
|
|
815
854
|
},
|
|
816
|
-
[open, filtered, highlightIdx, handleSelect, openList]
|
|
855
|
+
[open, moved, filtered, highlightIdx, handleSelect, openList, onChange]
|
|
817
856
|
);
|
|
818
857
|
const handleBlur = useCallback((e) => {
|
|
819
858
|
const next = e.relatedTarget;
|
|
@@ -858,11 +897,13 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
858
897
|
"aria-expanded": open,
|
|
859
898
|
"aria-controls": listboxId,
|
|
860
899
|
"aria-autocomplete": "list",
|
|
900
|
+
"aria-activedescendant": open && filtered[highlightIdx] ? `${listboxId}-opt-${highlightIdx}` : void 0,
|
|
861
901
|
value: open ? query : selectedLabel,
|
|
862
902
|
placeholder,
|
|
863
903
|
disabled,
|
|
864
904
|
onChange: (e) => {
|
|
865
905
|
setQuery(e.target.value);
|
|
906
|
+
setMoved(true);
|
|
866
907
|
if (!open) setOpen(true);
|
|
867
908
|
},
|
|
868
909
|
onFocus: (e) => e.currentTarget.select(),
|
|
@@ -917,8 +958,10 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
917
958
|
children: /* @__PURE__ */ jsx("div", { style: { padding: "var(--ds-space-1)" }, children: loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-ds-4", children: /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "Loading options" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-ds-2 py-ds-3 text-sm text-[var(--ds-text-muted)] text-center", children: "No results" }) : filtered.map((opt, i) => /* @__PURE__ */ jsxs(
|
|
918
959
|
"button",
|
|
919
960
|
{
|
|
961
|
+
id: `${listboxId}-opt-${i}`,
|
|
920
962
|
role: "option",
|
|
921
|
-
|
|
963
|
+
tabIndex: -1,
|
|
964
|
+
"aria-selected": i === highlightIdx,
|
|
922
965
|
onMouseDown: (e) => e.preventDefault(),
|
|
923
966
|
onClick: () => handleSelect(opt.value),
|
|
924
967
|
onMouseEnter: () => setHighlightIdx(i),
|
|
@@ -940,10 +983,7 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
|
|
|
940
983
|
container ?? document.body
|
|
941
984
|
)
|
|
942
985
|
] });
|
|
943
|
-
|
|
944
|
-
return /* @__PURE__ */ jsx(Tooltip, { content: selectedLabel, children: triggerContent });
|
|
945
|
-
}
|
|
946
|
-
return triggerContent;
|
|
986
|
+
return /* @__PURE__ */ jsx(Tooltip, { content: selectedLabel, disabled: !labelTruncated || open, children: triggerContent });
|
|
947
987
|
}
|
|
948
988
|
var CHECKBOX_SIZES = {
|
|
949
989
|
sm: { box: "h-[16px] w-[16px]", glyph: 10, label: "text-xs" },
|
|
@@ -1125,6 +1165,42 @@ function RadioGroup({ value, onChange, options, orientation = "vertical", classN
|
|
|
1125
1165
|
] });
|
|
1126
1166
|
}
|
|
1127
1167
|
|
|
1168
|
+
// ../../showcase/src/lib/date-grid.ts
|
|
1169
|
+
function addDays(d, n) {
|
|
1170
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
|
|
1171
|
+
}
|
|
1172
|
+
function addMonthsClamped(d, n) {
|
|
1173
|
+
const first = new Date(d.getFullYear(), d.getMonth() + n, 1);
|
|
1174
|
+
const last = new Date(first.getFullYear(), first.getMonth() + 1, 0).getDate();
|
|
1175
|
+
return new Date(first.getFullYear(), first.getMonth(), Math.min(d.getDate(), last));
|
|
1176
|
+
}
|
|
1177
|
+
function dayKey(d) {
|
|
1178
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1179
|
+
}
|
|
1180
|
+
function moveDay(from, key, shift, weekStartsOn = 0) {
|
|
1181
|
+
const intoWeek = (from.getDay() - weekStartsOn + 7) % 7;
|
|
1182
|
+
switch (key) {
|
|
1183
|
+
case "ArrowLeft":
|
|
1184
|
+
return addDays(from, -1);
|
|
1185
|
+
case "ArrowRight":
|
|
1186
|
+
return addDays(from, 1);
|
|
1187
|
+
case "ArrowUp":
|
|
1188
|
+
return addDays(from, -7);
|
|
1189
|
+
case "ArrowDown":
|
|
1190
|
+
return addDays(from, 7);
|
|
1191
|
+
case "Home":
|
|
1192
|
+
return addDays(from, -intoWeek);
|
|
1193
|
+
case "End":
|
|
1194
|
+
return addDays(from, 6 - intoWeek);
|
|
1195
|
+
case "PageUp":
|
|
1196
|
+
return addMonthsClamped(from, shift ? -12 : -1);
|
|
1197
|
+
case "PageDown":
|
|
1198
|
+
return addMonthsClamped(from, shift ? 12 : 1);
|
|
1199
|
+
default:
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1128
1204
|
// ../../showcase/src/lib/segments.ts
|
|
1129
1205
|
function stepValue(spec, current, delta) {
|
|
1130
1206
|
if (current === null) return delta > 0 ? spec.min : spec.max;
|
|
@@ -1165,6 +1241,7 @@ var TEXT_SIZE2 = {
|
|
|
1165
1241
|
lg: "text-base"
|
|
1166
1242
|
};
|
|
1167
1243
|
var DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
1244
|
+
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1168
1245
|
var MONTHS = [
|
|
1169
1246
|
"January",
|
|
1170
1247
|
"February",
|
|
@@ -1236,7 +1313,9 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1236
1313
|
const [hover, setHover] = useState(null);
|
|
1237
1314
|
const [viewDate, setViewDate] = useState(() => value ?? range?.start ?? range?.end ?? /* @__PURE__ */ new Date());
|
|
1238
1315
|
const triggerRef = useRef(null);
|
|
1316
|
+
const buttonRef = useRef(null);
|
|
1239
1317
|
const panelRef = useRef(null);
|
|
1318
|
+
const gridRef = useRef(null);
|
|
1240
1319
|
const generatedId = useId();
|
|
1241
1320
|
const inputId = id ?? generatedId;
|
|
1242
1321
|
const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "start", offset: 4, flip: true });
|
|
@@ -1245,18 +1324,47 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1245
1324
|
const daysInMonth = getDaysInMonth(year, month);
|
|
1246
1325
|
const firstDay = getFirstDayOfWeek(year, month);
|
|
1247
1326
|
const today = useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
1248
|
-
const isTopLayer = useOverlayLayer(open);
|
|
1249
|
-
const prevMonth = () => setViewDate(new Date(year, month - 1, 1));
|
|
1250
|
-
const nextMonth = () => setViewDate(new Date(year, month + 1, 1));
|
|
1251
1327
|
const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
1252
1328
|
const isDisabledDay = (d) => !!min && d < startOf(min) || !!max && d > startOf(max);
|
|
1253
|
-
|
|
1329
|
+
const [focusDate, setFocusDate] = useState(() => startOf(value ?? range?.start ?? range?.end ?? /* @__PURE__ */ new Date()));
|
|
1330
|
+
const [prevOpen, setPrevOpen] = useState(open);
|
|
1331
|
+
if (open !== prevOpen) {
|
|
1332
|
+
setPrevOpen(open);
|
|
1333
|
+
if (open) {
|
|
1334
|
+
const start = startOf(value ?? range?.start ?? range?.end ?? today);
|
|
1335
|
+
setFocusDate(start);
|
|
1336
|
+
setViewDate(new Date(start.getFullYear(), start.getMonth(), 1));
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
const showDay = (d) => {
|
|
1340
|
+
setFocusDate(d);
|
|
1341
|
+
if (d.getFullYear() !== year || d.getMonth() !== month) setViewDate(new Date(d.getFullYear(), d.getMonth(), 1));
|
|
1342
|
+
};
|
|
1343
|
+
const grid = useGridKeyboard({
|
|
1344
|
+
container: gridRef,
|
|
1345
|
+
active: focusDate,
|
|
1346
|
+
setActive: showDay,
|
|
1347
|
+
keyOf: dayKey,
|
|
1348
|
+
move: (from, e) => moveDay(from, e.key, e.shiftKey, 0)
|
|
1349
|
+
});
|
|
1350
|
+
const prevMonth = () => {
|
|
1351
|
+
setViewDate(new Date(year, month - 1, 1));
|
|
1352
|
+
setFocusDate(addMonthsClamped(focusDate, -1));
|
|
1353
|
+
};
|
|
1354
|
+
const nextMonth = () => {
|
|
1355
|
+
setViewDate(new Date(year, month + 1, 1));
|
|
1356
|
+
setFocusDate(addMonthsClamped(focusDate, 1));
|
|
1357
|
+
};
|
|
1358
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: buttonRef, panel: panelRef, close: () => setOpen(false), tabExits: "edges" });
|
|
1359
|
+
const focusActiveDay = useLatest(grid.focusActive);
|
|
1254
1360
|
useEffect(() => {
|
|
1255
|
-
if (open)
|
|
1256
|
-
|
|
1361
|
+
if (!open) return;
|
|
1362
|
+
const raf = requestAnimationFrame(() => focusActiveDay.current());
|
|
1363
|
+
return () => cancelAnimationFrame(raf);
|
|
1364
|
+
}, [open, focusActiveDay]);
|
|
1257
1365
|
const closeAndRefocus = () => {
|
|
1366
|
+
returnFocus();
|
|
1258
1367
|
setOpen(false);
|
|
1259
|
-
focusSegment(0);
|
|
1260
1368
|
};
|
|
1261
1369
|
const dayFirst = useMemo(() => localeDayFirst(), []);
|
|
1262
1370
|
const [segs, setSegs] = useState(() => value ? { day: value.getDate(), month: value.getMonth() + 1, year: value.getFullYear() } : { day: null, month: null, year: null });
|
|
@@ -1272,7 +1380,6 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1272
1380
|
const focusSegment = (i) => {
|
|
1273
1381
|
segmentRefs.current[Math.max(0, Math.min(specs.length - 1, i))]?.focus();
|
|
1274
1382
|
};
|
|
1275
|
-
const focusSegmentRef = useLatest(focusSegment);
|
|
1276
1383
|
useEffect(() => {
|
|
1277
1384
|
if (!open) return;
|
|
1278
1385
|
const handler = (e) => {
|
|
@@ -1282,8 +1389,8 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1282
1389
|
};
|
|
1283
1390
|
const keyHandler = (e) => {
|
|
1284
1391
|
if (e.key === "Escape" && isTopLayer()) {
|
|
1392
|
+
returnFocus();
|
|
1285
1393
|
setOpen(false);
|
|
1286
|
-
focusSegmentRef.current(0);
|
|
1287
1394
|
}
|
|
1288
1395
|
};
|
|
1289
1396
|
document.addEventListener("mousedown", handler);
|
|
@@ -1292,7 +1399,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1292
1399
|
document.removeEventListener("mousedown", handler);
|
|
1293
1400
|
document.removeEventListener("keydown", keyHandler);
|
|
1294
1401
|
};
|
|
1295
|
-
}, [open, isTopLayer,
|
|
1402
|
+
}, [open, isTopLayer, returnFocus]);
|
|
1296
1403
|
const [typedInvalid, setTypedInvalid] = useState(false);
|
|
1297
1404
|
const commitIfComplete = (next) => {
|
|
1298
1405
|
if (next.day === null || next.month === null || next.year === null) {
|
|
@@ -1390,6 +1497,9 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1390
1497
|
const cells = [];
|
|
1391
1498
|
for (let i = 0; i < firstDay; i++) cells.push(null);
|
|
1392
1499
|
for (let d = 1; d <= daysInMonth; d++) cells.push(d);
|
|
1500
|
+
while (cells.length % 7) cells.push(null);
|
|
1501
|
+
const weeks = [];
|
|
1502
|
+
for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
|
|
1393
1503
|
return /* @__PURE__ */ jsxs("div", { ref, className: cn(className), children: [
|
|
1394
1504
|
label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm font-medium text-[var(--ds-text-primary)] mb-ds-1", children: label }),
|
|
1395
1505
|
/* @__PURE__ */ jsxs(
|
|
@@ -1455,6 +1565,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1455
1565
|
/* @__PURE__ */ jsx(
|
|
1456
1566
|
"button",
|
|
1457
1567
|
{
|
|
1568
|
+
ref: buttonRef,
|
|
1458
1569
|
type: "button",
|
|
1459
1570
|
"aria-label": "Choose a date",
|
|
1460
1571
|
"aria-haspopup": "dialog",
|
|
@@ -1501,38 +1612,56 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
|
|
|
1501
1612
|
]
|
|
1502
1613
|
}
|
|
1503
1614
|
),
|
|
1504
|
-
/* @__PURE__ */ jsxs(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
"
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
"
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1615
|
+
/* @__PURE__ */ jsxs(
|
|
1616
|
+
"div",
|
|
1617
|
+
{
|
|
1618
|
+
ref: gridRef,
|
|
1619
|
+
role: "grid",
|
|
1620
|
+
"aria-label": `${MONTHS[month]} ${year}`,
|
|
1621
|
+
onKeyDown: grid.onKeyDown,
|
|
1622
|
+
className: "flex flex-col gap-ds-0.5 tabular-nums",
|
|
1623
|
+
style: { padding: "var(--ds-space-2) var(--ds-space-3)" },
|
|
1624
|
+
children: [
|
|
1625
|
+
/* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 gap-ds-0.5 mb-ds-0.5", children: DAYS.map((d, i) => /* @__PURE__ */ jsx("div", { role: "columnheader", "aria-label": DAY_NAMES[i], className: "text-center text-2xs font-medium text-[var(--ds-text-muted)] py-ds-1", children: d }, d)) }),
|
|
1626
|
+
weeks.map((week, w) => /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 gap-ds-0.5", onMouseLeave: () => setHover(null), children: week.map((day, i) => {
|
|
1627
|
+
if (day === null) return /* @__PURE__ */ jsx("div", { role: "gridcell" }, `empty-${i}`);
|
|
1628
|
+
const dateObj = new Date(year, month, day);
|
|
1629
|
+
const isSelected = !!value && isSameDay(dateObj, value);
|
|
1630
|
+
const isOtherEnd = !!otherEnd && isSameDay(dateObj, otherEnd);
|
|
1631
|
+
const isToday2 = isSameDay(dateObj, today);
|
|
1632
|
+
const between = inBand(dateObj);
|
|
1633
|
+
const dis = isDisabledDay(dateObj);
|
|
1634
|
+
return /* @__PURE__ */ jsx(
|
|
1635
|
+
"button",
|
|
1636
|
+
{
|
|
1637
|
+
type: "button",
|
|
1638
|
+
role: "gridcell",
|
|
1639
|
+
"data-grid-key": dayKey(dateObj),
|
|
1640
|
+
tabIndex: grid.tabIndexOf(dateObj),
|
|
1641
|
+
"aria-disabled": dis || void 0,
|
|
1642
|
+
onClick: () => selectDay(day),
|
|
1643
|
+
onFocus: () => {
|
|
1644
|
+
if (!isSameDay(dateObj, focusDate)) setFocusDate(dateObj);
|
|
1645
|
+
},
|
|
1646
|
+
onMouseEnter: () => setHover(dateObj),
|
|
1647
|
+
"aria-selected": isSelected,
|
|
1648
|
+
"aria-current": isToday2 ? "date" : void 0,
|
|
1649
|
+
"aria-label": formatDate(dateObj),
|
|
1650
|
+
className: cn(
|
|
1651
|
+
"w-full aspect-square flex items-center justify-center text-xs",
|
|
1652
|
+
"transition-colors cursor-pointer",
|
|
1653
|
+
"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
|
|
1654
|
+
isSelected ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-bold rounded-[var(--ds-radius-sm)]" : isOtherEnd ? "bg-[var(--ds-accent-muted)] text-[var(--ds-accent-subtle-text)] font-semibold rounded-[var(--ds-radius-sm)]" : between ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-subtle-text)] rounded-none" : isToday2 ? "border-[length:var(--ds-border-width)] border-[var(--ds-accent-primary)] text-[var(--ds-accent-primary)] font-medium rounded-[var(--ds-radius-sm)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)] rounded-[var(--ds-radius-sm)]",
|
|
1655
|
+
dis && "opacity-disabled cursor-not-allowed hover:bg-transparent"
|
|
1656
|
+
),
|
|
1657
|
+
children: day
|
|
1658
|
+
},
|
|
1659
|
+
day
|
|
1660
|
+
);
|
|
1661
|
+
}) }, w))
|
|
1662
|
+
]
|
|
1663
|
+
}
|
|
1664
|
+
),
|
|
1536
1665
|
/* @__PURE__ */ jsxs(
|
|
1537
1666
|
"div",
|
|
1538
1667
|
{
|
|
@@ -1645,7 +1774,7 @@ function endOfWeek(d, weekStartsOn = 0) {
|
|
|
1645
1774
|
end.setHours(23, 59, 59, 999);
|
|
1646
1775
|
return end;
|
|
1647
1776
|
}
|
|
1648
|
-
function
|
|
1777
|
+
function addDays2(d, n) {
|
|
1649
1778
|
const result = new Date(d);
|
|
1650
1779
|
result.setDate(result.getDate() + n);
|
|
1651
1780
|
return result;
|
|
@@ -1674,7 +1803,7 @@ function getWeekDays(date, weekStartsOn = 0) {
|
|
|
1674
1803
|
const start = startOfWeek(date, weekStartsOn);
|
|
1675
1804
|
const days = [];
|
|
1676
1805
|
for (let i = 0; i < 7; i++) {
|
|
1677
|
-
days.push(
|
|
1806
|
+
days.push(addDays2(start, i));
|
|
1678
1807
|
}
|
|
1679
1808
|
return days;
|
|
1680
1809
|
}
|
|
@@ -1764,11 +1893,11 @@ var DEFAULT_DATE_RANGE_PRESETS = [
|
|
|
1764
1893
|
} },
|
|
1765
1894
|
{ label: "Last 7 days", range: () => {
|
|
1766
1895
|
const t = startOfDay(/* @__PURE__ */ new Date());
|
|
1767
|
-
return { start:
|
|
1896
|
+
return { start: addDays2(t, -6), end: t };
|
|
1768
1897
|
} },
|
|
1769
1898
|
{ label: "Last 30 days", range: () => {
|
|
1770
1899
|
const t = startOfDay(/* @__PURE__ */ new Date());
|
|
1771
|
-
return { start:
|
|
1900
|
+
return { start: addDays2(t, -29), end: t };
|
|
1772
1901
|
} },
|
|
1773
1902
|
{ label: "This month", range: () => {
|
|
1774
1903
|
const t = /* @__PURE__ */ new Date();
|
|
@@ -1808,11 +1937,38 @@ function DateRangePicker({
|
|
|
1808
1937
|
const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "start", offset: 4, flip: true });
|
|
1809
1938
|
const today = useMemo(() => startOfDay(/* @__PURE__ */ new Date()), []);
|
|
1810
1939
|
const dayNames = getOrderedDayNamesShort(weekStartsOn);
|
|
1940
|
+
const gridRef = useRef(null);
|
|
1941
|
+
const [focusDate, setFocusDate] = useState(() => startOfDay(value.start ?? /* @__PURE__ */ new Date()));
|
|
1942
|
+
const [prevOpen, setPrevOpen] = useState(open);
|
|
1943
|
+
if (open !== prevOpen) {
|
|
1944
|
+
setPrevOpen(open);
|
|
1945
|
+
if (open) {
|
|
1946
|
+
const start = startOfDay(value.start ?? today);
|
|
1947
|
+
setFocusDate(start);
|
|
1948
|
+
setViewDate(new Date(start.getFullYear(), start.getMonth(), 1));
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
const showDay = (d) => {
|
|
1952
|
+
setFocusDate(d);
|
|
1953
|
+
const monthIndex = (x) => x.getFullYear() * 12 + x.getMonth();
|
|
1954
|
+
const offset = monthIndex(d) - monthIndex(viewDate);
|
|
1955
|
+
if (offset < 0) setViewDate(new Date(d.getFullYear(), d.getMonth(), 1));
|
|
1956
|
+
else if (offset > 1) setViewDate(new Date(d.getFullYear(), d.getMonth() - 1, 1));
|
|
1957
|
+
};
|
|
1958
|
+
const grid = useGridKeyboard({
|
|
1959
|
+
container: gridRef,
|
|
1960
|
+
active: focusDate,
|
|
1961
|
+
setActive: showDay,
|
|
1962
|
+
keyOf: dayKey,
|
|
1963
|
+
move: (from, e) => moveDay(from, e.key, e.shiftKey, weekStartsOn)
|
|
1964
|
+
});
|
|
1965
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close: () => setOpen(false), tabExits: "edges" });
|
|
1966
|
+
const focusActiveDay = useLatest(grid.focusActive);
|
|
1811
1967
|
useEffect(() => {
|
|
1812
|
-
if (open)
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1968
|
+
if (!open) return;
|
|
1969
|
+
const raf = requestAnimationFrame(() => focusActiveDay.current());
|
|
1970
|
+
return () => cancelAnimationFrame(raf);
|
|
1971
|
+
}, [open, focusActiveDay]);
|
|
1816
1972
|
useEffect(() => {
|
|
1817
1973
|
if (!open) return;
|
|
1818
1974
|
const onMouse = (e) => {
|
|
@@ -1822,8 +1978,8 @@ function DateRangePicker({
|
|
|
1822
1978
|
};
|
|
1823
1979
|
const onKey = (e) => {
|
|
1824
1980
|
if (e.key === "Escape" && isTopLayer()) {
|
|
1981
|
+
returnFocus();
|
|
1825
1982
|
setOpen(false);
|
|
1826
|
-
triggerRef.current?.focus();
|
|
1827
1983
|
}
|
|
1828
1984
|
};
|
|
1829
1985
|
document.addEventListener("mousedown", onMouse);
|
|
@@ -1832,7 +1988,7 @@ function DateRangePicker({
|
|
|
1832
1988
|
document.removeEventListener("mousedown", onMouse);
|
|
1833
1989
|
document.removeEventListener("keydown", onKey);
|
|
1834
1990
|
};
|
|
1835
|
-
}, [open, isTopLayer]);
|
|
1991
|
+
}, [open, isTopLayer, returnFocus]);
|
|
1836
1992
|
const isDisabledDay = (d) => min && d < startOfDay(min) || max && d > startOfDay(max) || false;
|
|
1837
1993
|
const selectDay = (d) => {
|
|
1838
1994
|
if (isDisabledDay(d)) return;
|
|
@@ -1844,9 +2000,9 @@ function DateRangePicker({
|
|
|
1844
2000
|
onChange({ start: d, end: null });
|
|
1845
2001
|
return;
|
|
1846
2002
|
}
|
|
2003
|
+
returnFocus();
|
|
1847
2004
|
onChange({ start: value.start, end: d });
|
|
1848
2005
|
setOpen(false);
|
|
1849
|
-
triggerRef.current?.focus();
|
|
1850
2006
|
};
|
|
1851
2007
|
const previewEnd = value.start && !value.end && hover && hover >= value.start ? hover : null;
|
|
1852
2008
|
const rangeStart = value.start;
|
|
@@ -1908,10 +2064,10 @@ function DateRangePicker({
|
|
|
1908
2064
|
children: [
|
|
1909
2065
|
presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-ds-0.5 p-ds-2 border-r-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] min-w-[140px]", children: presets.map((p) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "justify-start", onClick: () => {
|
|
1910
2066
|
const r = p.range();
|
|
2067
|
+
returnFocus();
|
|
1911
2068
|
onChange(r);
|
|
1912
2069
|
setViewDate(r.start ?? /* @__PURE__ */ new Date());
|
|
1913
2070
|
setOpen(false);
|
|
1914
|
-
triggerRef.current?.focus();
|
|
1915
2071
|
}, children: p.label }, p.label)) }),
|
|
1916
2072
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
1917
2073
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between bg-[var(--ds-bg-muted)] rounded-tr-[var(--ds-radius-md)] px-ds-3 py-ds-2", children: [
|
|
@@ -1927,10 +2083,10 @@ function DateRangePicker({
|
|
|
1927
2083
|
] }),
|
|
1928
2084
|
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => setViewDate(addMonths(viewDate, 1)), "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
|
|
1929
2085
|
] }),
|
|
1930
|
-
/* @__PURE__ */ jsx("div", { className: "flex gap-ds-4 px-ds-3 py-ds-2", onMouseLeave: () => setHover(null), children: months.map((m) => /* @__PURE__ */ jsxs("div", { className: "w-[224px]", children: [
|
|
1931
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-ds-0.5 mb-ds-
|
|
1932
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-ds-0.5", children:
|
|
1933
|
-
if (!isSameMonth(d, m)) return /* @__PURE__ */ jsx("div", {}, d.toISOString());
|
|
2086
|
+
/* @__PURE__ */ jsx("div", { ref: gridRef, onKeyDown: grid.onKeyDown, className: "flex gap-ds-4 px-ds-3 py-ds-2", onMouseLeave: () => setHover(null), children: months.map((m) => /* @__PURE__ */ jsxs("div", { role: "grid", "aria-label": `${MONTHS2[m.getMonth()]} ${m.getFullYear()}`, className: "w-[224px] flex flex-col gap-ds-0.5", children: [
|
|
2087
|
+
/* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 gap-ds-0.5 mb-ds-0.5", children: dayNames.map((d) => /* @__PURE__ */ jsx("div", { role: "columnheader", className: "text-center text-2xs font-medium text-[var(--ds-text-muted)] py-ds-1", children: d }, d)) }),
|
|
2088
|
+
getMonthGrid(m.getFullYear(), m.getMonth(), weekStartsOn).map((week, w) => /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 gap-ds-0.5", children: week.map((d) => {
|
|
2089
|
+
if (!isSameMonth(d, m)) return /* @__PURE__ */ jsx("div", { role: "gridcell" }, d.toISOString());
|
|
1934
2090
|
const isStart = !!rangeStart && isSameDay2(d, rangeStart);
|
|
1935
2091
|
const isEnd = !!rangeEnd && isSameDay2(d, rangeEnd);
|
|
1936
2092
|
const between = inRange(d);
|
|
@@ -1939,10 +2095,17 @@ function DateRangePicker({
|
|
|
1939
2095
|
"button",
|
|
1940
2096
|
{
|
|
1941
2097
|
type: "button",
|
|
1942
|
-
|
|
2098
|
+
role: "gridcell",
|
|
2099
|
+
"data-grid-key": dayKey(d),
|
|
2100
|
+
tabIndex: grid.tabIndexOf(d),
|
|
2101
|
+
"aria-disabled": dis || void 0,
|
|
1943
2102
|
onClick: () => selectDay(d),
|
|
2103
|
+
onFocus: () => {
|
|
2104
|
+
if (!isSameDay2(d, focusDate)) setFocusDate(d);
|
|
2105
|
+
},
|
|
1944
2106
|
onMouseEnter: () => setHover(d),
|
|
1945
|
-
"aria-
|
|
2107
|
+
"aria-selected": isStart || isEnd || between,
|
|
2108
|
+
"aria-current": isSameDay2(d, today) ? "date" : void 0,
|
|
1946
2109
|
"aria-label": fmt(d),
|
|
1947
2110
|
className: cn(
|
|
1948
2111
|
"w-full aspect-square flex items-center justify-center text-xs",
|
|
@@ -1955,7 +2118,7 @@ function DateRangePicker({
|
|
|
1955
2118
|
},
|
|
1956
2119
|
d.toISOString()
|
|
1957
2120
|
);
|
|
1958
|
-
}) })
|
|
2121
|
+
}) }, w))
|
|
1959
2122
|
] }, `${m.getFullYear()}-${m.getMonth()}`)) }),
|
|
1960
2123
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center bg-[var(--ds-bg-muted)] border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-br-[var(--ds-radius-md)] px-ds-3 py-ds-2", children: [
|
|
1961
2124
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--ds-text-muted)]", children: value.start && !value.end ? "Now pick an end date" : summary ?? "Pick a start date" }),
|
|
@@ -2365,14 +2528,25 @@ function TimePicker({
|
|
|
2365
2528
|
const size = sizeProp ?? contextSize ?? "md";
|
|
2366
2529
|
const [open, setOpen] = useState(false);
|
|
2367
2530
|
const triggerRef = useRef(null);
|
|
2531
|
+
const buttonRef = useRef(null);
|
|
2368
2532
|
const panelRef = useRef(null);
|
|
2369
2533
|
const listboxId = useId();
|
|
2370
2534
|
const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "start", offset: 4, flip: true });
|
|
2371
2535
|
const parsed = parseTime(value);
|
|
2372
2536
|
const hours = generateHours(use24Hour);
|
|
2373
2537
|
const minutes = generateMinutes(minuteStep);
|
|
2374
|
-
const isTopLayer =
|
|
2375
|
-
|
|
2538
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: buttonRef, panel: panelRef, close: () => setOpen(false), tabExits: "edges" });
|
|
2539
|
+
const [columnStop, setColumnStop] = useState({});
|
|
2540
|
+
const [prevOpen, setPrevOpen] = useState(open);
|
|
2541
|
+
if (open !== prevOpen) {
|
|
2542
|
+
setPrevOpen(open);
|
|
2543
|
+
if (open) setColumnStop({});
|
|
2544
|
+
}
|
|
2545
|
+
const stopOf = (column, chosen) => columnStop[column] ?? Math.max(0, chosen);
|
|
2546
|
+
const holdStop = (column, index) => setColumnStop((s) => s[column] === index ? s : { ...s, [column]: index });
|
|
2547
|
+
const chosenHour = hours.findIndex((h) => use24Hour ? h === parseInt(value) : h === parsed.hour);
|
|
2548
|
+
const chosenMinute = minutes.findIndex((m) => m === parsed.minute);
|
|
2549
|
+
const chosenPeriod = parsed.period === "PM" ? 1 : 0;
|
|
2376
2550
|
useEffect(() => {
|
|
2377
2551
|
if (!open) return;
|
|
2378
2552
|
const id2 = requestAnimationFrame(() => {
|
|
@@ -2424,7 +2598,6 @@ function TimePicker({
|
|
|
2424
2598
|
const el = segmentRefs.current[Math.max(0, Math.min(segmentCount - 1, i))];
|
|
2425
2599
|
el?.focus();
|
|
2426
2600
|
};
|
|
2427
|
-
const focusSegmentRef = useLatest(focusSegment);
|
|
2428
2601
|
useEffect(() => {
|
|
2429
2602
|
if (!open) return;
|
|
2430
2603
|
const handler = (e) => {
|
|
@@ -2433,8 +2606,8 @@ function TimePicker({
|
|
|
2433
2606
|
};
|
|
2434
2607
|
const keyHandler = (e) => {
|
|
2435
2608
|
if (e.key === "Escape" && isTopLayer()) {
|
|
2609
|
+
returnFocus();
|
|
2436
2610
|
setOpen(false);
|
|
2437
|
-
focusSegmentRef.current(0);
|
|
2438
2611
|
}
|
|
2439
2612
|
};
|
|
2440
2613
|
document.addEventListener("mousedown", handler);
|
|
@@ -2443,7 +2616,7 @@ function TimePicker({
|
|
|
2443
2616
|
document.removeEventListener("mousedown", handler);
|
|
2444
2617
|
document.removeEventListener("keydown", keyHandler);
|
|
2445
2618
|
};
|
|
2446
|
-
}, [open, isTopLayer,
|
|
2619
|
+
}, [open, isTopLayer, returnFocus]);
|
|
2447
2620
|
const commitIfComplete = (next) => {
|
|
2448
2621
|
if (next.hour === null || next.minute === null) return;
|
|
2449
2622
|
let h24 = next.hour;
|
|
@@ -2619,6 +2792,7 @@ function TimePicker({
|
|
|
2619
2792
|
/* @__PURE__ */ jsx(
|
|
2620
2793
|
"button",
|
|
2621
2794
|
{
|
|
2795
|
+
ref: buttonRef,
|
|
2622
2796
|
type: "button",
|
|
2623
2797
|
"aria-label": "Choose a time",
|
|
2624
2798
|
"aria-haspopup": "dialog",
|
|
@@ -2651,10 +2825,12 @@ function TimePicker({
|
|
|
2651
2825
|
),
|
|
2652
2826
|
style: { top: pos.top, left: pos.left },
|
|
2653
2827
|
children: /* @__PURE__ */ jsxs("div", { className: "inline-flex p-ds-1 gap-ds-1 tabular-nums", children: [
|
|
2654
|
-
/* @__PURE__ */ jsx("div", { "data-column": true, className: "max-h-48 overflow-y-auto w-ds-10", children: hours.map((h) => /* @__PURE__ */ jsx(
|
|
2828
|
+
/* @__PURE__ */ jsx("div", { "data-column": true, className: "max-h-48 overflow-y-auto w-ds-10", children: hours.map((h, i) => /* @__PURE__ */ jsx(
|
|
2655
2829
|
"button",
|
|
2656
2830
|
{
|
|
2657
2831
|
type: "button",
|
|
2832
|
+
tabIndex: i === stopOf("hour", chosenHour) ? 0 : -1,
|
|
2833
|
+
onFocus: () => holdStop("hour", i),
|
|
2658
2834
|
"data-selected": use24Hour ? h === parseInt(value) : h === parsed.hour,
|
|
2659
2835
|
onMouseDown: (e) => e.preventDefault(),
|
|
2660
2836
|
onClick: () => emitChange(h, parsed.minute, parsed.period),
|
|
@@ -2668,10 +2844,12 @@ function TimePicker({
|
|
|
2668
2844
|
h
|
|
2669
2845
|
)) }),
|
|
2670
2846
|
/* @__PURE__ */ jsx("div", { className: "w-px bg-[var(--ds-border-subtle)]" }),
|
|
2671
|
-
/* @__PURE__ */ jsx("div", { "data-column": true, className: "max-h-48 overflow-y-auto w-ds-10", children: minutes.map((m) => /* @__PURE__ */ jsx(
|
|
2847
|
+
/* @__PURE__ */ jsx("div", { "data-column": true, className: "max-h-48 overflow-y-auto w-ds-10", children: minutes.map((m, i) => /* @__PURE__ */ jsx(
|
|
2672
2848
|
"button",
|
|
2673
2849
|
{
|
|
2674
2850
|
type: "button",
|
|
2851
|
+
tabIndex: i === stopOf("minute", chosenMinute) ? 0 : -1,
|
|
2852
|
+
onFocus: () => holdStop("minute", i),
|
|
2675
2853
|
"data-selected": m === parsed.minute,
|
|
2676
2854
|
onMouseDown: (e) => e.preventDefault(),
|
|
2677
2855
|
onClick: () => emitChange(parsed.hour, m, parsed.period),
|
|
@@ -2686,10 +2864,12 @@ function TimePicker({
|
|
|
2686
2864
|
)) }),
|
|
2687
2865
|
!use24Hour && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2688
2866
|
/* @__PURE__ */ jsx("div", { className: "w-px bg-[var(--ds-border-subtle)]" }),
|
|
2689
|
-
/* @__PURE__ */ jsx("div", { "data-column": true, className: "flex flex-col gap-ds-1 w-ds-10", children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsx(
|
|
2867
|
+
/* @__PURE__ */ jsx("div", { "data-column": true, className: "flex flex-col gap-ds-1 w-ds-10", children: ["AM", "PM"].map((p, i) => /* @__PURE__ */ jsx(
|
|
2690
2868
|
"button",
|
|
2691
2869
|
{
|
|
2692
2870
|
type: "button",
|
|
2871
|
+
tabIndex: i === stopOf("period", chosenPeriod) ? 0 : -1,
|
|
2872
|
+
onFocus: () => holdStop("period", i),
|
|
2693
2873
|
"data-selected": p === parsed.period,
|
|
2694
2874
|
onMouseDown: (e) => e.preventDefault(),
|
|
2695
2875
|
onClick: () => emitChange(parsed.hour, parsed.minute, p),
|
|
@@ -3016,7 +3196,8 @@ var Tag = forwardRef(function Tag2({
|
|
|
3016
3196
|
{
|
|
3017
3197
|
type: "button",
|
|
3018
3198
|
onClick: handleRemove,
|
|
3019
|
-
"
|
|
3199
|
+
"data-tag-remove": "",
|
|
3200
|
+
"aria-label": typeof children === "string" ? `Remove ${children}` : "Remove",
|
|
3020
3201
|
className: cn(
|
|
3021
3202
|
"inline-flex items-center justify-center shrink-0",
|
|
3022
3203
|
"rounded-[var(--ds-radius-sm)]",
|
|
@@ -3063,6 +3244,16 @@ function TagInput({
|
|
|
3063
3244
|
},
|
|
3064
3245
|
[value, onChange]
|
|
3065
3246
|
);
|
|
3247
|
+
const containerRef = useRef(null);
|
|
3248
|
+
const pendingFocus = useRef(null);
|
|
3249
|
+
const tagCount = value.length;
|
|
3250
|
+
useLayoutEffect(() => {
|
|
3251
|
+
const index = pendingFocus.current;
|
|
3252
|
+
if (index === null) return;
|
|
3253
|
+
pendingFocus.current = null;
|
|
3254
|
+
const buttons = Array.from(containerRef.current?.querySelectorAll("[data-tag-remove]") ?? []);
|
|
3255
|
+
focusAfterRemoval(buttons, index, inputRef.current);
|
|
3256
|
+
}, [tagCount]);
|
|
3066
3257
|
const handleKeyDown = useCallback(
|
|
3067
3258
|
(e) => {
|
|
3068
3259
|
if ((e.key === "Enter" || e.key === ",") && input.trim()) {
|
|
@@ -3095,7 +3286,10 @@ function TagInput({
|
|
|
3095
3286
|
return /* @__PURE__ */ jsxs(
|
|
3096
3287
|
"div",
|
|
3097
3288
|
{
|
|
3098
|
-
|
|
3289
|
+
ref: containerRef,
|
|
3290
|
+
onClick: (e) => {
|
|
3291
|
+
if (!(e.target instanceof Element && e.target.closest("button"))) inputRef.current?.focus();
|
|
3292
|
+
},
|
|
3099
3293
|
className: cn(
|
|
3100
3294
|
"flex flex-wrap items-center gap-[calc(var(--ds-space-1)*1.5)] min-h-[calc(var(--ds-space-1)*10.5)] px-ds-2 py-[calc(var(--ds-space-1)*1.5)]",
|
|
3101
3295
|
"bg-[var(--ds-input-bg)] text-[var(--ds-text-primary)]",
|
|
@@ -3110,10 +3304,13 @@ function TagInput({
|
|
|
3110
3304
|
className
|
|
3111
3305
|
),
|
|
3112
3306
|
children: [
|
|
3113
|
-
value.map((tag) => /* @__PURE__ */ jsx(
|
|
3307
|
+
value.map((tag, i) => /* @__PURE__ */ jsx(
|
|
3114
3308
|
Tag,
|
|
3115
3309
|
{
|
|
3116
|
-
onRemove: disabled ? void 0 : () =>
|
|
3310
|
+
onRemove: disabled ? void 0 : () => {
|
|
3311
|
+
pendingFocus.current = i;
|
|
3312
|
+
removeTag(tag);
|
|
3313
|
+
},
|
|
3117
3314
|
disabled,
|
|
3118
3315
|
children: tag
|
|
3119
3316
|
},
|
|
@@ -4683,17 +4880,30 @@ var Card = forwardRef(function Card2({
|
|
|
4683
4880
|
onSelectedChange?.(!selected);
|
|
4684
4881
|
onClick?.(e);
|
|
4685
4882
|
} : onClick;
|
|
4883
|
+
const isAction = !isSelectable && variant === "interactive" && !!onClick;
|
|
4686
4884
|
const handleKeyDown = isSelectable && !disabled ? (e) => {
|
|
4687
4885
|
if (e.key === "Enter" || e.key === " ") {
|
|
4688
4886
|
e.preventDefault();
|
|
4689
4887
|
onSelectedChange?.(!selected);
|
|
4690
4888
|
}
|
|
4889
|
+
} : isAction && !disabled ? (e) => {
|
|
4890
|
+
if (e.target !== e.currentTarget) return;
|
|
4891
|
+
if (e.key === "Enter") {
|
|
4892
|
+
e.preventDefault();
|
|
4893
|
+
e.currentTarget.click();
|
|
4894
|
+
} else if (e.key === " ") e.preventDefault();
|
|
4895
|
+
} : void 0;
|
|
4896
|
+
const handleKeyUp = isAction && !disabled ? (e) => {
|
|
4897
|
+
if (e.target === e.currentTarget && e.key === " ") {
|
|
4898
|
+
e.preventDefault();
|
|
4899
|
+
e.currentTarget.click();
|
|
4900
|
+
}
|
|
4691
4901
|
} : void 0;
|
|
4692
4902
|
const handleCheckableClick = (e) => {
|
|
4693
4903
|
e.stopPropagation();
|
|
4694
4904
|
onSelectedChange?.(!selected);
|
|
4695
4905
|
};
|
|
4696
|
-
const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isSelectable ? "button" : void 0;
|
|
4906
|
+
const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isSelectable || isAction ? "button" : void 0;
|
|
4697
4907
|
const ariaAttr = selectType === "radio" || selectType === "checkbox" || selectType === "toggle" ? { "aria-checked": !!selected } : isSelectable ? { "aria-pressed": !!selected } : {};
|
|
4698
4908
|
const showRing = isSelectable && !isToggle && !hasSelectType && selected;
|
|
4699
4909
|
const indicator = selectType === "radio" ? /* @__PURE__ */ jsx(RadioIndicator, { checked: !!selected, disabled }) : selectType === "checkbox" ? /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected, indeterminate, disabled }) : selectType === "toggle" ? /* @__PURE__ */ jsx(ToggleIndicator, { checked: !!selected, disabled }) : null;
|
|
@@ -4704,8 +4914,8 @@ var Card = forwardRef(function Card2({
|
|
|
4704
4914
|
ref,
|
|
4705
4915
|
"data-card": "",
|
|
4706
4916
|
role,
|
|
4707
|
-
tabIndex: isSelectable ? 0 : void 0,
|
|
4708
|
-
"aria-disabled": isSelectable && disabled ? true : void 0,
|
|
4917
|
+
tabIndex: isSelectable || isAction && !disabled ? 0 : void 0,
|
|
4918
|
+
"aria-disabled": (isSelectable || isAction) && disabled ? true : void 0,
|
|
4709
4919
|
...name && selectType === "radio" ? { "data-radio-group": name } : {},
|
|
4710
4920
|
style: variant === "interactive" ? { ...INTERACTIVE_SCALE_CAP, ...style } : style,
|
|
4711
4921
|
className: cn(
|
|
@@ -4713,7 +4923,7 @@ var Card = forwardRef(function Card2({
|
|
|
4713
4923
|
variantStyles3[variant],
|
|
4714
4924
|
isSelectable && !disabled && "cursor-pointer select-none transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
|
|
4715
4925
|
isSelectable && !disabled && !selected && "hover:border-[var(--ds-accent-primary)]",
|
|
4716
|
-
isSelectable && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
|
|
4926
|
+
(isSelectable || isAction) && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
|
|
4717
4927
|
(showRing || hasSelectType && selected) && ACTIVE_RING,
|
|
4718
4928
|
hasSelectType && selected && "bg-[var(--ds-accent-subtle)]",
|
|
4719
4929
|
disabled && "opacity-disabled cursor-not-allowed",
|
|
@@ -4721,6 +4931,7 @@ var Card = forwardRef(function Card2({
|
|
|
4721
4931
|
),
|
|
4722
4932
|
onClick: handleClick,
|
|
4723
4933
|
onKeyDown: handleKeyDown,
|
|
4934
|
+
onKeyUp: handleKeyUp,
|
|
4724
4935
|
...ariaAttr,
|
|
4725
4936
|
...props,
|
|
4726
4937
|
children: [
|
|
@@ -5278,6 +5489,7 @@ function Table({
|
|
|
5278
5489
|
return /* @__PURE__ */ jsxs(
|
|
5279
5490
|
"div",
|
|
5280
5491
|
{
|
|
5492
|
+
"data-row": "",
|
|
5281
5493
|
className: cn(
|
|
5282
5494
|
"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
5283
5495
|
"rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
|
|
@@ -5830,11 +6042,34 @@ function OverflowPopover({
|
|
|
5830
6042
|
offset: 4,
|
|
5831
6043
|
flip: true
|
|
5832
6044
|
});
|
|
6045
|
+
const { isTop, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => setOpen(false), tabExits: "edges" });
|
|
6046
|
+
useEffect(() => {
|
|
6047
|
+
if (!open) return;
|
|
6048
|
+
const raf = requestAnimationFrame(() => focusInitial(floatingRef.current));
|
|
6049
|
+
const onKey = (e) => {
|
|
6050
|
+
if (e.key === "Escape" && isTop()) {
|
|
6051
|
+
returnFocus();
|
|
6052
|
+
setOpen(false);
|
|
6053
|
+
}
|
|
6054
|
+
};
|
|
6055
|
+
const onDown = (e) => {
|
|
6056
|
+
if (!triggerRef.current?.contains(e.target)) setOpen(false);
|
|
6057
|
+
};
|
|
6058
|
+
document.addEventListener("keydown", onKey);
|
|
6059
|
+
document.addEventListener("mousedown", onDown);
|
|
6060
|
+
return () => {
|
|
6061
|
+
cancelAnimationFrame(raf);
|
|
6062
|
+
document.removeEventListener("keydown", onKey);
|
|
6063
|
+
document.removeEventListener("mousedown", onDown);
|
|
6064
|
+
};
|
|
6065
|
+
}, [open, isTop, returnFocus]);
|
|
5833
6066
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5834
6067
|
/* @__PURE__ */ jsxs(
|
|
5835
6068
|
"button",
|
|
5836
6069
|
{
|
|
5837
6070
|
ref: triggerRef,
|
|
6071
|
+
"aria-haspopup": "dialog",
|
|
6072
|
+
"aria-expanded": open,
|
|
5838
6073
|
className: "text-2xs text-[var(--ds-accent-primary)] font-medium cursor-pointer hover:underline px-ds-1.5",
|
|
5839
6074
|
onClick: (e) => {
|
|
5840
6075
|
e.stopPropagation();
|
|
@@ -5852,6 +6087,8 @@ function OverflowPopover({
|
|
|
5852
6087
|
"div",
|
|
5853
6088
|
{
|
|
5854
6089
|
ref: floatingRef,
|
|
6090
|
+
role: "dialog",
|
|
6091
|
+
"aria-label": date.toLocaleDateString(void 0, { weekday: "long", month: "long", day: "numeric" }),
|
|
5855
6092
|
className: cn(
|
|
5856
6093
|
"fixed z-[var(--ds-z-popover)] w-56 max-h-48 overflow-y-auto",
|
|
5857
6094
|
"bg-[var(--ds-bg-surface-elevated)]",
|
|
@@ -6663,10 +6900,18 @@ function MonthDropdown({
|
|
|
6663
6900
|
setPrevBrowseSyncKey(browseSyncKey);
|
|
6664
6901
|
if (open) setBrowseYear(currentDate.getFullYear());
|
|
6665
6902
|
}
|
|
6903
|
+
const { isTop, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => setOpen(false), tabExits: "edges" });
|
|
6666
6904
|
useEffect(() => {
|
|
6667
6905
|
if (!open) return;
|
|
6906
|
+
const raf = requestAnimationFrame(() => {
|
|
6907
|
+
const panel = floatingRef.current;
|
|
6908
|
+
(panel?.querySelector("[data-current]") ?? panel?.querySelector("button"))?.focus();
|
|
6909
|
+
});
|
|
6668
6910
|
const onKey = (e) => {
|
|
6669
|
-
if (e.key === "Escape"
|
|
6911
|
+
if (e.key === "Escape" && isTop()) {
|
|
6912
|
+
returnFocus();
|
|
6913
|
+
setOpen(false);
|
|
6914
|
+
}
|
|
6670
6915
|
};
|
|
6671
6916
|
const onClick = (e) => {
|
|
6672
6917
|
const t = e.target;
|
|
@@ -6676,10 +6921,11 @@ function MonthDropdown({
|
|
|
6676
6921
|
document.addEventListener("keydown", onKey);
|
|
6677
6922
|
document.addEventListener("mousedown", onClick);
|
|
6678
6923
|
return () => {
|
|
6924
|
+
cancelAnimationFrame(raf);
|
|
6679
6925
|
document.removeEventListener("keydown", onKey);
|
|
6680
6926
|
document.removeEventListener("mousedown", onClick);
|
|
6681
6927
|
};
|
|
6682
|
-
}, [open]);
|
|
6928
|
+
}, [open, isTop, returnFocus]);
|
|
6683
6929
|
const currentMonth = currentDate.getMonth();
|
|
6684
6930
|
const currentYear = currentDate.getFullYear();
|
|
6685
6931
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -6749,7 +6995,9 @@ function MonthDropdown({
|
|
|
6749
6995
|
"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
|
|
6750
6996
|
isActive ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)] hover:text-[var(--ds-text-primary)]"
|
|
6751
6997
|
),
|
|
6998
|
+
"data-current": isActive || void 0,
|
|
6752
6999
|
onClick: () => {
|
|
7000
|
+
returnFocus();
|
|
6753
7001
|
onSelect(new Date(browseYear, i, 1));
|
|
6754
7002
|
setOpen(false);
|
|
6755
7003
|
},
|
|
@@ -6786,10 +7034,18 @@ function YearDropdown({
|
|
|
6786
7034
|
setPrevRangeSyncKey(rangeSyncKey);
|
|
6787
7035
|
if (open) setRangeStart(currentYear - 4);
|
|
6788
7036
|
}
|
|
7037
|
+
const { isTop, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => setOpen(false), tabExits: "edges" });
|
|
6789
7038
|
useEffect(() => {
|
|
6790
7039
|
if (!open) return;
|
|
7040
|
+
const raf = requestAnimationFrame(() => {
|
|
7041
|
+
const panel = floatingRef.current;
|
|
7042
|
+
(panel?.querySelector("[data-current]") ?? panel?.querySelector("button"))?.focus();
|
|
7043
|
+
});
|
|
6791
7044
|
const onKey = (e) => {
|
|
6792
|
-
if (e.key === "Escape"
|
|
7045
|
+
if (e.key === "Escape" && isTop()) {
|
|
7046
|
+
returnFocus();
|
|
7047
|
+
setOpen(false);
|
|
7048
|
+
}
|
|
6793
7049
|
};
|
|
6794
7050
|
const onClick = (e) => {
|
|
6795
7051
|
const t = e.target;
|
|
@@ -6799,10 +7055,11 @@ function YearDropdown({
|
|
|
6799
7055
|
document.addEventListener("keydown", onKey);
|
|
6800
7056
|
document.addEventListener("mousedown", onClick);
|
|
6801
7057
|
return () => {
|
|
7058
|
+
cancelAnimationFrame(raf);
|
|
6802
7059
|
document.removeEventListener("keydown", onKey);
|
|
6803
7060
|
document.removeEventListener("mousedown", onClick);
|
|
6804
7061
|
};
|
|
6805
|
-
}, [open]);
|
|
7062
|
+
}, [open, isTop, returnFocus]);
|
|
6806
7063
|
const years = Array.from({ length: 9 }, (_, i) => rangeStart + i);
|
|
6807
7064
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6808
7065
|
/* @__PURE__ */ jsxs(
|
|
@@ -6871,7 +7128,9 @@ function YearDropdown({
|
|
|
6871
7128
|
"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
|
|
6872
7129
|
y === currentYear ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)] hover:text-[var(--ds-text-primary)]"
|
|
6873
7130
|
),
|
|
7131
|
+
"data-current": y === currentYear || void 0,
|
|
6874
7132
|
onClick: () => {
|
|
7133
|
+
returnFocus();
|
|
6875
7134
|
onSelect(new Date(y, currentDate.getMonth(), 1));
|
|
6876
7135
|
setOpen(false);
|
|
6877
7136
|
},
|
|
@@ -6947,7 +7206,7 @@ function Calendar({
|
|
|
6947
7206
|
if (currentView === "month") {
|
|
6948
7207
|
setCurrentDate(addMonths(currentDate, direction));
|
|
6949
7208
|
} else if (currentView === "week") {
|
|
6950
|
-
setCurrentDate(
|
|
7209
|
+
setCurrentDate(addDays2(currentDate, direction * 7));
|
|
6951
7210
|
} else {
|
|
6952
7211
|
setCurrentDate(new Date(currentDate.getFullYear() + direction, currentDate.getMonth(), 1));
|
|
6953
7212
|
}
|
|
@@ -9600,8 +9859,11 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
|
|
|
9600
9859
|
container
|
|
9601
9860
|
}, ref) {
|
|
9602
9861
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
9862
|
+
const [activeIdx, setActiveIdx] = useState(0);
|
|
9603
9863
|
const ellipsisBtnRef = useRef(null);
|
|
9604
9864
|
const dropdownRef = useRef(null);
|
|
9865
|
+
const closeDropdown = useCallback(() => setDropdownOpen(false), []);
|
|
9866
|
+
const { isTop, returnFocus } = usePopupFocus(dropdownOpen, { trigger: ellipsisBtnRef, panel: dropdownRef, close: closeDropdown, tabExits: "any" });
|
|
9605
9867
|
const pos = usePosition(ellipsisBtnRef, dropdownRef, dropdownOpen, { side: "bottom", align: "start", offset: 4, flip: true });
|
|
9606
9868
|
const effectiveMaxItems = maxItems ? Math.max(maxItems, 2) : void 0;
|
|
9607
9869
|
const shouldCollapse = effectiveMaxItems && items.length > effectiveMaxItems;
|
|
@@ -9614,9 +9876,31 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
|
|
|
9614
9876
|
if (ellipsisBtnRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
|
|
9615
9877
|
setDropdownOpen(false);
|
|
9616
9878
|
};
|
|
9879
|
+
const onKeyDown = (e) => {
|
|
9880
|
+
if (e.key === "Escape" && isTop()) {
|
|
9881
|
+
returnFocus();
|
|
9882
|
+
setDropdownOpen(false);
|
|
9883
|
+
}
|
|
9884
|
+
};
|
|
9617
9885
|
document.addEventListener("mousedown", handler);
|
|
9618
|
-
|
|
9619
|
-
|
|
9886
|
+
document.addEventListener("keydown", onKeyDown);
|
|
9887
|
+
return () => {
|
|
9888
|
+
document.removeEventListener("mousedown", handler);
|
|
9889
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
9890
|
+
};
|
|
9891
|
+
}, [dropdownOpen, isTop, returnFocus]);
|
|
9892
|
+
useEffect(() => {
|
|
9893
|
+
if (!dropdownOpen) return;
|
|
9894
|
+
const id = requestAnimationFrame(() => dropdownRef.current?.querySelector(`[data-index="${activeIdx}"]`)?.focus());
|
|
9895
|
+
return () => cancelAnimationFrame(id);
|
|
9896
|
+
}, [dropdownOpen, activeIdx]);
|
|
9897
|
+
function onMenuKeyDown(e) {
|
|
9898
|
+
const last = collapsedItems.length - 1;
|
|
9899
|
+
const next = e.key === "ArrowDown" ? activeIdx >= last ? 0 : activeIdx + 1 : e.key === "ArrowUp" ? activeIdx <= 0 ? last : activeIdx - 1 : e.key === "Home" ? 0 : e.key === "End" ? last : null;
|
|
9900
|
+
if (next === null) return;
|
|
9901
|
+
e.preventDefault();
|
|
9902
|
+
setActiveIdx(next);
|
|
9903
|
+
}
|
|
9620
9904
|
const textClass = size === "sm" ? "text-xs" : "text-sm";
|
|
9621
9905
|
const gapClass = size === "sm" ? "gap-ds-1" : "gap-ds-1.5";
|
|
9622
9906
|
const iconSize = size === "sm" ? 12 : 14;
|
|
@@ -9681,7 +9965,10 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
|
|
|
9681
9965
|
"button",
|
|
9682
9966
|
{
|
|
9683
9967
|
ref: ellipsisBtnRef,
|
|
9684
|
-
onClick: () =>
|
|
9968
|
+
onClick: () => {
|
|
9969
|
+
if (!dropdownOpen) setActiveIdx(0);
|
|
9970
|
+
setDropdownOpen(!dropdownOpen);
|
|
9971
|
+
},
|
|
9685
9972
|
"aria-label": "Show hidden breadcrumbs",
|
|
9686
9973
|
"aria-expanded": dropdownOpen,
|
|
9687
9974
|
"aria-haspopup": "menu",
|
|
@@ -9712,6 +9999,8 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
|
|
|
9712
9999
|
{
|
|
9713
10000
|
ref: dropdownRef,
|
|
9714
10001
|
role: "menu",
|
|
10002
|
+
"aria-label": "Hidden breadcrumbs",
|
|
10003
|
+
onKeyDown: onMenuKeyDown,
|
|
9715
10004
|
className: cn(
|
|
9716
10005
|
"fixed z-[var(--ds-z-popover)] min-w-[160px] max-h-60 overflow-auto",
|
|
9717
10006
|
"bg-[var(--ds-bg-surface-elevated)]",
|
|
@@ -9725,12 +10014,15 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
|
|
|
9725
10014
|
children: collapsedItems.map((ci, idx) => /* @__PURE__ */ jsxs(
|
|
9726
10015
|
"button",
|
|
9727
10016
|
{
|
|
10017
|
+
"data-index": idx,
|
|
9728
10018
|
role: "menuitem",
|
|
10019
|
+
tabIndex: idx === activeIdx ? 0 : -1,
|
|
9729
10020
|
className: cn(
|
|
9730
10021
|
"flex items-center w-full text-left text-sm cursor-pointer",
|
|
9731
10022
|
"px-ds-3 py-ds-1.5 transition-colors",
|
|
9732
10023
|
"text-[var(--ds-text-secondary)]",
|
|
9733
10024
|
"hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
|
|
10025
|
+
"focus-visible:outline-none focus-visible:bg-[var(--ds-bg-muted)] focus-visible:text-[var(--ds-text-primary)] focus-visible:shadow-[inset_var(--ds-shadow-focus)]",
|
|
9734
10026
|
gapClass
|
|
9735
10027
|
),
|
|
9736
10028
|
onClick: () => {
|
|
@@ -9788,14 +10080,13 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
|
|
|
9788
10080
|
setTriggerWidth(node?.offsetWidth);
|
|
9789
10081
|
}, []);
|
|
9790
10082
|
const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
|
|
9791
|
-
const isTopLayer =
|
|
9792
|
-
useCloseOnFocusOut(open, triggerRef, floatingRef, () => onOpenChange(false), isTopLayer);
|
|
10083
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => onOpenChange(false), tabExits: "edges" });
|
|
9793
10084
|
useEffect(() => {
|
|
9794
10085
|
if (!open) return;
|
|
9795
10086
|
const onKeyDown = (e) => {
|
|
9796
10087
|
if (e.key === "Escape" && isTopLayer()) {
|
|
10088
|
+
returnFocus();
|
|
9797
10089
|
onOpenChange(false);
|
|
9798
|
-
triggerRef.current?.focus();
|
|
9799
10090
|
}
|
|
9800
10091
|
};
|
|
9801
10092
|
const onClick = (e) => {
|
|
@@ -9809,7 +10100,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
|
|
|
9809
10100
|
document.removeEventListener("keydown", onKeyDown);
|
|
9810
10101
|
document.removeEventListener("mousedown", onClick);
|
|
9811
10102
|
};
|
|
9812
|
-
}, [open, onOpenChange, isTopLayer]);
|
|
10103
|
+
}, [open, onOpenChange, isTopLayer, returnFocus]);
|
|
9813
10104
|
useEffect(() => {
|
|
9814
10105
|
if (open) focusInitial(floatingRef.current);
|
|
9815
10106
|
}, [open]);
|
|
@@ -9831,6 +10122,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
|
|
|
9831
10122
|
{
|
|
9832
10123
|
ref: floatingRef,
|
|
9833
10124
|
"data-gs": "popover",
|
|
10125
|
+
onClick: (e) => e.stopPropagation(),
|
|
9834
10126
|
className: cn(
|
|
9835
10127
|
"fixed z-[var(--ds-z-popover)]",
|
|
9836
10128
|
"bg-[var(--ds-bg-surface-elevated)]",
|
|
@@ -10510,17 +10802,39 @@ function CompactFlyout({
|
|
|
10510
10802
|
onItemClick,
|
|
10511
10803
|
onClose,
|
|
10512
10804
|
onMouseEnter,
|
|
10513
|
-
onMouseLeave
|
|
10805
|
+
onMouseLeave,
|
|
10806
|
+
focusIn = false
|
|
10514
10807
|
}) {
|
|
10515
10808
|
const pos = useMemo(() => computeCompactFlyoutPos(anchorEl, items.length), [anchorEl, items.length]);
|
|
10516
|
-
const
|
|
10809
|
+
const anchorRef = useRef(anchorEl);
|
|
10810
|
+
const panelRef = useRef(null);
|
|
10811
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(true, { trigger: anchorRef, panel: panelRef, close: onClose, tabExits: "edges" });
|
|
10812
|
+
useEffect(() => {
|
|
10813
|
+
if (!focusIn) return;
|
|
10814
|
+
const raf = requestAnimationFrame(() => panelRef.current?.querySelector('[role="menuitem"]')?.focus());
|
|
10815
|
+
return () => cancelAnimationFrame(raf);
|
|
10816
|
+
}, [focusIn]);
|
|
10517
10817
|
useEffect(() => {
|
|
10518
10818
|
function handler(e) {
|
|
10519
|
-
if (
|
|
10819
|
+
if (!isTopLayer()) return;
|
|
10820
|
+
const inside = !!panelRef.current?.contains(document.activeElement);
|
|
10821
|
+
if (e.key === "Escape" || e.key === "ArrowLeft" && inside) {
|
|
10822
|
+
e.preventDefault();
|
|
10823
|
+
if (inside) returnFocus();
|
|
10824
|
+
onClose();
|
|
10825
|
+
} else if (inside && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
10826
|
+
const links = Array.from(panelRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
10827
|
+
const at = links.indexOf(document.activeElement);
|
|
10828
|
+
const next = links[(at + (e.key === "ArrowDown" ? 1 : -1) + links.length) % links.length];
|
|
10829
|
+
if (next) {
|
|
10830
|
+
e.preventDefault();
|
|
10831
|
+
next.focus();
|
|
10832
|
+
}
|
|
10833
|
+
}
|
|
10520
10834
|
}
|
|
10521
10835
|
document.addEventListener("keydown", handler);
|
|
10522
10836
|
return () => document.removeEventListener("keydown", handler);
|
|
10523
|
-
}, [onClose, isTopLayer]);
|
|
10837
|
+
}, [onClose, isTopLayer, returnFocus]);
|
|
10524
10838
|
const content = /* @__PURE__ */ jsx(
|
|
10525
10839
|
"div",
|
|
10526
10840
|
{
|
|
@@ -10531,7 +10845,9 @@ function CompactFlyout({
|
|
|
10531
10845
|
children: /* @__PURE__ */ jsxs(
|
|
10532
10846
|
"div",
|
|
10533
10847
|
{
|
|
10848
|
+
ref: panelRef,
|
|
10534
10849
|
role: "menu",
|
|
10850
|
+
"aria-label": parentLabel,
|
|
10535
10851
|
className: cn(
|
|
10536
10852
|
"min-w-[160px] py-ds-1",
|
|
10537
10853
|
"bg-[var(--ds-sidebar-bg)] rounded-[var(--ds-radius-md)]",
|
|
@@ -10908,6 +11224,8 @@ function CompactNavItem({
|
|
|
10908
11224
|
onToggleExpand
|
|
10909
11225
|
}) {
|
|
10910
11226
|
const [triggerEl, setTriggerEl] = useState(null);
|
|
11227
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
11228
|
+
if (!isOpen && keyboardOpen) setKeyboardOpen(false);
|
|
10911
11229
|
const handleMouseEnter = useCallback(() => {
|
|
10912
11230
|
onCancelClose();
|
|
10913
11231
|
if (hasChildren && !isOpen) onOpen(flyoutKey);
|
|
@@ -10930,6 +11248,14 @@ function CompactNavItem({
|
|
|
10930
11248
|
onMouseEnter: !mobile && hasChildren ? handleMouseEnter : void 0,
|
|
10931
11249
|
onMouseLeave: !mobile && hasChildren && isOpen ? handleMouseLeave : void 0,
|
|
10932
11250
|
onClick: handleClick,
|
|
11251
|
+
onKeyDown: !mobile && hasChildren ? (e) => {
|
|
11252
|
+
if (e.key !== "ArrowRight") return;
|
|
11253
|
+
e.preventDefault();
|
|
11254
|
+
onCancelClose();
|
|
11255
|
+
setKeyboardOpen(true);
|
|
11256
|
+
onOpen(flyoutKey);
|
|
11257
|
+
} : void 0,
|
|
11258
|
+
"aria-haspopup": !mobile && hasChildren ? "menu" : void 0,
|
|
10933
11259
|
"aria-expanded": hasChildren ? mobile ? expanded : isOpen : void 0,
|
|
10934
11260
|
className: cn(
|
|
10935
11261
|
"relative flex flex-col items-center justify-center gap-[3px] w-full py-ds-3 px-ds-1 rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
|
|
@@ -10972,7 +11298,8 @@ function CompactNavItem({
|
|
|
10972
11298
|
onItemClick,
|
|
10973
11299
|
onClose,
|
|
10974
11300
|
onMouseEnter: onCancelClose,
|
|
10975
|
-
onMouseLeave: handleMouseLeave
|
|
11301
|
+
onMouseLeave: handleMouseLeave,
|
|
11302
|
+
focusIn: keyboardOpen
|
|
10976
11303
|
}
|
|
10977
11304
|
),
|
|
10978
11305
|
mobile && expanded && hasChildren && item.children && /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col gap-ds-1 pb-ds-1", children: item.children.map((child, ci) => /* @__PURE__ */ jsxs(
|
|
@@ -12326,12 +12653,11 @@ function NavMenu({
|
|
|
12326
12653
|
className
|
|
12327
12654
|
}) {
|
|
12328
12655
|
const [open, setOpen] = useState(false);
|
|
12329
|
-
const isTopLayer = useOverlayLayer(open);
|
|
12330
12656
|
const triggerRef = useRef(null);
|
|
12331
12657
|
const panelRef = useRef(null);
|
|
12332
12658
|
const [pos, setPos] = useState({ top: 0, left: 0, minWidth: 0 });
|
|
12333
12659
|
const close = useCallback(() => setOpen(false), []);
|
|
12334
|
-
|
|
12660
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close, tabExits: "edges" });
|
|
12335
12661
|
useEffect(() => {
|
|
12336
12662
|
if (!open) return;
|
|
12337
12663
|
const id = requestAnimationFrame(() => focusInitial(panelRef.current));
|
|
@@ -12355,8 +12681,8 @@ function NavMenu({
|
|
|
12355
12681
|
}
|
|
12356
12682
|
function handleKey(e) {
|
|
12357
12683
|
if (e.key === "Escape" && isTopLayer()) {
|
|
12684
|
+
returnFocus();
|
|
12358
12685
|
close();
|
|
12359
|
-
triggerRef.current?.focus();
|
|
12360
12686
|
}
|
|
12361
12687
|
}
|
|
12362
12688
|
document.addEventListener("mousedown", handleClick);
|
|
@@ -12365,7 +12691,7 @@ function NavMenu({
|
|
|
12365
12691
|
document.removeEventListener("mousedown", handleClick);
|
|
12366
12692
|
document.removeEventListener("keydown", handleKey);
|
|
12367
12693
|
};
|
|
12368
|
-
}, [open, close, isTopLayer]);
|
|
12694
|
+
}, [open, close, isTopLayer, returnFocus]);
|
|
12369
12695
|
const hasActiveChild = active || items.some(
|
|
12370
12696
|
(it) => it.active || it.children?.some((c) => c.active)
|
|
12371
12697
|
);
|
|
@@ -12932,15 +13258,20 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
|
|
|
12932
13258
|
const isMobile = useBreakpoint("sm");
|
|
12933
13259
|
const overlayRef = useRef(null);
|
|
12934
13260
|
const dialogRef = useRef(null);
|
|
12935
|
-
const
|
|
13261
|
+
const opener = useRef(null);
|
|
12936
13262
|
const defaultButtonRef = useRef(null);
|
|
12937
13263
|
const onCloseRef = useLatest(onClose);
|
|
12938
13264
|
const onSubmitRef = useLatest(onSubmit);
|
|
12939
13265
|
const isTopLayer = useOverlayLayer(open);
|
|
12940
13266
|
useFocusTrap(dialogRef, open, isTopLayer);
|
|
13267
|
+
const before = useFocusBeforeOpen(open);
|
|
13268
|
+
useEffect(() => {
|
|
13269
|
+
if (!open) return;
|
|
13270
|
+
const active = before.current;
|
|
13271
|
+
opener.current = active instanceof HTMLElement && active !== document.body && active.isConnected ? rememberFocus(active) : null;
|
|
13272
|
+
}, [open, before]);
|
|
12941
13273
|
useEffect(() => {
|
|
12942
13274
|
if (!open) return;
|
|
12943
|
-
triggerRef.current = document.activeElement;
|
|
12944
13275
|
requestAnimationFrame(() => {
|
|
12945
13276
|
focusInitial(dialogRef.current, { destructive: variant === "destructive" });
|
|
12946
13277
|
});
|
|
@@ -12960,7 +13291,10 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
|
|
|
12960
13291
|
lockBodyScroll();
|
|
12961
13292
|
return () => {
|
|
12962
13293
|
unlockBodyScroll();
|
|
12963
|
-
|
|
13294
|
+
const r = opener.current;
|
|
13295
|
+
opener.current = null;
|
|
13296
|
+
const active = document.activeElement;
|
|
13297
|
+
if (!active || active === document.body || !active.isConnected) r?.restore();
|
|
12964
13298
|
};
|
|
12965
13299
|
}, [open]);
|
|
12966
13300
|
if (!open) return null;
|
|
@@ -13078,6 +13412,7 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
|
|
|
13078
13412
|
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
13079
13413
|
const inputRef = useRef(null);
|
|
13080
13414
|
const listRef = useRef(null);
|
|
13415
|
+
const dialogRef = useRef(null);
|
|
13081
13416
|
const listboxId = useId();
|
|
13082
13417
|
const filtered = commands.filter(
|
|
13083
13418
|
(c) => c.label.toLowerCase().includes(query.toLowerCase()) || c.group?.toLowerCase().includes(query.toLowerCase())
|
|
@@ -13106,6 +13441,8 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
|
|
|
13106
13441
|
if (open) requestAnimationFrame(() => inputRef.current?.focus());
|
|
13107
13442
|
}, [open]);
|
|
13108
13443
|
const isTopLayer = useOverlayLayer(open);
|
|
13444
|
+
useFocusTrap(dialogRef, open, isTopLayer);
|
|
13445
|
+
const returnFocus = useReturnFocus(open, dialogRef);
|
|
13109
13446
|
useEffect(() => {
|
|
13110
13447
|
if (!open) return;
|
|
13111
13448
|
const handler = (e) => {
|
|
@@ -13116,10 +13453,11 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
|
|
|
13116
13453
|
}, [open, onOpenChange, isTopLayer]);
|
|
13117
13454
|
const handleSelect = useCallback(
|
|
13118
13455
|
(cmd) => {
|
|
13456
|
+
returnFocus();
|
|
13119
13457
|
cmd.onSelect();
|
|
13120
13458
|
onOpenChange(false);
|
|
13121
13459
|
},
|
|
13122
|
-
[onOpenChange]
|
|
13460
|
+
[onOpenChange, returnFocus]
|
|
13123
13461
|
);
|
|
13124
13462
|
const handleKeyDown = (e) => {
|
|
13125
13463
|
if (e.key === "ArrowDown") {
|
|
@@ -13150,8 +13488,10 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
|
|
|
13150
13488
|
/* @__PURE__ */ jsxs(
|
|
13151
13489
|
"div",
|
|
13152
13490
|
{
|
|
13491
|
+
ref: dialogRef,
|
|
13153
13492
|
role: "dialog",
|
|
13154
13493
|
"aria-modal": "true",
|
|
13494
|
+
"aria-label": "Command palette",
|
|
13155
13495
|
className: cn(
|
|
13156
13496
|
"relative z-10 w-full max-w-lg",
|
|
13157
13497
|
"bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
|
|
@@ -13177,6 +13517,7 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
|
|
|
13177
13517
|
"aria-expanded": true,
|
|
13178
13518
|
"aria-controls": listboxId,
|
|
13179
13519
|
"aria-autocomplete": "list",
|
|
13520
|
+
"aria-activedescendant": flatFiltered[selectedIdx] ? `${listboxId}-opt-${selectedIdx}` : void 0,
|
|
13180
13521
|
value: query,
|
|
13181
13522
|
onChange: (e) => setQuery(e.target.value),
|
|
13182
13523
|
onKeyDown: handleKeyDown,
|
|
@@ -13210,7 +13551,9 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
|
|
|
13210
13551
|
return /* @__PURE__ */ jsxs(
|
|
13211
13552
|
"button",
|
|
13212
13553
|
{
|
|
13554
|
+
id: `${listboxId}-opt-${myIdx}`,
|
|
13213
13555
|
role: "option",
|
|
13556
|
+
tabIndex: -1,
|
|
13214
13557
|
"aria-selected": myIdx === selectedIdx,
|
|
13215
13558
|
onClick: () => handleSelect(cmd),
|
|
13216
13559
|
onMouseEnter: () => setSelectedIdx(myIdx),
|
|
@@ -13469,8 +13812,7 @@ function NotificationCenter({
|
|
|
13469
13812
|
const setFilter = onFilterChange ?? setInternalFilter;
|
|
13470
13813
|
const unreadCount = notifications.filter((n) => !n.read).length;
|
|
13471
13814
|
const filteredNotifications = filter === "unread" ? notifications.filter((n) => !n.read) : notifications;
|
|
13472
|
-
const isTopLayer =
|
|
13473
|
-
useCloseOnFocusOut(open, triggerRef, panelRef, () => setOpen(false), isTopLayer);
|
|
13815
|
+
const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close: () => setOpen(false), tabExits: "edges" });
|
|
13474
13816
|
useEffect(() => {
|
|
13475
13817
|
if (!open) return;
|
|
13476
13818
|
const id = requestAnimationFrame(() => focusInitial(panelRef.current));
|
|
@@ -13480,8 +13822,8 @@ function NotificationCenter({
|
|
|
13480
13822
|
if (!open) return;
|
|
13481
13823
|
const onKey = (e) => {
|
|
13482
13824
|
if (e.key === "Escape" && isTopLayer()) {
|
|
13825
|
+
returnFocus();
|
|
13483
13826
|
setOpen(false);
|
|
13484
|
-
triggerRef.current?.focus();
|
|
13485
13827
|
}
|
|
13486
13828
|
};
|
|
13487
13829
|
const onClick = (e) => {
|
|
@@ -13495,7 +13837,7 @@ function NotificationCenter({
|
|
|
13495
13837
|
document.removeEventListener("keydown", onKey);
|
|
13496
13838
|
document.removeEventListener("mousedown", onClick);
|
|
13497
13839
|
};
|
|
13498
|
-
}, [open, setOpen, isTopLayer]);
|
|
13840
|
+
}, [open, setOpen, isTopLayer, returnFocus]);
|
|
13499
13841
|
const grouped = groupByDate(filteredNotifications);
|
|
13500
13842
|
if (variant === "page") {
|
|
13501
13843
|
return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
|
|
@@ -13768,6 +14110,15 @@ function Tour({ steps, open, onClose, onComplete, container }) {
|
|
|
13768
14110
|
if (current > 0) setCurrent((c) => c - 1);
|
|
13769
14111
|
}, [current]);
|
|
13770
14112
|
const isTopLayer = useOverlayLayer(open);
|
|
14113
|
+
const nextRef = useRef(null);
|
|
14114
|
+
const titleId = useId();
|
|
14115
|
+
useFocusTrap(popoverRef, open, isTopLayer);
|
|
14116
|
+
useReturnFocus(open, popoverRef);
|
|
14117
|
+
useEffect(() => {
|
|
14118
|
+
if (!open) return;
|
|
14119
|
+
const raf = requestAnimationFrame(() => nextRef.current?.focus());
|
|
14120
|
+
return () => cancelAnimationFrame(raf);
|
|
14121
|
+
}, [open, current]);
|
|
13771
14122
|
useEffect(() => {
|
|
13772
14123
|
if (!open) return;
|
|
13773
14124
|
const onKey = (e) => {
|
|
@@ -13820,6 +14171,9 @@ function Tour({ steps, open, onClose, onComplete, container }) {
|
|
|
13820
14171
|
"div",
|
|
13821
14172
|
{
|
|
13822
14173
|
ref: popoverRef,
|
|
14174
|
+
role: "dialog",
|
|
14175
|
+
"aria-modal": "true",
|
|
14176
|
+
"aria-labelledby": titleId,
|
|
13823
14177
|
className: cn(
|
|
13824
14178
|
"fixed z-[2] w-[300px]",
|
|
13825
14179
|
"bg-[var(--ds-bg-surface-elevated)]",
|
|
@@ -13832,7 +14186,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
|
|
|
13832
14186
|
children: [
|
|
13833
14187
|
/* @__PURE__ */ jsxs("div", { className: "px-ds-4 pt-ds-4 pb-ds-3", children: [
|
|
13834
14188
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-ds-2 mb-ds-1", children: [
|
|
13835
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: step.title }),
|
|
14189
|
+
/* @__PURE__ */ jsx("h3", { id: titleId, className: "text-sm font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: step.title }),
|
|
13836
14190
|
/* @__PURE__ */ jsx(
|
|
13837
14191
|
"button",
|
|
13838
14192
|
{
|
|
@@ -13868,6 +14222,7 @@ function Tour({ steps, open, onClose, onComplete, container }) {
|
|
|
13868
14222
|
/* @__PURE__ */ jsx(
|
|
13869
14223
|
"button",
|
|
13870
14224
|
{
|
|
14225
|
+
ref: nextRef,
|
|
13871
14226
|
onClick: handleNext,
|
|
13872
14227
|
className: cn(
|
|
13873
14228
|
"px-ds-3 py-ds-1.5 text-xs font-medium rounded-[var(--ds-radius-sm)]",
|