@stevederico/skateboard-ui 4.9.0 → 4.11.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/CHANGELOG.md +20 -0
- package/dist/App.js +1 -3
- package/dist/components/core/Calendar.js +26 -2
- package/dist/components/core/Command.d.ts +8 -16
- package/dist/components/core/Command.js +25 -19
- package/dist/components/core/Context.js +3 -1
- package/dist/components/core/Utilities.js +13 -4
- package/dist/components/layout/TabBar.js +2 -1
- package/dist/components/views/LandingView.js +1 -1
- package/dist/components/views/NotFound.d.ts +3 -4
- package/dist/components/views/SettingsView.js +8 -3
- package/dist/components/views/SignInView.js +7 -1
- package/dist/components/views/SignUpView.js +8 -1
- package/dist/ui/accordion.js +32 -10
- package/dist/ui/alert-dialog.js +7 -4
- package/dist/ui/avatar.js +14 -1
- package/dist/ui/context-menu.js +62 -9
- package/dist/ui/dialog.js +7 -5
- package/dist/ui/drawer.js +7 -4
- package/dist/ui/dropdown-menu.d.ts +2 -2
- package/dist/ui/dropdown-menu.js +88 -11
- package/dist/ui/field.d.ts +11 -10
- package/dist/ui/field.js +2 -2
- package/dist/ui/hover-card.js +1 -1
- package/dist/ui/input-group.js +3 -1
- package/dist/ui/layer-stack.d.ts +2 -0
- package/dist/ui/layer-stack.js +16 -0
- package/dist/ui/menubar.js +51 -8
- package/dist/ui/popover.js +34 -0
- package/dist/ui/radio-group.js +12 -1
- package/dist/ui/select.js +4 -5
- package/dist/ui/sheet.js +7 -4
- package/dist/ui/slider.js +51 -11
- package/dist/ui/slot.js +5 -3
- package/dist/ui/tabs.js +15 -8
- package/dist/ui/use-dismiss.js +3 -0
- package/package.json +1 -1
package/dist/ui/radio-group.js
CHANGED
|
@@ -53,8 +53,19 @@ function RadioGroupItem({ className, value, onClick, disabled, ref, ...props })
|
|
|
53
53
|
if (selected !== undefined)
|
|
54
54
|
return;
|
|
55
55
|
const el = btnRef.current;
|
|
56
|
-
|
|
56
|
+
// Scope to the radiogroup (not the immediate parent) so per-item wrappers
|
|
57
|
+
// like <label>/<div> don't change which radio counts as "first enabled" —
|
|
58
|
+
// this matches the arrow-key handler, which queries the group element.
|
|
59
|
+
const group = el?.closest('[role="radiogroup"]');
|
|
60
|
+
const first = (group ?? el)?.querySelector('[role="radio"]:not([disabled]):not([data-disabled])');
|
|
57
61
|
setIsFirstEnabled(first === el);
|
|
62
|
+
// No dependency array on purpose: "first enabled" depends on the whole
|
|
63
|
+
// sibling set, so an item must recompute when ANY sibling's disabled state
|
|
64
|
+
// or order changes — a per-item dep list ([selected, value, disabled])
|
|
65
|
+
// misses those cross-sibling changes and can leave the group with zero tab
|
|
66
|
+
// stops. The body is cheap (two querySelectors) and setIsFirstEnabled bails
|
|
67
|
+
// out of re-render when the value is unchanged, so running every render is
|
|
68
|
+
// negligible.
|
|
58
69
|
});
|
|
59
70
|
const tabbable = checked || (selected === undefined && isFirstEnabled);
|
|
60
71
|
return (_jsx("button", { ref: mergeRefs(btnRef, ref), type: "button", role: "radio", "aria-checked": checked, disabled: disabled, tabIndex: tabbable ? 0 : -1, "data-slot": "radio-group-item", "data-state": checked ? "checked" : "unchecked", "data-checked": checked ? "" : undefined, className: cn("cursor-pointer group/radio-group-item peer relative flex aspect-square size-4 shrink-0 items-center justify-center rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary", className), onClick: (e) => {
|
package/dist/ui/select.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { cn } from "../shadcn/lib/utils.js";
|
|
5
5
|
import { mergeRefs } from "./slot.js";
|
|
6
6
|
import { Portal } from "./portal.js";
|
|
7
|
+
import { registerLayer } from "./layer-stack.js";
|
|
7
8
|
import { useFloating } from "./use-floating.js";
|
|
8
9
|
import { usePresence } from "./use-presence.js";
|
|
9
10
|
import { useControllableState } from "./use-controllable-state.js";
|
|
@@ -33,10 +34,6 @@ function Select({ value, defaultValue, onValueChange, open, defaultOpen = false,
|
|
|
33
34
|
const triggerId = React.useId();
|
|
34
35
|
const [labels, setLabels] = React.useState({});
|
|
35
36
|
const registerLabel = React.useCallback((v, label) => setLabels((prev) => ({ ...prev, [v]: label })), []);
|
|
36
|
-
const unregisterLabel = React.useCallback((v) => setLabels((prev) => {
|
|
37
|
-
const { [v]: _, ...rest } = prev;
|
|
38
|
-
return rest;
|
|
39
|
-
}), []);
|
|
40
37
|
const setValue = React.useCallback((v) => {
|
|
41
38
|
setSelected(v);
|
|
42
39
|
setOpen(false);
|
|
@@ -52,7 +49,6 @@ function Select({ value, defaultValue, onValueChange, open, defaultOpen = false,
|
|
|
52
49
|
triggerId,
|
|
53
50
|
labels,
|
|
54
51
|
registerLabel,
|
|
55
|
-
unregisterLabel,
|
|
56
52
|
}, children: children }));
|
|
57
53
|
}
|
|
58
54
|
function SelectGroup({ className, ...props }) {
|
|
@@ -124,6 +120,8 @@ function SelectContent({ className, children, side = "bottom", sideOffset = 4, a
|
|
|
124
120
|
React.useEffect(() => {
|
|
125
121
|
if (!open)
|
|
126
122
|
return;
|
|
123
|
+
const node = containerRef.current;
|
|
124
|
+
const off = node ? registerLayer(node) : undefined;
|
|
127
125
|
const onPointerDown = (e) => {
|
|
128
126
|
const t = e.target;
|
|
129
127
|
if (triggerRef.current?.contains(t))
|
|
@@ -135,6 +133,7 @@ function SelectContent({ className, children, side = "bottom", sideOffset = 4, a
|
|
|
135
133
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
136
134
|
return () => {
|
|
137
135
|
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
136
|
+
off?.();
|
|
138
137
|
};
|
|
139
138
|
}, [open, setOpen, triggerRef]);
|
|
140
139
|
// While closed, render the items into a hidden tree so their value→label
|
package/dist/ui/sheet.js
CHANGED
|
@@ -28,9 +28,12 @@ function Sheet({ open, defaultOpen = false, onOpenChange, children }) {
|
|
|
28
28
|
// Title/Description part is actually rendered, so the dialog never points at a
|
|
29
29
|
// missing element (a dangling IDREF that screen readers announce as broken).
|
|
30
30
|
const { titleId, descriptionId, registerTitle, registerDescription, labelledBy, describedBy } = useDialogLabelling();
|
|
31
|
+
// Stable id wiring the trigger's aria-controls to the content's id.
|
|
32
|
+
const contentId = React.useId();
|
|
31
33
|
return (_jsx(SheetContext.Provider, { value: {
|
|
32
34
|
open: isOpen,
|
|
33
35
|
setOpen,
|
|
36
|
+
contentId,
|
|
34
37
|
titleId,
|
|
35
38
|
descriptionId,
|
|
36
39
|
registerTitle,
|
|
@@ -40,10 +43,10 @@ function Sheet({ open, defaultOpen = false, onOpenChange, children }) {
|
|
|
40
43
|
}, children: children }));
|
|
41
44
|
}
|
|
42
45
|
function SheetTrigger({ asChild = false, render, nativeButton: _nativeButton, children, onClick, ...props }) {
|
|
43
|
-
const { open, setOpen } = useSheet();
|
|
46
|
+
const { open, setOpen, contentId } = useSheet();
|
|
44
47
|
const { useSlot, slotChild } = resolveRender(asChild, render, children);
|
|
45
48
|
const Comp = useSlot ? Slot : "button";
|
|
46
|
-
return (_jsx(Comp, { type: useSlot ? undefined : "button", "data-slot": "sheet-trigger", "aria-haspopup": "dialog", "data-state": open ? "open" : "closed", onClick: (e) => {
|
|
49
|
+
return (_jsx(Comp, { type: useSlot ? undefined : "button", "data-slot": "sheet-trigger", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? contentId : undefined, "data-state": open ? "open" : "closed", onClick: (e) => {
|
|
47
50
|
onClick?.(e);
|
|
48
51
|
if (!e.defaultPrevented)
|
|
49
52
|
setOpen(true);
|
|
@@ -80,7 +83,7 @@ const SIDE_OUT = {
|
|
|
80
83
|
bottom: "slide-out-to-bottom-52",
|
|
81
84
|
};
|
|
82
85
|
function SheetContent({ className, children, side = "right", showCloseButton = true, ...props }) {
|
|
83
|
-
const { open, setOpen, labelledBy, describedBy } = useSheet();
|
|
86
|
+
const { open, setOpen, contentId, labelledBy, describedBy } = useSheet();
|
|
84
87
|
const ref = React.useRef(null);
|
|
85
88
|
const pointerDownOnBackdrop = React.useRef(false);
|
|
86
89
|
const [mounted, presenceRef] = usePresence(open);
|
|
@@ -98,7 +101,7 @@ function SheetContent({ className, children, side = "right", showCloseButton = t
|
|
|
98
101
|
useScrollLock(mounted);
|
|
99
102
|
if (!mounted)
|
|
100
103
|
return null;
|
|
101
|
-
return (_jsxs("dialog", { ...props, ref: mergeRefs(ref, presenceRef), "aria-labelledby": labelledBy, "aria-describedby": describedBy, "data-slot": "sheet-content", "data-side": side, "data-state": open ? "open" : "closed", "data-open": open ? "" : undefined, "data-closed": open ? undefined : "", onCancel: (e) => {
|
|
104
|
+
return (_jsxs("dialog", { ...props, ref: mergeRefs(ref, presenceRef), id: contentId, "aria-labelledby": labelledBy, "aria-describedby": describedBy, "data-slot": "sheet-content", "data-side": side, "data-state": open ? "open" : "closed", "data-open": open ? "" : undefined, "data-closed": open ? undefined : "", onCancel: (e) => {
|
|
102
105
|
e.preventDefault();
|
|
103
106
|
setOpen(false);
|
|
104
107
|
}, onClose: () => setOpen(false), onPointerDown: (e) => {
|
package/dist/ui/slider.js
CHANGED
|
@@ -4,6 +4,17 @@ import * as React from "react";
|
|
|
4
4
|
import { cn } from "../shadcn/lib/utils.js";
|
|
5
5
|
import { useControllableState } from "./use-controllable-state.js";
|
|
6
6
|
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
7
|
+
// Snap a value to the step grid relative to `min`, then round away the
|
|
8
|
+
// floating-point drift that `Math.round(x / step) * step` introduces (e.g.
|
|
9
|
+
// 0.1 * 3 === 0.30000000000000004) so value/aria-valuenow stay clean.
|
|
10
|
+
const snapToStep = (v, min, step) => {
|
|
11
|
+
if (step <= 0)
|
|
12
|
+
return v;
|
|
13
|
+
const snapped = min + Math.round((v - min) / step) * step;
|
|
14
|
+
// Derive the decimal precision from the step (e.g. 0.1 -> 1, 0.25 -> 2).
|
|
15
|
+
const decimals = (String(step).split(".")[1] ?? "").length;
|
|
16
|
+
return decimals > 0 ? Number(snapped.toFixed(decimals)) : snapped;
|
|
17
|
+
};
|
|
7
18
|
/**
|
|
8
19
|
* Self-contained slider with pointer drag and keyboard control. Supports one or
|
|
9
20
|
* more thumbs (the nearest thumb to the pointer is the one that moves). Track,
|
|
@@ -21,6 +32,12 @@ function Slider({ className, value, defaultValue, onValueChange, min = 0, max =
|
|
|
21
32
|
const trackRef = React.useRef(null);
|
|
22
33
|
const activeThumb = React.useRef(0);
|
|
23
34
|
const vertical = orientation === "vertical";
|
|
35
|
+
// Track the latest values so pointer-move handlers (which close over the
|
|
36
|
+
// pointerdown-time render) read fresh state — otherwise, in controlled mode,
|
|
37
|
+
// a parent correction (e.g. min-gap push between thumbs) gets overwritten on
|
|
38
|
+
// the next move.
|
|
39
|
+
const valuesRef = React.useRef(values);
|
|
40
|
+
valuesRef.current = values;
|
|
24
41
|
const percent = (v) => ((v - min) / (max - min)) * 100;
|
|
25
42
|
const valueFromPointer = (clientX, clientY) => {
|
|
26
43
|
const track = trackRef.current;
|
|
@@ -31,17 +48,28 @@ function Slider({ className, value, defaultValue, onValueChange, min = 0, max =
|
|
|
31
48
|
? (rect.bottom - clientY) / rect.height
|
|
32
49
|
: (clientX - rect.left) / rect.width;
|
|
33
50
|
const raw = min + clamp(ratio, 0, 1) * (max - min);
|
|
34
|
-
return clamp(
|
|
51
|
+
return clamp(snapToStep(raw, min, step), min, max);
|
|
35
52
|
};
|
|
36
53
|
const updateThumb = (index, next) => {
|
|
37
|
-
|
|
54
|
+
const current = valuesRef.current;
|
|
55
|
+
// Clamp the moving thumb against its immediate neighbors so values stay
|
|
56
|
+
// ordered. When neighbors share this thumb's value (overlap), only the
|
|
57
|
+
// actively-dragged thumb is allowed to move away — clamping against the
|
|
58
|
+
// neighbor on the side it's heading lets it pass instead of pinning it.
|
|
59
|
+
const lower = index > 0 ? current[index - 1] : min;
|
|
60
|
+
const upper = index < current.length - 1 ? current[index + 1] : max;
|
|
61
|
+
const clamped = clamp(next, lower, upper);
|
|
62
|
+
setValues(current.map((v, i) => (i === index ? clamped : v)));
|
|
38
63
|
};
|
|
39
64
|
const nearestThumb = (v) => {
|
|
40
65
|
let best = 0;
|
|
41
66
|
let bestDist = Infinity;
|
|
42
|
-
|
|
67
|
+
valuesRef.current.forEach((tv, i) => {
|
|
43
68
|
const d = Math.abs(tv - v);
|
|
44
|
-
|
|
69
|
+
// On exact ties prefer the thumb whose move direction matches the
|
|
70
|
+
// pointer, so overlapping thumbs don't both resolve to the lowest index
|
|
71
|
+
// (which would leave the higher-index thumb unmovable).
|
|
72
|
+
if (d < bestDist || (d === bestDist && v > tv)) {
|
|
45
73
|
bestDist = d;
|
|
46
74
|
best = i;
|
|
47
75
|
}
|
|
@@ -56,27 +84,39 @@ function Slider({ className, value, defaultValue, onValueChange, min = 0, max =
|
|
|
56
84
|
activeThumb.current = idx;
|
|
57
85
|
updateThumb(idx, v);
|
|
58
86
|
const target = e.currentTarget;
|
|
59
|
-
|
|
87
|
+
const pointerId = e.pointerId;
|
|
88
|
+
target.setPointerCapture(pointerId);
|
|
60
89
|
const onMove = (ev) => {
|
|
61
90
|
updateThumb(activeThumb.current, valueFromPointer(ev.clientX, ev.clientY));
|
|
62
91
|
};
|
|
63
|
-
|
|
64
|
-
|
|
92
|
+
// Single teardown for every drag-ending event so a cancelled or lost
|
|
93
|
+
// pointer (pointercancel/lostpointercapture) doesn't leak the move/up
|
|
94
|
+
// listeners and leave the drag stuck.
|
|
95
|
+
const teardown = () => {
|
|
96
|
+
if (target.hasPointerCapture(pointerId)) {
|
|
97
|
+
target.releasePointerCapture(pointerId);
|
|
98
|
+
}
|
|
65
99
|
target.removeEventListener("pointermove", onMove);
|
|
66
|
-
target.removeEventListener("pointerup",
|
|
100
|
+
target.removeEventListener("pointerup", teardown);
|
|
101
|
+
target.removeEventListener("pointercancel", teardown);
|
|
102
|
+
target.removeEventListener("lostpointercapture", teardown);
|
|
67
103
|
};
|
|
68
104
|
target.addEventListener("pointermove", onMove);
|
|
69
|
-
target.addEventListener("pointerup",
|
|
105
|
+
target.addEventListener("pointerup", teardown);
|
|
106
|
+
target.addEventListener("pointercancel", teardown);
|
|
107
|
+
target.addEventListener("lostpointercapture", teardown);
|
|
70
108
|
};
|
|
71
109
|
const onThumbKeyDown = (index) => (e) => {
|
|
72
110
|
if (disabled)
|
|
73
111
|
return;
|
|
74
112
|
let next = null;
|
|
75
113
|
const cur = values[index];
|
|
114
|
+
// Round each step back onto the grid so repeated arrow presses don't
|
|
115
|
+
// accumulate float drift in value/aria-valuenow.
|
|
76
116
|
if (e.key === "ArrowRight" || e.key === "ArrowUp")
|
|
77
|
-
next = cur + step;
|
|
117
|
+
next = snapToStep(cur + step, min, step);
|
|
78
118
|
else if (e.key === "ArrowLeft" || e.key === "ArrowDown")
|
|
79
|
-
next = cur - step;
|
|
119
|
+
next = snapToStep(cur - step, min, step);
|
|
80
120
|
else if (e.key === "Home")
|
|
81
121
|
next = min;
|
|
82
122
|
else if (e.key === "End")
|
package/dist/ui/slot.js
CHANGED
|
@@ -24,7 +24,9 @@ function Slot({ children, className, style, ref, ...props }) {
|
|
|
24
24
|
const childProps = child.props;
|
|
25
25
|
// Child props win on plain values, but event handlers (on*) are COMPOSED so
|
|
26
26
|
// the slot-injected behavior (open/close/etc.) runs alongside the child's own
|
|
27
|
-
// handler — matching Radix
|
|
27
|
+
// handler — matching Radix composeEventHandlers instead of clobbering it. The
|
|
28
|
+
// child's own handler runs FIRST; the forwarded slot handler runs ONLY IF the
|
|
29
|
+
// child hasn't called preventDefault, so a child can opt out of slot behavior.
|
|
28
30
|
const merged = { ...props, ...childProps };
|
|
29
31
|
for (const key of Object.keys(props)) {
|
|
30
32
|
const slotHandler = props[key];
|
|
@@ -34,11 +36,11 @@ function Slot({ children, className, style, ref, ...props }) {
|
|
|
34
36
|
typeof childHandler === "function") {
|
|
35
37
|
merged[key] = (...args) => {
|
|
36
38
|
;
|
|
37
|
-
|
|
39
|
+
childHandler(...args);
|
|
38
40
|
const event = args[0];
|
|
39
41
|
if (!event?.defaultPrevented) {
|
|
40
42
|
;
|
|
41
|
-
|
|
43
|
+
slotHandler(...args);
|
|
42
44
|
}
|
|
43
45
|
};
|
|
44
46
|
}
|
package/dist/ui/tabs.js
CHANGED
|
@@ -70,18 +70,25 @@ function TabsTrigger({ className, value, onClick, ref, ...props }) {
|
|
|
70
70
|
const { value: active, setValue, idBase } = useTabs();
|
|
71
71
|
const selected = active === value;
|
|
72
72
|
const btnRef = React.useRef(null);
|
|
73
|
-
// Roving tab index: the selected tab is tabbable. When
|
|
74
|
-
//
|
|
73
|
+
// Roving tab index: the selected tab is tabbable. When no trigger matches the
|
|
74
|
+
// active value — undefined, or a value with no rendered trigger — the first
|
|
75
|
+
// enabled tab must still be reachable so the tablist stays in Tab order
|
|
75
76
|
// (WAI-ARIA: a tablist always has exactly one tab stop).
|
|
76
|
-
const [
|
|
77
|
+
const [isFallbackStop, setIsFallbackStop] = React.useState(false);
|
|
77
78
|
React.useLayoutEffect(() => {
|
|
78
|
-
if (active !== undefined)
|
|
79
|
-
return;
|
|
80
79
|
const el = btnRef.current;
|
|
81
|
-
const
|
|
82
|
-
|
|
80
|
+
const list = el?.parentElement;
|
|
81
|
+
if (!list)
|
|
82
|
+
return;
|
|
83
|
+
const anySelected = list.querySelector('[role="tab"][aria-selected="true"]');
|
|
84
|
+
if (anySelected) {
|
|
85
|
+
setIsFallbackStop(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const first = list.querySelector('[role="tab"]:not([disabled]):not([data-disabled])');
|
|
89
|
+
setIsFallbackStop(first === el);
|
|
83
90
|
});
|
|
84
|
-
const tabbable = selected ||
|
|
91
|
+
const tabbable = selected || isFallbackStop;
|
|
85
92
|
return (_jsx("button", { ref: mergeRefs(btnRef, ref), type: "button", role: "tab", id: `${idBase}-tab-${idify(value)}`, "aria-selected": selected, "aria-controls": `${idBase}-panel-${idify(value)}`, tabIndex: tabbable ? 0 : -1, "data-slot": "tabs-trigger", "data-active": selected ? "" : undefined, className: cn("cursor-pointer relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className), onClick: (e) => {
|
|
86
93
|
onClick?.(e);
|
|
87
94
|
if (!e.defaultPrevented)
|
package/dist/ui/use-dismiss.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { isInLayer } from "./layer-stack.js";
|
|
3
4
|
// Shared stack of open dismissable layers (most-recently-opened last). Escape
|
|
4
5
|
// only closes the topmost layer, so a popover opened inside a dialog doesn't
|
|
5
6
|
// close both at once.
|
|
@@ -35,6 +36,8 @@ export function useDismiss(open, onDismiss, refs) {
|
|
|
35
36
|
return;
|
|
36
37
|
if (refs.some((r) => r.current && r.current.contains(target)))
|
|
37
38
|
return;
|
|
39
|
+
if (isInLayer(target))
|
|
40
|
+
return;
|
|
38
41
|
onDismissRef.current();
|
|
39
42
|
};
|
|
40
43
|
document.addEventListener("keydown", onKeyDown, true);
|