tempest-react-sdk 0.65.0 → 0.67.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/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +31 -36
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js +31 -40
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js +94 -73
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Combobox/Combobox.module.cjs +1 -1
- package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.module.js +4 -3
- package/dist/components/Combobox/Combobox.module.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +80 -83
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js +43 -46
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +129 -105
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.cjs +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.js +12 -11
- package/dist/components/DropdownMenu/DropdownMenu.module.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.cjs +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js +54 -29
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.module.cjs +1 -1
- package/dist/components/HoverCard/HoverCard.module.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.module.js +4 -3
- package/dist/components/HoverCard/HoverCard.module.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js +86 -89
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js +86 -65
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Menubar/Menubar.module.cjs +1 -1
- package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.module.js +4 -3
- package/dist/components/Menubar/Menubar.module.js.map +1 -1
- package/dist/components/Modal/Modal.cjs +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js +44 -51
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js +113 -96
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.js +4 -3
- package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +83 -61
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.js +4 -3
- package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
- package/dist/components/Popover/Popover.cjs +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js +57 -36
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Popover/Popover.module.cjs +1 -1
- package/dist/components/Popover/Popover.module.cjs.map +1 -1
- package/dist/components/Popover/Popover.module.js +7 -6
- package/dist/components/Popover/Popover.module.js.map +1 -1
- package/dist/components/Portal/anchor-position.cjs +2 -0
- package/dist/components/Portal/anchor-position.cjs.map +1 -0
- package/dist/components/Portal/anchor-position.js +99 -0
- package/dist/components/Portal/anchor-position.js.map +1 -0
- package/dist/components/Portal/escape-layer.cjs +2 -0
- package/dist/components/Portal/escape-layer.cjs.map +1 -0
- package/dist/components/Portal/escape-layer.js +30 -0
- package/dist/components/Portal/escape-layer.js.map +1 -0
- package/dist/components/Portal/tab-bridge.cjs +2 -0
- package/dist/components/Portal/tab-bridge.cjs.map +1 -0
- package/dist/components/Portal/tab-bridge.js +39 -0
- package/dist/components/Portal/tab-bridge.js.map +1 -0
- package/dist/components/Tooltip/Tooltip.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +43 -29
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.module.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.module.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.module.js +4 -3
- package/dist/components/Tooltip/Tooltip.module.js.map +1 -1
- package/dist/hooks/use-focus-trap.cjs +1 -1
- package/dist/hooks/use-focus-trap.cjs.map +1 -1
- package/dist/hooks/use-focus-trap.js +2 -2
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +3 -3
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +87 -85
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +2 -0
- package/dist/http/credential-scope.cjs.map +1 -0
- package/dist/http/credential-scope.js +23 -0
- package/dist/http/credential-scope.js.map +1 -0
- package/dist/http/describe-api-error.cjs +1 -1
- package/dist/http/describe-api-error.cjs.map +1 -1
- package/dist/http/describe-api-error.js +4 -2
- package/dist/http/describe-api-error.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +121 -120
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +35 -34
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/styles/actions.css +3 -0
- package/dist/styles/advanced.css +1 -0
- package/dist/styles/component/Combobox.css +1 -0
- package/dist/styles/component/DropdownMenu.css +1 -0
- package/dist/styles/component/HoverCard.css +1 -0
- package/dist/styles/component/Menubar.css +1 -0
- package/dist/styles/component/MultiSelect.css +1 -0
- package/dist/styles/component/NavigationMenu.css +1 -0
- package/dist/styles/component/Popover.css +1 -0
- package/dist/styles/component/Tooltip.css +1 -0
- package/dist/styles/forms.css +2 -0
- package/dist/styles/navigation.css +2 -0
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +364 -29
- package/dist/tempest-react-sdk.js +335 -333
- package/dist/utils/absent.cjs +2 -0
- package/dist/utils/absent.cjs.map +1 -0
- package/dist/utils/absent.js +12 -0
- package/dist/utils/absent.js.map +1 -0
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +42 -37
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/numbers.cjs +1 -1
- package/dist/utils/numbers.cjs.map +1 -1
- package/dist/utils/numbers.js +14 -12
- package/dist/utils/numbers.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useLayoutEffect as e, useState as t } from "react";
|
|
2
|
+
//#region src/components/Portal/anchor-position.ts
|
|
3
|
+
var n = {
|
|
4
|
+
top: "bottom",
|
|
5
|
+
bottom: "top",
|
|
6
|
+
left: "right",
|
|
7
|
+
right: "left"
|
|
8
|
+
};
|
|
9
|
+
function r(e, t, n) {
|
|
10
|
+
return Math.max(t, Math.min(e, Math.max(t, n)));
|
|
11
|
+
}
|
|
12
|
+
function i(e, t) {
|
|
13
|
+
let { anchor: n, width: r, height: i, offset: a } = e;
|
|
14
|
+
switch (t) {
|
|
15
|
+
case "top": return n.top - a - i;
|
|
16
|
+
case "bottom": return n.top + n.height + a;
|
|
17
|
+
case "left": return n.left - a - r;
|
|
18
|
+
default: return n.left + n.width + a;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function a(e, t) {
|
|
22
|
+
let { width: n, height: r, viewportWidth: a, viewportHeight: o, margin: s } = e, c = i(e, t), l = t === "top" || t === "bottom" ? r : n, u = t === "top" || t === "bottom" ? o : a;
|
|
23
|
+
return c >= s && c + l <= u - s;
|
|
24
|
+
}
|
|
25
|
+
function o(e, t, n, r) {
|
|
26
|
+
return r === "start" ? e : r === "end" ? e + t - n : e + (t - n) / 2;
|
|
27
|
+
}
|
|
28
|
+
function s(e) {
|
|
29
|
+
let { anchor: t, width: s, height: c, viewportWidth: l, viewportHeight: u, align: d, margin: f } = e, p = e.side, m = n[p], h = !a(e, p) && a(e, m) ? m : p, g = h === "top" || h === "bottom", _ = i(e, h), v = g ? _ : o(t.top, t.height, c, d), y = g ? o(t.left, t.width, s, d) : _;
|
|
30
|
+
return {
|
|
31
|
+
top: r(v, f, u - c - f),
|
|
32
|
+
left: r(y, f, l - s - f),
|
|
33
|
+
side: h
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function c({ anchor: n, floating: r, side: i, align: a, offset: o, enabled: c, matchWidth: l = !1 }) {
|
|
37
|
+
let [u, d] = t(null);
|
|
38
|
+
if (e(() => {
|
|
39
|
+
if (!c || !n || !r) {
|
|
40
|
+
d(null);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let e = () => {
|
|
44
|
+
let e = n.getBoundingClientRect(), t = l ? e.width : void 0;
|
|
45
|
+
t !== void 0 && r.style.setProperty("width", `${t}px`);
|
|
46
|
+
let c = {
|
|
47
|
+
...s({
|
|
48
|
+
anchor: {
|
|
49
|
+
top: e.top,
|
|
50
|
+
left: e.left,
|
|
51
|
+
width: e.width,
|
|
52
|
+
height: e.height
|
|
53
|
+
},
|
|
54
|
+
width: r.offsetWidth,
|
|
55
|
+
height: r.offsetHeight,
|
|
56
|
+
viewportWidth: window.innerWidth,
|
|
57
|
+
viewportHeight: window.innerHeight,
|
|
58
|
+
side: i,
|
|
59
|
+
align: a,
|
|
60
|
+
offset: o,
|
|
61
|
+
margin: 8
|
|
62
|
+
}),
|
|
63
|
+
width: t
|
|
64
|
+
};
|
|
65
|
+
d((e) => e && e.top === c.top && e.left === c.left && e.side === c.side && e.width === c.width ? e : c);
|
|
66
|
+
};
|
|
67
|
+
e();
|
|
68
|
+
let t = 0, u = () => {
|
|
69
|
+
t === 0 && (t = requestAnimationFrame(() => {
|
|
70
|
+
t = 0, e();
|
|
71
|
+
}));
|
|
72
|
+
};
|
|
73
|
+
window.addEventListener("scroll", u, {
|
|
74
|
+
capture: !0,
|
|
75
|
+
passive: !0
|
|
76
|
+
}), window.addEventListener("resize", u);
|
|
77
|
+
let f = typeof ResizeObserver > "u" ? null : new ResizeObserver(u);
|
|
78
|
+
return f?.observe(r), f?.observe(n), () => {
|
|
79
|
+
f?.disconnect(), t !== 0 && cancelAnimationFrame(t), window.removeEventListener("scroll", u, { capture: !0 }), window.removeEventListener("resize", u);
|
|
80
|
+
};
|
|
81
|
+
}, [
|
|
82
|
+
c,
|
|
83
|
+
n,
|
|
84
|
+
r,
|
|
85
|
+
i,
|
|
86
|
+
a,
|
|
87
|
+
o,
|
|
88
|
+
l
|
|
89
|
+
]), c) return {
|
|
90
|
+
position: "fixed",
|
|
91
|
+
top: u?.top ?? 0,
|
|
92
|
+
left: u?.left ?? 0,
|
|
93
|
+
width: u?.width
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { s as computeAnchorPosition, c as useAnchorPosition };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=anchor-position.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-position.js","names":[],"sources":["../../../src/components/Portal/anchor-position.ts"],"sourcesContent":["import { useLayoutEffect, useState } from \"react\";\nimport type { CSSProperties } from \"react\";\n\n/** Side of the anchor a floating layer is placed on. */\nexport type AnchorSide = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/** Alignment of the floating layer along the anchor's edge. */\nexport type AnchorAlign = \"start\" | \"center\" | \"end\";\n\n/** The viewport-relative box a placement is computed from. */\nexport interface AnchorRect {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Everything {@link computeAnchorPosition} needs, all in CSS pixels. */\nexport interface AnchorGeometry {\n anchor: AnchorRect;\n width: number;\n height: number;\n viewportWidth: number;\n viewportHeight: number;\n side: AnchorSide;\n align: AnchorAlign;\n offset: number;\n margin: number;\n}\n\n/** A resolved placement: viewport coordinates plus the side actually used. */\nexport interface AnchorPlacement {\n top: number;\n left: number;\n side: AnchorSide;\n}\n\n/** What {@link useAnchorPosition} keeps between measurements. */\ninterface MeasuredPlacement extends AnchorPlacement {\n /** The anchor's width, when the layer matches it. */\n width: number | undefined;\n}\n\nconst OPPOSITE: Record<AnchorSide, AnchorSide> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n};\n\n/** Distance kept between a floating layer and the viewport edge, in px. */\nexport const ANCHOR_VIEWPORT_MARGIN = 8;\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, Math.max(min, max)));\n}\n\nfunction mainAxis(geometry: AnchorGeometry, side: AnchorSide): number {\n const { anchor, width, height, offset } = geometry;\n switch (side) {\n case \"top\":\n return anchor.top - offset - height;\n case \"bottom\":\n return anchor.top + anchor.height + offset;\n case \"left\":\n return anchor.left - offset - width;\n case \"right\":\n default:\n return anchor.left + anchor.width + offset;\n }\n}\n\nfunction fits(geometry: AnchorGeometry, side: AnchorSide): boolean {\n const { width, height, viewportWidth, viewportHeight, margin } = geometry;\n const start = mainAxis(geometry, side);\n const size = side === \"top\" || side === \"bottom\" ? height : width;\n const limit = side === \"top\" || side === \"bottom\" ? viewportHeight : viewportWidth;\n return start >= margin && start + size <= limit - margin;\n}\n\nfunction crossAxis(start: number, length: number, size: number, align: AnchorAlign): number {\n if (align === \"start\") return start;\n if (align === \"end\") return start + length - size;\n return start + (length - size) / 2;\n}\n\n/**\n * Place a floating layer next to its anchor, inside the viewport.\n *\n * The requested side wins when the layer fits there. When it does not and the\n * opposite side does, the layer flips — the case that matters is the action menu\n * of the last row of a table near the bottom of the screen, which would otherwise\n * open below the fold. When neither side fits, the requested one is kept and the\n * layer is clamped, because a partially visible menu the user can scroll to beats\n * one that jumps sides depending on a pixel.\n *\n * Both axes are then clamped to the viewport minus `margin`.\n *\n * @param geometry - The anchor box, the floating layer's size, the viewport, and\n * the requested placement.\n * @returns The viewport coordinates for a `position: fixed` layer, and the side\n * that was used after flipping.\n */\nexport function computeAnchorPosition(geometry: AnchorGeometry): AnchorPlacement {\n const { anchor, width, height, viewportWidth, viewportHeight, align, margin } = geometry;\n const requested = geometry.side;\n const flipped = OPPOSITE[requested];\n const side = !fits(geometry, requested) && fits(geometry, flipped) ? flipped : requested;\n const vertical = side === \"top\" || side === \"bottom\";\n const main = mainAxis(geometry, side);\n const top = vertical ? main : crossAxis(anchor.top, anchor.height, height, align);\n const left = vertical ? crossAxis(anchor.left, anchor.width, width, align) : main;\n return {\n top: clamp(top, margin, viewportHeight - height - margin),\n left: clamp(left, margin, viewportWidth - width - margin),\n side,\n };\n}\n\n/** Options of {@link useAnchorPosition}. */\nexport interface AnchorPositionOptions {\n /** Element the layer is anchored to. */\n anchor: HTMLElement | null;\n /** The floating layer itself, once mounted. */\n floating: HTMLElement | null;\n side: AnchorSide;\n align: AnchorAlign;\n /** Gap between anchor and layer, in px. */\n offset: number;\n /** When false the hook does nothing and returns `undefined`. */\n enabled: boolean;\n /**\n * Give the layer the anchor's width — a listbox under its input. Default\n * `false`.\n */\n matchWidth?: boolean;\n}\n\n/**\n * Keep a portalled layer glued to its anchor.\n *\n * Takes the floating element as a value rather than a ref, because `Portal`\n * renders `null` on its first pass and mounts in an effect: a layout effect keyed\n * off `open` would run while the layer does not exist and measure nothing. With\n * the node held in state (a callback ref), the effect runs on the render that has\n * it, and `useLayoutEffect` settles the position before the first paint.\n *\n * The size comes from `offsetWidth`/`offsetHeight`, not from\n * `getBoundingClientRect`, which reports the transformed box — the layers enter\n * with a `translate`/`scale` animation, and the transformed rect would place them\n * a few pixels off. The anchor's rect is the right one to read: it is where the\n * trigger is painted.\n *\n * Scroll (captured, so a scrolling table wrapper counts) and resize reposition the\n * layer on the next animation frame, so a menu open inside a table that scrolls\n * follows its row instead of floating where the row used to be. So does a change\n * in the size of either box, through a `ResizeObserver`: a combobox list flipped\n * above its input shrinks as the user types, and a multi-select field grows a\n * line as chips are added — without the observer the list would keep its old\n * top and float away from the field, or cover it.\n *\n * With `matchWidth` the anchor's width is written to the layer **before** its\n * height is read, so the height is measured at the width it will be painted at —\n * a list whose options wrap at the narrower width is taller.\n *\n * The unmeasured first pass is **not** hidden with `visibility: hidden`: an\n * element under it cannot take focus, and the menu's focus effect runs before\n * the measured re-render, so the first entry silently kept no focus (measured in\n * Chrome; jsdom does not enforce it). Nothing is painted at the unmeasured\n * position anyway — the layout effect re-renders before the browser paints.\n *\n * @param options - Anchor, floating node, placement, and whether it is active.\n * @returns A `position: fixed` style for the layer, or `undefined` when disabled.\n */\nexport function useAnchorPosition({\n anchor,\n floating,\n side,\n align,\n offset,\n enabled,\n matchWidth = false,\n}: AnchorPositionOptions): CSSProperties | undefined {\n const [placement, setPlacement] = useState<MeasuredPlacement | null>(null);\n\n useLayoutEffect(() => {\n if (!enabled || !anchor || !floating) {\n setPlacement(null);\n return;\n }\n const update = (): void => {\n const rect = anchor.getBoundingClientRect();\n const width = matchWidth ? rect.width : undefined;\n if (width !== undefined) floating.style.setProperty(\"width\", `${width}px`);\n const position = computeAnchorPosition({\n anchor: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n width: floating.offsetWidth,\n height: floating.offsetHeight,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n side,\n align,\n offset,\n margin: ANCHOR_VIEWPORT_MARGIN,\n });\n const next: MeasuredPlacement = { ...position, width };\n setPlacement((current) =>\n current &&\n current.top === next.top &&\n current.left === next.left &&\n current.side === next.side &&\n current.width === next.width\n ? current\n : next,\n );\n };\n update();\n let frame = 0;\n const schedule = (): void => {\n if (frame !== 0) return;\n frame = requestAnimationFrame(() => {\n frame = 0;\n update();\n });\n };\n window.addEventListener(\"scroll\", schedule, { capture: true, passive: true });\n window.addEventListener(\"resize\", schedule);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(schedule);\n observer?.observe(floating);\n observer?.observe(anchor);\n return () => {\n observer?.disconnect();\n if (frame !== 0) cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", schedule, { capture: true });\n window.removeEventListener(\"resize\", schedule);\n };\n }, [enabled, anchor, floating, side, align, offset, matchWidth]);\n\n if (!enabled) return undefined;\n return {\n position: \"fixed\",\n top: placement?.top ?? 0,\n left: placement?.left ?? 0,\n width: placement?.width,\n };\n}\n"],"mappings":";;AA2CA,IAAM,IAA2C;CAC7C,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;AACX;AAKA,SAAS,EAAM,GAAe,GAAa,GAAqB;CAC5D,OAAO,KAAK,IAAI,GAAK,KAAK,IAAI,GAAO,KAAK,IAAI,GAAK,CAAG,CAAC,CAAC;AAC5D;AAEA,SAAS,EAAS,GAA0B,GAA0B;CAClE,IAAM,EAAE,WAAQ,UAAO,WAAQ,cAAW;CAC1C,QAAQ,GAAR;EACI,KAAK,OACD,OAAO,EAAO,MAAM,IAAS;EACjC,KAAK,UACD,OAAO,EAAO,MAAM,EAAO,SAAS;EACxC,KAAK,QACD,OAAO,EAAO,OAAO,IAAS;EAElC,SACI,OAAO,EAAO,OAAO,EAAO,QAAQ;CAC5C;AACJ;AAEA,SAAS,EAAK,GAA0B,GAA2B;CAC/D,IAAM,EAAE,UAAO,WAAQ,kBAAe,mBAAgB,cAAW,GAC3D,IAAQ,EAAS,GAAU,CAAI,GAC/B,IAAO,MAAS,SAAS,MAAS,WAAW,IAAS,GACtD,IAAQ,MAAS,SAAS,MAAS,WAAW,IAAiB;CACrE,OAAO,KAAS,KAAU,IAAQ,KAAQ,IAAQ;AACtD;AAEA,SAAS,EAAU,GAAe,GAAgB,GAAc,GAA4B;CAGxF,OAFI,MAAU,UAAgB,IAC1B,MAAU,QAAc,IAAQ,IAAS,IACtC,KAAS,IAAS,KAAQ;AACrC;AAmBA,SAAgB,EAAsB,GAA2C;CAC7E,IAAM,EAAE,WAAQ,UAAO,WAAQ,kBAAe,mBAAgB,UAAO,cAAW,GAC1E,IAAY,EAAS,MACrB,IAAU,EAAS,IACnB,IAAO,CAAC,EAAK,GAAU,CAAS,KAAK,EAAK,GAAU,CAAO,IAAI,IAAU,GACzE,IAAW,MAAS,SAAS,MAAS,UACtC,IAAO,EAAS,GAAU,CAAI,GAC9B,IAAM,IAAW,IAAO,EAAU,EAAO,KAAK,EAAO,QAAQ,GAAQ,CAAK,GAC1E,IAAO,IAAW,EAAU,EAAO,MAAM,EAAO,OAAO,GAAO,CAAK,IAAI;CAC7E,OAAO;EACH,KAAK,EAAM,GAAK,GAAQ,IAAiB,IAAS,CAAM;EACxD,MAAM,EAAM,GAAM,GAAQ,IAAgB,IAAQ,CAAM;EACxD;CACJ;AACJ;AAyDA,SAAgB,EAAkB,EAC9B,WACA,aACA,SACA,UACA,WACA,YACA,gBAAa,MACoC;CACjD,IAAM,CAAC,GAAW,KAAgB,EAAmC,IAAI;CAEzE,YAAsB;EAClB,IAAI,CAAC,KAAW,CAAC,KAAU,CAAC,GAAU;GAClC,EAAa,IAAI;GACjB;EACJ;EACA,IAAM,UAAqB;GACvB,IAAM,IAAO,EAAO,sBAAsB,GACpC,IAAQ,IAAa,EAAK,QAAQ,KAAA;GACxC,AAAI,MAAU,KAAA,KAAW,EAAS,MAAM,YAAY,SAAS,GAAG,EAAM,GAAG;GAYzE,IAAM,IAA0B;IAAE,GAXjB,EAAsB;KACnC,QAAQ;MAAE,KAAK,EAAK;MAAK,MAAM,EAAK;MAAM,OAAO,EAAK;MAAO,QAAQ,EAAK;KAAO;KACjF,OAAO,EAAS;KAChB,QAAQ,EAAS;KACjB,eAAe,OAAO;KACtB,gBAAgB,OAAO;KACvB;KACA;KACA;KACA,QAAA;IACJ,CACqC;IAAU;GAAM;GACrD,GAAc,MACV,KACA,EAAQ,QAAQ,EAAK,OACrB,EAAQ,SAAS,EAAK,QACtB,EAAQ,SAAS,EAAK,QACtB,EAAQ,UAAU,EAAK,QACjB,IACA,CACV;EACJ;EACA,EAAO;EACP,IAAI,IAAQ,GACN,UAAuB;GACrB,MAAU,MACd,IAAQ,4BAA4B;IAEhC,AADA,IAAQ,GACR,EAAO;GACX,CAAC;EACL;EAEA,AADA,OAAO,iBAAiB,UAAU,GAAU;GAAE,SAAS;GAAM,SAAS;EAAK,CAAC,GAC5E,OAAO,iBAAiB,UAAU,CAAQ;EAC1C,IAAM,IACF,OAAO,iBAAmB,MAAc,OAAO,IAAI,eAAe,CAAQ;EAG9E,OAFA,GAAU,QAAQ,CAAQ,GAC1B,GAAU,QAAQ,CAAM,SACX;GAIT,AAHA,GAAU,WAAW,GACjB,MAAU,KAAG,qBAAqB,CAAK,GAC3C,OAAO,oBAAoB,UAAU,GAAU,EAAE,SAAS,GAAK,CAAC,GAChE,OAAO,oBAAoB,UAAU,CAAQ;EACjD;CACJ,GAAG;EAAC;EAAS;EAAQ;EAAU;EAAM;EAAO;EAAQ;CAAU,CAAC,GAE1D,GACL,OAAO;EACH,UAAU;EACV,KAAK,GAAW,OAAO;EACvB,MAAM,GAAW,QAAQ;EACzB,OAAO,GAAW;CACtB;AACJ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("react");var t=[],n=0;function r(e){if(e.key!==`Escape`||e.defaultPrevented||e.isComposing)return;let n=t.at(-1);n&&(e.preventDefault(),n.current?.())}function i(i,a){let o=(0,e.useMemo)(()=>i?++n:0,[i]),s=(0,e.useRef)({current:a,order:o});(0,e.useEffect)(()=>{s.current.current=a},[a]),(0,e.useEffect)(()=>{if(!i)return;let e=s.current;e.order=o,t.length===0&&window.addEventListener(`keydown`,r);let n=t.findIndex(e=>e.order>o);return t.splice(n===-1?t.length:n,0,e),()=>{let n=t.lastIndexOf(e);n!==-1&&t.splice(n,1),t.length===0&&window.removeEventListener(`keydown`,r)}},[i,o])}exports.useEscapeLayer=i;
|
|
2
|
+
//# sourceMappingURL=escape-layer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape-layer.cjs","names":[],"sources":["../../../src/components/Portal/escape-layer.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\n\n/** One open layer that `Escape` can dismiss. */\ninterface EscapeLayer {\n /** Latest dismiss callback; `null` makes the layer swallow the key without closing. */\n current: (() => void) | null;\n /** Position in the stack: higher is more recently opened, or deeper in the tree. */\n order: number;\n}\n\nconst stack: EscapeLayer[] = [];\n\nlet sequence = 0;\n\n/**\n * The single `keydown` listener behind every layer.\n *\n * Dismisses only the top of the stack. Every overlay used to listen on `window`\n * and close on any `Escape`, so one key press closed everything open at once —\n * measured in Chrome, a `Modal` holding a `DropdownMenu`, `Combobox`,\n * `MultiSelect`, `Popover`, `ContextMenu`, editable `DataTable` cell or a second\n * `Modal` closed together with the inner layer in all seven cases, and a\n * `Drawer` closed with the `Popover` inside it.\n *\n * Two kinds of inner layer exist, and this covers both:\n *\n * - Layers that listen on `window` themselves can never see each other's\n * events, so they register here and only the most recently opened one reacts.\n * - Layers that handle the key on their own element (a menu, a combobox input,\n * an inline cell editor) run first — React's delegated listener sits below\n * `window` — and call `preventDefault()`. A consumed key is left alone.\n *\n * A key pressed while an IME is composing belongs to the composition (it cancels\n * the candidate), so it is ignored too.\n *\n * @param event - The native keyboard event.\n * @returns Nothing.\n */\nfunction handleKeyDown(event: KeyboardEvent): void {\n if (event.key !== \"Escape\" || event.defaultPrevented || event.isComposing) return;\n const top = stack.at(-1);\n if (!top) return;\n event.preventDefault();\n top.current?.();\n}\n\n/**\n * Register an overlay as an `Escape`-dismissable layer while it is open.\n *\n * Layers stack in the order they open, so the one opened last is dismissed\n * first, and a second `Escape` reaches the next one down. The callback is read\n * through a ref, so a new closure on every render does not move the layer in\n * the stack.\n *\n * Pass `null` as `onEscape` for a modal layer whose dismissal is turned off\n * (`closeOnEsc={false}`): it stays on the stack and swallows the key, instead of\n * letting it fall through and close the modal underneath.\n *\n * The position is taken when the layer **renders** open, not when its effect\n * runs. Effects run child-first, so a `Modal` that mounts already holding an open\n * inner `Modal` would register the inner one first and put the outer one on top —\n * `Escape` would close the wrong layer, or a locked inner modal would let the key\n * through to the outer one. Parents render before their children, so render\n * order is tree order within a commit and opening order across commits. A\n * discarded or doubled render (Strict Mode, concurrent rendering) only skips\n * numbers; the relative order stays.\n *\n * @param active - Whether the layer is currently open.\n * @param onEscape - What `Escape` does to this layer, or `null` to block only.\n * @returns Nothing.\n */\nexport function useEscapeLayer(active: boolean, onEscape: (() => void) | null): void {\n const order = useMemo(() => (active ? ++sequence : 0), [active]);\n const layer = useRef<EscapeLayer>({ current: onEscape, order });\n\n useEffect(() => {\n layer.current.current = onEscape;\n }, [onEscape]);\n\n useEffect(() => {\n if (!active) return;\n const entry = layer.current;\n entry.order = order;\n if (stack.length === 0) window.addEventListener(\"keydown\", handleKeyDown);\n const index = stack.findIndex((other) => other.order > order);\n stack.splice(index === -1 ? stack.length : index, 0, entry);\n return () => {\n const index = stack.lastIndexOf(entry);\n if (index !== -1) stack.splice(index, 1);\n if (stack.length === 0) window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [active, order]);\n}\n"],"mappings":"uBAUA,IAAM,EAAuB,CAAC,EAE1B,EAAW,EA0Bf,SAAS,EAAc,EAA4B,CAC/C,GAAI,EAAM,MAAQ,UAAY,EAAM,kBAAoB,EAAM,YAAa,OAC3E,IAAM,EAAM,EAAM,GAAG,EAAE,EAClB,IACL,EAAM,eAAe,EACrB,EAAI,UAAU,EAClB,CA2BA,SAAgB,EAAe,EAAiB,EAAqC,CACjF,IAAM,GAAA,EAAQ,EAAA,QAAA,KAAe,EAAS,EAAE,EAAW,EAAI,CAAC,CAAM,CAAC,EACzD,GAAA,EAAQ,EAAA,OAAA,CAAoB,CAAE,QAAS,EAAU,OAAM,CAAC,GAE9D,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,QAAQ,QAAU,CAC5B,EAAG,CAAC,CAAQ,CAAC,GAEb,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAQ,OACb,IAAM,EAAQ,EAAM,QACpB,EAAM,MAAQ,EACV,EAAM,SAAW,GAAG,OAAO,iBAAiB,UAAW,CAAa,EACxE,IAAM,EAAQ,EAAM,UAAW,GAAU,EAAM,MAAQ,CAAK,EAE5D,OADA,EAAM,OAAO,IAAU,GAAK,EAAM,OAAS,EAAO,EAAG,CAAK,MAC7C,CACT,IAAM,EAAQ,EAAM,YAAY,CAAK,EACjC,IAAU,IAAI,EAAM,OAAO,EAAO,CAAC,EACnC,EAAM,SAAW,GAAG,OAAO,oBAAoB,UAAW,CAAa,CAC/E,CACJ,EAAG,CAAC,EAAQ,CAAK,CAAC,CACtB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect as e, useMemo as t, useRef as n } from "react";
|
|
2
|
+
//#region src/components/Portal/escape-layer.ts
|
|
3
|
+
var r = [], i = 0;
|
|
4
|
+
function a(e) {
|
|
5
|
+
if (e.key !== "Escape" || e.defaultPrevented || e.isComposing) return;
|
|
6
|
+
let t = r.at(-1);
|
|
7
|
+
t && (e.preventDefault(), t.current?.());
|
|
8
|
+
}
|
|
9
|
+
function o(o, s) {
|
|
10
|
+
let c = t(() => o ? ++i : 0, [o]), l = n({
|
|
11
|
+
current: s,
|
|
12
|
+
order: c
|
|
13
|
+
});
|
|
14
|
+
e(() => {
|
|
15
|
+
l.current.current = s;
|
|
16
|
+
}, [s]), e(() => {
|
|
17
|
+
if (!o) return;
|
|
18
|
+
let e = l.current;
|
|
19
|
+
e.order = c, r.length === 0 && window.addEventListener("keydown", a);
|
|
20
|
+
let t = r.findIndex((e) => e.order > c);
|
|
21
|
+
return r.splice(t === -1 ? r.length : t, 0, e), () => {
|
|
22
|
+
let t = r.lastIndexOf(e);
|
|
23
|
+
t !== -1 && r.splice(t, 1), r.length === 0 && window.removeEventListener("keydown", a);
|
|
24
|
+
};
|
|
25
|
+
}, [o, c]);
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { o as useEscapeLayer };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=escape-layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape-layer.js","names":[],"sources":["../../../src/components/Portal/escape-layer.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\n\n/** One open layer that `Escape` can dismiss. */\ninterface EscapeLayer {\n /** Latest dismiss callback; `null` makes the layer swallow the key without closing. */\n current: (() => void) | null;\n /** Position in the stack: higher is more recently opened, or deeper in the tree. */\n order: number;\n}\n\nconst stack: EscapeLayer[] = [];\n\nlet sequence = 0;\n\n/**\n * The single `keydown` listener behind every layer.\n *\n * Dismisses only the top of the stack. Every overlay used to listen on `window`\n * and close on any `Escape`, so one key press closed everything open at once —\n * measured in Chrome, a `Modal` holding a `DropdownMenu`, `Combobox`,\n * `MultiSelect`, `Popover`, `ContextMenu`, editable `DataTable` cell or a second\n * `Modal` closed together with the inner layer in all seven cases, and a\n * `Drawer` closed with the `Popover` inside it.\n *\n * Two kinds of inner layer exist, and this covers both:\n *\n * - Layers that listen on `window` themselves can never see each other's\n * events, so they register here and only the most recently opened one reacts.\n * - Layers that handle the key on their own element (a menu, a combobox input,\n * an inline cell editor) run first — React's delegated listener sits below\n * `window` — and call `preventDefault()`. A consumed key is left alone.\n *\n * A key pressed while an IME is composing belongs to the composition (it cancels\n * the candidate), so it is ignored too.\n *\n * @param event - The native keyboard event.\n * @returns Nothing.\n */\nfunction handleKeyDown(event: KeyboardEvent): void {\n if (event.key !== \"Escape\" || event.defaultPrevented || event.isComposing) return;\n const top = stack.at(-1);\n if (!top) return;\n event.preventDefault();\n top.current?.();\n}\n\n/**\n * Register an overlay as an `Escape`-dismissable layer while it is open.\n *\n * Layers stack in the order they open, so the one opened last is dismissed\n * first, and a second `Escape` reaches the next one down. The callback is read\n * through a ref, so a new closure on every render does not move the layer in\n * the stack.\n *\n * Pass `null` as `onEscape` for a modal layer whose dismissal is turned off\n * (`closeOnEsc={false}`): it stays on the stack and swallows the key, instead of\n * letting it fall through and close the modal underneath.\n *\n * The position is taken when the layer **renders** open, not when its effect\n * runs. Effects run child-first, so a `Modal` that mounts already holding an open\n * inner `Modal` would register the inner one first and put the outer one on top —\n * `Escape` would close the wrong layer, or a locked inner modal would let the key\n * through to the outer one. Parents render before their children, so render\n * order is tree order within a commit and opening order across commits. A\n * discarded or doubled render (Strict Mode, concurrent rendering) only skips\n * numbers; the relative order stays.\n *\n * @param active - Whether the layer is currently open.\n * @param onEscape - What `Escape` does to this layer, or `null` to block only.\n * @returns Nothing.\n */\nexport function useEscapeLayer(active: boolean, onEscape: (() => void) | null): void {\n const order = useMemo(() => (active ? ++sequence : 0), [active]);\n const layer = useRef<EscapeLayer>({ current: onEscape, order });\n\n useEffect(() => {\n layer.current.current = onEscape;\n }, [onEscape]);\n\n useEffect(() => {\n if (!active) return;\n const entry = layer.current;\n entry.order = order;\n if (stack.length === 0) window.addEventListener(\"keydown\", handleKeyDown);\n const index = stack.findIndex((other) => other.order > order);\n stack.splice(index === -1 ? stack.length : index, 0, entry);\n return () => {\n const index = stack.lastIndexOf(entry);\n if (index !== -1) stack.splice(index, 1);\n if (stack.length === 0) window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [active, order]);\n}\n"],"mappings":";;AAUA,IAAM,IAAuB,CAAC,GAE1B,IAAW;AA0Bf,SAAS,EAAc,GAA4B;CAC/C,IAAI,EAAM,QAAQ,YAAY,EAAM,oBAAoB,EAAM,aAAa;CAC3E,IAAM,IAAM,EAAM,GAAG,EAAE;CAClB,MACL,EAAM,eAAe,GACrB,EAAI,UAAU;AAClB;AA2BA,SAAgB,EAAe,GAAiB,GAAqC;CACjF,IAAM,IAAQ,QAAe,IAAS,EAAE,IAAW,GAAI,CAAC,CAAM,CAAC,GACzD,IAAQ,EAAoB;EAAE,SAAS;EAAU;CAAM,CAAC;CAM9D,AAJA,QAAgB;EACZ,EAAM,QAAQ,UAAU;CAC5B,GAAG,CAAC,CAAQ,CAAC,GAEb,QAAgB;EACZ,IAAI,CAAC,GAAQ;EACb,IAAM,IAAQ,EAAM;EAEpB,AADA,EAAM,QAAQ,GACV,EAAM,WAAW,KAAG,OAAO,iBAAiB,WAAW,CAAa;EACxE,IAAM,IAAQ,EAAM,WAAW,MAAU,EAAM,QAAQ,CAAK;EAE5D,OADA,EAAM,OAAO,MAAU,KAAK,EAAM,SAAS,GAAO,GAAG,CAAK,SAC7C;GACT,IAAM,IAAQ,EAAM,YAAY,CAAK;GAErC,AADI,MAAU,MAAI,EAAM,OAAO,GAAO,CAAC,GACnC,EAAM,WAAW,KAAG,OAAO,oBAAoB,WAAW,CAAa;EAC/E;CACJ,GAAG,CAAC,GAAQ,CAAK,CAAC;AACtB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../hooks/use-focus-trap.cjs");let t=require("react");function n(t){return Array.from(t.querySelectorAll(e.FOCUSABLE_SELECTOR)).filter(e=>{if(e.tabIndex<0||e.hasAttribute(`aria-hidden`))return!1;let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`})}function r({enabled:e,anchor:r,panel:i}){(0,t.useEffect)(()=>{if(!e||!r||!i)return;let t=(e,t)=>{t&&(e.preventDefault(),t.focus())},a=e=>{if(e.key!==`Tab`||e.defaultPrevented)return;let a=n(i);if(a.length===0)return;let o=document.activeElement;if(!o)return;let s=n(document).filter(e=>!i.contains(e)),c=s.filter(e=>r.contains(e)).at(-1),l=c?s[s.indexOf(c)+1]:void 0;if(i.contains(o)){!e.shiftKey&&o===a.at(-1)&&t(e,l),e.shiftKey&&o===a[0]&&t(e,c);return}!e.shiftKey&&o===c&&t(e,a[0]),e.shiftKey&&l&&o===l&&t(e,a.at(-1))};return document.addEventListener(`keydown`,a,!0),()=>document.removeEventListener(`keydown`,a,!0)},[e,r,i])}exports.usePortalTabOrder=r;
|
|
2
|
+
//# sourceMappingURL=tab-bridge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-bridge.cjs","names":[],"sources":["../../../src/components/Portal/tab-bridge.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { FOCUSABLE_SELECTOR } from \"@/hooks/use-focus-trap\";\n\n/** Options of {@link usePortalTabOrder}. */\nexport interface PortalTabOrderOptions {\n /** Whether the panel is open and portalled. */\n enabled: boolean;\n /** Wrapper of the trigger — the spot in the page the panel belongs to. */\n anchor: HTMLElement | null;\n /** The portalled panel. */\n panel: HTMLElement | null;\n}\n\n/**\n * The elements `Tab` can stop on inside `root`, in document order.\n *\n * @param root - Where to look.\n * @returns The tabbable descendants that are not disabled, hidden or removed\n * from the tab order.\n */\nfunction tabbables(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => {\n if (el.tabIndex < 0 || el.hasAttribute(\"aria-hidden\")) return false;\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n}\n\n/**\n * Put a portalled panel back in the tab order, right after its trigger.\n *\n * A portal moves the panel to the end of `body`, so the browser's own order goes\n * from the trigger straight to whatever follows it in the page and reaches the\n * panel last, if ever. Measured in Chrome on a `Popover` holding two inputs:\n * in flow, `Tab` from the open trigger landed on the first input; in a portal it\n * skipped the panel and went to the next button of the page.\n *\n * While the panel is open, this restores the in-flow order:\n *\n * - `Tab` on the last stop of the anchor enters the panel's first stop.\n * - `Tab` on the panel's last stop leaves to the first stop after the anchor.\n * - `Shift+Tab` on the panel's first stop returns to the anchor.\n * - `Shift+Tab` on the first stop after the anchor enters the panel's last stop.\n *\n * Listened on `document` in the capture phase and marked consumed, so it runs\n * before an enclosing `Modal`'s focus trap, which skips a consumed `Tab`.\n *\n * @param options - Whether it is active, the anchor and the panel.\n * @returns Nothing.\n */\nexport function usePortalTabOrder({ enabled, anchor, panel }: PortalTabOrderOptions): void {\n useEffect(() => {\n if (!enabled || !anchor || !panel) return;\n\n const move = (event: KeyboardEvent, target: HTMLElement | undefined): void => {\n if (!target) return;\n event.preventDefault();\n target.focus();\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== \"Tab\" || event.defaultPrevented) return;\n const inPanel = tabbables(panel);\n if (inPanel.length === 0) return;\n const active = document.activeElement as HTMLElement | null;\n if (!active) return;\n const page = tabbables(document).filter((el) => !panel.contains(el));\n const inAnchor = page.filter((el) => anchor.contains(el));\n const lastOfAnchor = inAnchor.at(-1);\n const afterAnchor = lastOfAnchor ? page[page.indexOf(lastOfAnchor) + 1] : undefined;\n\n if (panel.contains(active)) {\n if (!event.shiftKey && active === inPanel.at(-1)) move(event, afterAnchor);\n if (event.shiftKey && active === inPanel[0]) move(event, lastOfAnchor);\n return;\n }\n if (!event.shiftKey && active === lastOfAnchor) move(event, inPanel[0]);\n if (event.shiftKey && afterAnchor && active === afterAnchor) {\n move(event, inPanel.at(-1));\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown, true);\n return () => document.removeEventListener(\"keydown\", onKeyDown, true);\n }, [enabled, anchor, panel]);\n}\n"],"mappings":"yEAoBA,SAAS,EAAU,EAAiC,CAChD,OAAO,MAAM,KAAK,EAAK,iBAA8B,EAAA,kBAAkB,CAAC,CAAC,CAAC,OAAQ,GAAO,CACrF,GAAI,EAAG,SAAW,GAAK,EAAG,aAAa,aAAa,EAAG,MAAO,GAC9D,IAAM,EAAQ,OAAO,iBAAiB,CAAE,EACxC,OAAO,EAAM,UAAY,QAAU,EAAM,aAAe,QAC5D,CAAC,CACL,CAwBA,SAAgB,EAAkB,CAAE,UAAS,SAAQ,SAAsC,EACvF,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,GAAW,CAAC,GAAU,CAAC,EAAO,OAEnC,IAAM,GAAQ,EAAsB,IAA0C,CACrE,IACL,EAAM,eAAe,EACrB,EAAO,MAAM,EACjB,EAEM,EAAa,GAA+B,CAC9C,GAAI,EAAM,MAAQ,OAAS,EAAM,iBAAkB,OACnD,IAAM,EAAU,EAAU,CAAK,EAC/B,GAAI,EAAQ,SAAW,EAAG,OAC1B,IAAM,EAAS,SAAS,cACxB,GAAI,CAAC,EAAQ,OACb,IAAM,EAAO,EAAU,QAAQ,CAAC,CAAC,OAAQ,GAAO,CAAC,EAAM,SAAS,CAAE,CAAC,EAE7D,EADW,EAAK,OAAQ,GAAO,EAAO,SAAS,CAAE,CAClC,CAAA,CAAS,GAAG,EAAE,EAC7B,EAAc,EAAe,EAAK,EAAK,QAAQ,CAAY,EAAI,GAAK,IAAA,GAE1E,GAAI,EAAM,SAAS,CAAM,EAAG,CACpB,CAAC,EAAM,UAAY,IAAW,EAAQ,GAAG,EAAE,GAAG,EAAK,EAAO,CAAW,EACrE,EAAM,UAAY,IAAW,EAAQ,IAAI,EAAK,EAAO,CAAY,EACrE,MACJ,CACI,CAAC,EAAM,UAAY,IAAW,GAAc,EAAK,EAAO,EAAQ,EAAE,EAClE,EAAM,UAAY,GAAe,IAAW,GAC5C,EAAK,EAAO,EAAQ,GAAG,EAAE,CAAC,CAElC,EAGA,OADA,SAAS,iBAAiB,UAAW,EAAW,EAAI,MACvC,SAAS,oBAAoB,UAAW,EAAW,EAAI,CACxE,EAAG,CAAC,EAAS,EAAQ,CAAK,CAAC,CAC/B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FOCUSABLE_SELECTOR as e } from "../../hooks/use-focus-trap.js";
|
|
2
|
+
import { useEffect as t } from "react";
|
|
3
|
+
//#region src/components/Portal/tab-bridge.ts
|
|
4
|
+
function n(t) {
|
|
5
|
+
return Array.from(t.querySelectorAll(e)).filter((e) => {
|
|
6
|
+
if (e.tabIndex < 0 || e.hasAttribute("aria-hidden")) return !1;
|
|
7
|
+
let t = window.getComputedStyle(e);
|
|
8
|
+
return t.display !== "none" && t.visibility !== "hidden";
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function r({ enabled: e, anchor: r, panel: i }) {
|
|
12
|
+
t(() => {
|
|
13
|
+
if (!e || !r || !i) return;
|
|
14
|
+
let t = (e, t) => {
|
|
15
|
+
t && (e.preventDefault(), t.focus());
|
|
16
|
+
}, a = (e) => {
|
|
17
|
+
if (e.key !== "Tab" || e.defaultPrevented) return;
|
|
18
|
+
let a = n(i);
|
|
19
|
+
if (a.length === 0) return;
|
|
20
|
+
let o = document.activeElement;
|
|
21
|
+
if (!o) return;
|
|
22
|
+
let s = n(document).filter((e) => !i.contains(e)), c = s.filter((e) => r.contains(e)).at(-1), l = c ? s[s.indexOf(c) + 1] : void 0;
|
|
23
|
+
if (i.contains(o)) {
|
|
24
|
+
!e.shiftKey && o === a.at(-1) && t(e, l), e.shiftKey && o === a[0] && t(e, c);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
!e.shiftKey && o === c && t(e, a[0]), e.shiftKey && l && o === l && t(e, a.at(-1));
|
|
28
|
+
};
|
|
29
|
+
return document.addEventListener("keydown", a, !0), () => document.removeEventListener("keydown", a, !0);
|
|
30
|
+
}, [
|
|
31
|
+
e,
|
|
32
|
+
r,
|
|
33
|
+
i
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { r as usePortalTabOrder };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=tab-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-bridge.js","names":[],"sources":["../../../src/components/Portal/tab-bridge.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { FOCUSABLE_SELECTOR } from \"@/hooks/use-focus-trap\";\n\n/** Options of {@link usePortalTabOrder}. */\nexport interface PortalTabOrderOptions {\n /** Whether the panel is open and portalled. */\n enabled: boolean;\n /** Wrapper of the trigger — the spot in the page the panel belongs to. */\n anchor: HTMLElement | null;\n /** The portalled panel. */\n panel: HTMLElement | null;\n}\n\n/**\n * The elements `Tab` can stop on inside `root`, in document order.\n *\n * @param root - Where to look.\n * @returns The tabbable descendants that are not disabled, hidden or removed\n * from the tab order.\n */\nfunction tabbables(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => {\n if (el.tabIndex < 0 || el.hasAttribute(\"aria-hidden\")) return false;\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n}\n\n/**\n * Put a portalled panel back in the tab order, right after its trigger.\n *\n * A portal moves the panel to the end of `body`, so the browser's own order goes\n * from the trigger straight to whatever follows it in the page and reaches the\n * panel last, if ever. Measured in Chrome on a `Popover` holding two inputs:\n * in flow, `Tab` from the open trigger landed on the first input; in a portal it\n * skipped the panel and went to the next button of the page.\n *\n * While the panel is open, this restores the in-flow order:\n *\n * - `Tab` on the last stop of the anchor enters the panel's first stop.\n * - `Tab` on the panel's last stop leaves to the first stop after the anchor.\n * - `Shift+Tab` on the panel's first stop returns to the anchor.\n * - `Shift+Tab` on the first stop after the anchor enters the panel's last stop.\n *\n * Listened on `document` in the capture phase and marked consumed, so it runs\n * before an enclosing `Modal`'s focus trap, which skips a consumed `Tab`.\n *\n * @param options - Whether it is active, the anchor and the panel.\n * @returns Nothing.\n */\nexport function usePortalTabOrder({ enabled, anchor, panel }: PortalTabOrderOptions): void {\n useEffect(() => {\n if (!enabled || !anchor || !panel) return;\n\n const move = (event: KeyboardEvent, target: HTMLElement | undefined): void => {\n if (!target) return;\n event.preventDefault();\n target.focus();\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== \"Tab\" || event.defaultPrevented) return;\n const inPanel = tabbables(panel);\n if (inPanel.length === 0) return;\n const active = document.activeElement as HTMLElement | null;\n if (!active) return;\n const page = tabbables(document).filter((el) => !panel.contains(el));\n const inAnchor = page.filter((el) => anchor.contains(el));\n const lastOfAnchor = inAnchor.at(-1);\n const afterAnchor = lastOfAnchor ? page[page.indexOf(lastOfAnchor) + 1] : undefined;\n\n if (panel.contains(active)) {\n if (!event.shiftKey && active === inPanel.at(-1)) move(event, afterAnchor);\n if (event.shiftKey && active === inPanel[0]) move(event, lastOfAnchor);\n return;\n }\n if (!event.shiftKey && active === lastOfAnchor) move(event, inPanel[0]);\n if (event.shiftKey && afterAnchor && active === afterAnchor) {\n move(event, inPanel.at(-1));\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown, true);\n return () => document.removeEventListener(\"keydown\", onKeyDown, true);\n }, [enabled, anchor, panel]);\n}\n"],"mappings":";;;AAoBA,SAAS,EAAU,GAAiC;CAChD,OAAO,MAAM,KAAK,EAAK,iBAA8B,CAAkB,CAAC,CAAC,CAAC,QAAQ,MAAO;EACrF,IAAI,EAAG,WAAW,KAAK,EAAG,aAAa,aAAa,GAAG,OAAO;EAC9D,IAAM,IAAQ,OAAO,iBAAiB,CAAE;EACxC,OAAO,EAAM,YAAY,UAAU,EAAM,eAAe;CAC5D,CAAC;AACL;AAwBA,SAAgB,EAAkB,EAAE,YAAS,WAAQ,YAAsC;CACvF,QAAgB;EACZ,IAAI,CAAC,KAAW,CAAC,KAAU,CAAC,GAAO;EAEnC,IAAM,KAAQ,GAAsB,MAA0C;GACrE,MACL,EAAM,eAAe,GACrB,EAAO,MAAM;EACjB,GAEM,KAAa,MAA+B;GAC9C,IAAI,EAAM,QAAQ,SAAS,EAAM,kBAAkB;GACnD,IAAM,IAAU,EAAU,CAAK;GAC/B,IAAI,EAAQ,WAAW,GAAG;GAC1B,IAAM,IAAS,SAAS;GACxB,IAAI,CAAC,GAAQ;GACb,IAAM,IAAO,EAAU,QAAQ,CAAC,CAAC,QAAQ,MAAO,CAAC,EAAM,SAAS,CAAE,CAAC,GAE7D,IADW,EAAK,QAAQ,MAAO,EAAO,SAAS,CAAE,CAClC,CAAA,CAAS,GAAG,EAAE,GAC7B,IAAc,IAAe,EAAK,EAAK,QAAQ,CAAY,IAAI,KAAK,KAAA;GAE1E,IAAI,EAAM,SAAS,CAAM,GAAG;IAExB,AADI,CAAC,EAAM,YAAY,MAAW,EAAQ,GAAG,EAAE,KAAG,EAAK,GAAO,CAAW,GACrE,EAAM,YAAY,MAAW,EAAQ,MAAI,EAAK,GAAO,CAAY;IACrE;GACJ;GAEA,AADI,CAAC,EAAM,YAAY,MAAW,KAAc,EAAK,GAAO,EAAQ,EAAE,GAClE,EAAM,YAAY,KAAe,MAAW,KAC5C,EAAK,GAAO,EAAQ,GAAG,EAAE,CAAC;EAElC;EAGA,OADA,SAAS,iBAAiB,WAAW,GAAW,EAAI,SACvC,SAAS,oBAAoB,WAAW,GAAW,EAAI;CACxE,GAAG;EAAC;EAAS;EAAQ;CAAK,CAAC;AAC/B"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("./Tooltip.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../Portal/Portal.cjs"),r=require("../Portal/anchor-position.cjs"),i=require("./Tooltip.module.cjs");let a=require("react"),o=require("react/jsx-runtime");var s=8;function c({content:c,placement:l=`top`,children:u,disabled:d=!1,openDelay:f=150,portal:p=!0}){let[m,h]=(0,a.useState)(!1),[g,_]=(0,a.useState)(null),[v,y]=(0,a.useState)(null),b=r.useAnchorPosition({anchor:g,floating:v,side:l,align:`center`,offset:s,enabled:p&&m}),x=(0,a.useId)(),S=(0,a.useRef)(null);function C(){S.current!==null&&(clearTimeout(S.current),S.current=null)}function w(){d||(C(),S.current=setTimeout(()=>h(!0),f))}function T(){C(),h(!1)}(0,a.useEffect)(()=>()=>{C()},[]),t.useEscapeLayer(m,T);let E=(0,a.cloneElement)(u,{onMouseEnter:w,onMouseLeave:T,onFocus:w,onBlur:T,"aria-describedby":m?x:void 0}),D=(0,o.jsx)(`span`,{ref:y,id:x,role:`tooltip`,className:e.cn(i.default.bubble,i.default[l],p&&i.default.portalled),style:b,children:c});return(0,o.jsxs)(`span`,{ref:_,className:i.default.trigger,children:[E,m&&(p?(0,o.jsx)(n.Portal,{children:D}):D)]})}exports.Tooltip=c;
|
|
2
2
|
//# sourceMappingURL=Tooltip.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n}\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n
|
|
1
|
+
{"version":3,"file":"Tooltip.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n /**\n * Render the bubble in a portal, positioned against the trigger. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the bubble —\n * a `DataTable` cell cut it 23 px short (measured in Chrome at 1440 px). In a\n * portal it escapes the clip and flips to the opposite side at the viewport\n * edge. `false` keeps the in-flow bubble.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and bubble, in px — the in-flow CSS uses the same 8 px. */\nconst TOOLTIP_OFFSET = 8;\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n *\n * `Escape` hides it without moving the pointer or the focus, which is what\n * WCAG 2.2 SC 1.4.13 (Content on Hover or Focus) asks of content that appears on\n * hover. It is an `Escape` layer like the overlays, so inside a `Modal` the first\n * press hides the tooltip and the second closes the modal.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n portal = true,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const [triggerNode, setTriggerNode] = useState<HTMLSpanElement | null>(null);\n const [bubbleNode, setBubbleNode] = useState<HTMLSpanElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: triggerNode,\n floating: bubbleNode,\n side: placement,\n align: \"center\",\n offset: TOOLTIP_OFFSET,\n enabled: portal && open,\n });\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n useEscapeLayer(open, hide);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n const bubble = (\n <span\n ref={setBubbleNode}\n id={tooltipId}\n role=\"tooltip\"\n className={cn(styles.bubble, styles[placement], portal && styles.portalled)}\n style={floatingStyle}\n >\n {content}\n </span>\n );\n\n return (\n <span ref={setTriggerNode} className={styles.trigger}>\n {trigger}\n {open && (portal ? <Portal>{bubble}</Portal> : bubble)}\n </span>\n );\n}\n"],"mappings":"mPAmCA,IAAM,EAAiB,EAYvB,SAAgB,EAAQ,CACpB,UACA,YAAY,MACZ,WACA,WAAW,GACX,YAAY,IACZ,SAAS,IACI,CACb,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EACzC,CAAC,EAAa,IAAA,EAAkB,EAAA,SAAA,CAAiC,IAAI,EACrE,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAiC,IAAI,EACnE,EAAgB,EAAA,kBAAkB,CACpC,OAAQ,EACR,SAAU,EACV,KAAM,EACN,MAAO,SACP,OAAQ,EACR,QAAS,GAAU,CACvB,CAAC,EACK,GAAA,EAAY,EAAA,MAAA,CAAM,EASlB,GAAA,EAAW,EAAA,OAAA,CAA6C,IAAI,EAElE,SAAS,GAAmB,CACpB,EAAS,UAAY,OACrB,aAAa,EAAS,OAAO,EAC7B,EAAS,QAAU,KAE3B,CAEA,SAAS,GAAa,CACd,IACJ,EAAW,EACX,EAAS,QAAU,eAAiB,EAAQ,EAAI,EAAG,CAAS,EAChE,CAEA,SAAS,GAAa,CAClB,EAAW,EACX,EAAQ,EAAK,CACjB,EAEA,EAAA,EAAA,UAAA,SACiB,CACT,EAAW,CACf,EACD,CAAC,CAAC,EAEL,EAAA,eAAe,EAAM,CAAI,EAEzB,IAAM,GAAA,EAAU,EAAA,aAAA,CAAa,EAAU,CACnC,aAAc,EACd,aAAc,EACd,QAAS,EACT,OAAQ,EACR,mBAAoB,EAAO,EAAY,IAAA,EAC3C,CAA4B,EAEtB,GACF,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,IAAK,EACL,GAAI,EACJ,KAAK,UACL,UAAW,EAAA,GAAG,EAAA,QAAO,OAAQ,EAAA,QAAO,GAAY,GAAU,EAAA,QAAO,SAAS,EAC1E,MAAO,EAEN,SAAA,CACC,CAAA,EAGV,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,IAAK,EAAgB,UAAW,EAAA,QAAO,QAA7C,SAAA,CACK,EACA,IAAS,GAAS,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAA,SAAS,CAAe,CAAA,EAAI,EAC7C,GAEd"}
|
|
@@ -1,40 +1,54 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { useEscapeLayer as t } from "../Portal/escape-layer.js";
|
|
3
|
+
import { Portal as n } from "../Portal/Portal.js";
|
|
4
|
+
import { useAnchorPosition as r } from "../Portal/anchor-position.js";
|
|
5
|
+
import i from "./Tooltip.module.js";
|
|
6
|
+
import { cloneElement as a, useEffect as o, useId as s, useRef as c, useState as l } from "react";
|
|
7
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
5
8
|
//#region src/components/Tooltip/Tooltip.tsx
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
var f = 8;
|
|
10
|
+
function p({ content: p, placement: m = "top", children: h, disabled: g = !1, openDelay: _ = 150, portal: v = !0 }) {
|
|
11
|
+
let [y, b] = l(!1), [x, S] = l(null), [C, w] = l(null), T = r({
|
|
12
|
+
anchor: x,
|
|
13
|
+
floating: C,
|
|
14
|
+
side: m,
|
|
15
|
+
align: "center",
|
|
16
|
+
offset: f,
|
|
17
|
+
enabled: v && y
|
|
18
|
+
}), E = s(), D = c(null);
|
|
19
|
+
function O() {
|
|
20
|
+
D.current !== null && (clearTimeout(D.current), D.current = null);
|
|
10
21
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
22
|
+
function k() {
|
|
23
|
+
g || (O(), D.current = setTimeout(() => b(!0), _));
|
|
13
24
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
25
|
+
function A() {
|
|
26
|
+
O(), b(!1);
|
|
16
27
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}, []);
|
|
20
|
-
let
|
|
21
|
-
onMouseEnter:
|
|
22
|
-
onMouseLeave:
|
|
23
|
-
onFocus:
|
|
24
|
-
onBlur:
|
|
25
|
-
"aria-describedby":
|
|
28
|
+
o(() => () => {
|
|
29
|
+
O();
|
|
30
|
+
}, []), t(y, A);
|
|
31
|
+
let j = a(h, {
|
|
32
|
+
onMouseEnter: k,
|
|
33
|
+
onMouseLeave: A,
|
|
34
|
+
onFocus: k,
|
|
35
|
+
onBlur: A,
|
|
36
|
+
"aria-describedby": y ? E : void 0
|
|
37
|
+
}), M = /* @__PURE__ */ u("span", {
|
|
38
|
+
ref: w,
|
|
39
|
+
id: E,
|
|
40
|
+
role: "tooltip",
|
|
41
|
+
className: e(i.bubble, i[m], v && i.portalled),
|
|
42
|
+
style: T,
|
|
43
|
+
children: p
|
|
26
44
|
});
|
|
27
|
-
return /* @__PURE__ */
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
role: "tooltip",
|
|
32
|
-
className: e(t.bubble, t[u]),
|
|
33
|
-
children: l
|
|
34
|
-
})]
|
|
45
|
+
return /* @__PURE__ */ d("span", {
|
|
46
|
+
ref: S,
|
|
47
|
+
className: i.trigger,
|
|
48
|
+
children: [j, y && (v ? /* @__PURE__ */ u(n, { children: M }) : M)]
|
|
35
49
|
});
|
|
36
50
|
}
|
|
37
51
|
//#endregion
|
|
38
|
-
export {
|
|
52
|
+
export { p as Tooltip };
|
|
39
53
|
|
|
40
54
|
//# sourceMappingURL=Tooltip.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n}\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n
|
|
1
|
+
{"version":3,"file":"Tooltip.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n /**\n * Render the bubble in a portal, positioned against the trigger. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the bubble —\n * a `DataTable` cell cut it 23 px short (measured in Chrome at 1440 px). In a\n * portal it escapes the clip and flips to the opposite side at the viewport\n * edge. `false` keeps the in-flow bubble.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and bubble, in px — the in-flow CSS uses the same 8 px. */\nconst TOOLTIP_OFFSET = 8;\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n *\n * `Escape` hides it without moving the pointer or the focus, which is what\n * WCAG 2.2 SC 1.4.13 (Content on Hover or Focus) asks of content that appears on\n * hover. It is an `Escape` layer like the overlays, so inside a `Modal` the first\n * press hides the tooltip and the second closes the modal.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n portal = true,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const [triggerNode, setTriggerNode] = useState<HTMLSpanElement | null>(null);\n const [bubbleNode, setBubbleNode] = useState<HTMLSpanElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: triggerNode,\n floating: bubbleNode,\n side: placement,\n align: \"center\",\n offset: TOOLTIP_OFFSET,\n enabled: portal && open,\n });\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n useEscapeLayer(open, hide);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n const bubble = (\n <span\n ref={setBubbleNode}\n id={tooltipId}\n role=\"tooltip\"\n className={cn(styles.bubble, styles[placement], portal && styles.portalled)}\n style={floatingStyle}\n >\n {content}\n </span>\n );\n\n return (\n <span ref={setTriggerNode} className={styles.trigger}>\n {trigger}\n {open && (portal ? <Portal>{bubble}</Portal> : bubble)}\n </span>\n );\n}\n"],"mappings":";;;;;;;;AAmCA,IAAM,IAAiB;AAYvB,SAAgB,EAAQ,EACpB,YACA,eAAY,OACZ,aACA,cAAW,IACX,eAAY,KACZ,YAAS,MACI;CACb,IAAM,CAAC,GAAM,KAAW,EAAkB,EAAK,GACzC,CAAC,GAAa,KAAkB,EAAiC,IAAI,GACrE,CAAC,GAAY,KAAiB,EAAiC,IAAI,GACnE,IAAgB,EAAkB;EACpC,QAAQ;EACR,UAAU;EACV,MAAM;EACN,OAAO;EACP,QAAQ;EACR,SAAS,KAAU;CACvB,CAAC,GACK,IAAY,EAAM,GASlB,IAAW,EAA6C,IAAI;CAElE,SAAS,IAAmB;EACxB,AAAI,EAAS,YAAY,SACrB,aAAa,EAAS,OAAO,GAC7B,EAAS,UAAU;CAE3B;CAEA,SAAS,IAAa;EACd,MACJ,EAAW,GACX,EAAS,UAAU,iBAAiB,EAAQ,EAAI,GAAG,CAAS;CAChE;CAEA,SAAS,IAAa;EAElB,AADA,EAAW,GACX,EAAQ,EAAK;CACjB;CAQA,AANA,cACiB;EACT,EAAW;CACf,GACD,CAAC,CAAC,GAEL,EAAe,GAAM,CAAI;CAEzB,IAAM,IAAU,EAAa,GAAU;EACnC,cAAc;EACd,cAAc;EACd,SAAS;EACT,QAAQ;EACR,oBAAoB,IAAO,IAAY,KAAA;CAC3C,CAA4B,GAEtB,IACF,kBAAC,QAAD;EACI,KAAK;EACL,IAAI;EACJ,MAAK;EACL,WAAW,EAAG,EAAO,QAAQ,EAAO,IAAY,KAAU,EAAO,SAAS;EAC1E,OAAO;EAEN,UAAA;CACC,CAAA;CAGV,OACI,kBAAC,QAAD;EAAM,KAAK;EAAgB,WAAW,EAAO;EAA7C,UAAA,CACK,GACA,MAAS,IAAS,kBAAC,GAAD,EAAA,UAAS,EAAe,CAAA,IAAI,EAC7C;;AAEd"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
require("../../styles/component/Tooltip.css");
|
|
2
|
-
var e=`tempest_trigger_Dmc5E`,t=`tempest_bubble_TPGHB`,n=`tempest_tempest-tooltip-in_csYeZ`,r=`tempest_top_m2tnn`,i=`tempest_bottom_9Twz4`,a=`tempest_left_UPdrG`,o=`tempest_right_CV--T`,s={trigger:e,bubble:t,tempestTooltipIn:n,top:r,bottom:i,left:a,right:o};exports.bottom=i,exports.bubble=t,exports.default=
|
|
2
|
+
var e=`tempest_trigger_Dmc5E`,t=`tempest_bubble_TPGHB`,n=`tempest_tempest-tooltip-in_csYeZ`,r=`tempest_top_m2tnn`,i=`tempest_bottom_9Twz4`,a=`tempest_left_UPdrG`,o=`tempest_right_CV--T`,s=`tempest_portalled_MImIY`,c={trigger:e,bubble:t,tempestTooltipIn:n,top:r,bottom:i,left:a,right:o,portalled:s};exports.bottom=i,exports.bubble=t,exports.default=c,exports.left=a,exports.portalled=s,exports.right=o,exports.tempestTooltipIn=n,exports.top=r,exports.trigger=e;
|
|
3
3
|
//# sourceMappingURL=Tooltip.module.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.module.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
|
|
1
|
+
{"version":3,"file":"Tooltip.module.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.portalled {\n right: auto;\n bottom: auto;\n transform: none;\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import "../../styles/component/Tooltip.css";
|
|
2
2
|
//#region src/components/Tooltip/Tooltip.module.css
|
|
3
|
-
var e = "tempest_trigger_Dmc5E", t = "tempest_bubble_TPGHB", n = "tempest_tempest-tooltip-in_csYeZ", r = "tempest_top_m2tnn", i = "tempest_bottom_9Twz4", a = "tempest_left_UPdrG", o = "tempest_right_CV--T", s = {
|
|
3
|
+
var e = "tempest_trigger_Dmc5E", t = "tempest_bubble_TPGHB", n = "tempest_tempest-tooltip-in_csYeZ", r = "tempest_top_m2tnn", i = "tempest_bottom_9Twz4", a = "tempest_left_UPdrG", o = "tempest_right_CV--T", s = "tempest_portalled_MImIY", c = {
|
|
4
4
|
trigger: e,
|
|
5
5
|
bubble: t,
|
|
6
6
|
tempestTooltipIn: n,
|
|
7
7
|
top: r,
|
|
8
8
|
bottom: i,
|
|
9
9
|
left: a,
|
|
10
|
-
right: o
|
|
10
|
+
right: o,
|
|
11
|
+
portalled: s
|
|
11
12
|
};
|
|
12
13
|
//#endregion
|
|
13
|
-
export { i as bottom, t as bubble,
|
|
14
|
+
export { i as bottom, t as bubble, c as default, a as left, s as portalled, o as right, n as tempestTooltipIn, r as top, e as trigger };
|
|
14
15
|
|
|
15
16
|
//# sourceMappingURL=Tooltip.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.module.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
|
|
1
|
+
{"version":3,"file":"Tooltip.module.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.portalled {\n right: auto;\n bottom: auto;\n transform: none;\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("react");var t=[`a[href]`,`button:not([disabled])`,`textarea:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`[tabindex]:not([tabindex='-1'])`].join(`,`);function n(n,r){(0,e.useEffect)(()=>{if(!r)return;let e=n.current;if(!e)return;let i=document.activeElement;function a(){return Array.from(e?.querySelectorAll(t)??[]).filter(e=>{if(e.hasAttribute(`aria-hidden`))return!1;let t=typeof window<`u`?window.getComputedStyle(e):null;return!t||t.display!==`none`&&t.visibility!==`hidden`})}function o(t){if(t.key!==`Tab
|
|
1
|
+
let e=require("react");var t=[`a[href]`,`button:not([disabled])`,`textarea:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`[tabindex]:not([tabindex='-1'])`].join(`,`);function n(n,r){(0,e.useEffect)(()=>{if(!r)return;let e=n.current;if(!e)return;let i=document.activeElement;function a(){return Array.from(e?.querySelectorAll(t)??[]).filter(e=>{if(e.hasAttribute(`aria-hidden`))return!1;let t=typeof window<`u`?window.getComputedStyle(e):null;return!t||t.display!==`none`&&t.visibility!==`hidden`})}function o(t){if(t.key!==`Tab`||t.defaultPrevented)return;let n=a();if(n.length===0){t.preventDefault();return}let r=n[0],i=n[n.length-1],o=document.activeElement;t.shiftKey?(o===r||!o||!e?.contains(o))&&(t.preventDefault(),i.focus()):o===i&&(t.preventDefault(),r.focus())}return a()[0]?.focus(),document.addEventListener(`keydown`,o),()=>{document.removeEventListener(`keydown`,o),i?.focus?.()}},[n,r])}exports.FOCUSABLE_SELECTOR=t,exports.useFocusTrap=n;
|
|
2
2
|
//# sourceMappingURL=use-focus-trap.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-focus-trap.cjs","names":[],"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { RefObject } from \"react\";\n\
|
|
1
|
+
{"version":3,"file":"use-focus-trap.cjs","names":[],"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { RefObject } from \"react\";\n\n/** Elements that can take keyboard focus — shared with the portal tab order. */\nexport const FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\n/**\n * Trap keyboard focus inside `containerRef` while `active` is true. Cycles\n * Tab and Shift+Tab between the first and last focusable descendants. Pair\n * with Modal/Drawer for fully-accessible overlays.\n */\nexport function useFocusTrap(containerRef: RefObject<HTMLElement | null>, active: boolean): void {\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n function getFocusable(): HTMLElement[] {\n return Array.from(\n container?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR) ?? [],\n ).filter((el) => {\n if (el.hasAttribute(\"aria-hidden\")) return false;\n const style = typeof window !== \"undefined\" ? window.getComputedStyle(el) : null;\n if (style && (style.display === \"none\" || style.visibility === \"hidden\")) {\n return false;\n }\n return true;\n });\n }\n\n /**\n * Keep `Tab` and `Shift+Tab` inside the container.\n *\n * A `Tab` already consumed is left alone: a portalled panel inside the\n * container (a `Popover`, say) routes its own `Tab` back next to its\n * trigger, and wrapping here as well would move focus a second time.\n */\n function handleKeydown(event: KeyboardEvent): void {\n if (event.key !== \"Tab\" || event.defaultPrevented) return;\n const elements = getFocusable();\n if (elements.length === 0) {\n event.preventDefault();\n return;\n }\n const first = elements[0]!;\n const last = elements[elements.length - 1]!;\n const current = document.activeElement as HTMLElement | null;\n if (event.shiftKey) {\n if (current === first || !current || !container?.contains(current)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n if (current === last) {\n event.preventDefault();\n first.focus();\n }\n }\n }\n\n const focusable = getFocusable();\n focusable[0]?.focus();\n document.addEventListener(\"keydown\", handleKeydown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeydown);\n previouslyFocused?.focus?.();\n };\n }, [containerRef, active]);\n}\n"],"mappings":"uBAIA,IAAa,EAAqB,CAC9B,UACA,yBACA,2BACA,wBACA,yBACA,iCACJ,CAAC,CAAC,KAAK,GAAG,EAOV,SAAgB,EAAa,EAA6C,EAAuB,EAC7F,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAQ,OACb,IAAM,EAAY,EAAa,QAC/B,GAAI,CAAC,EAAW,OAEhB,IAAM,EAAoB,SAAS,cAEnC,SAAS,GAA8B,CACnC,OAAO,MAAM,KACT,GAAW,iBAA8B,CAAkB,GAAK,CAAC,CACrE,CAAC,CAAC,OAAQ,GAAO,CACb,GAAI,EAAG,aAAa,aAAa,EAAG,MAAO,GAC3C,IAAM,EAAQ,OAAO,OAAW,IAAc,OAAO,iBAAiB,CAAE,EAAI,KAI5E,MAHI,IAAU,EAAM,UAAY,QAAU,EAAM,aAAe,QAInE,CAAC,CACL,CASA,SAAS,EAAc,EAA4B,CAC/C,GAAI,EAAM,MAAQ,OAAS,EAAM,iBAAkB,OACnD,IAAM,EAAW,EAAa,EAC9B,GAAI,EAAS,SAAW,EAAG,CACvB,EAAM,eAAe,EACrB,MACJ,CACA,IAAM,EAAQ,EAAS,GACjB,EAAO,EAAS,EAAS,OAAS,GAClC,EAAU,SAAS,cACrB,EAAM,UACF,IAAY,GAAS,CAAC,GAAW,CAAC,GAAW,SAAS,CAAO,KAC7D,EAAM,eAAe,EACrB,EAAK,MAAM,GAGX,IAAY,IACZ,EAAM,eAAe,EACrB,EAAM,MAAM,EAGxB,CAKA,OAFA,EAAA,CAAA,CAAU,EAAE,EAAE,MAAM,EACpB,SAAS,iBAAiB,UAAW,CAAa,MACrC,CACT,SAAS,oBAAoB,UAAW,CAAa,EACrD,GAAmB,QAAQ,CAC/B,CACJ,EAAG,CAAC,EAAc,CAAM,CAAC,CAC7B"}
|
|
@@ -22,7 +22,7 @@ function n(n, r) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
function o(t) {
|
|
25
|
-
if (t.key !== "Tab") return;
|
|
25
|
+
if (t.key !== "Tab" || t.defaultPrevented) return;
|
|
26
26
|
let n = a();
|
|
27
27
|
if (n.length === 0) {
|
|
28
28
|
t.preventDefault();
|
|
@@ -37,6 +37,6 @@ function n(n, r) {
|
|
|
37
37
|
}, [n, r]);
|
|
38
38
|
}
|
|
39
39
|
//#endregion
|
|
40
|
-
export { n as useFocusTrap };
|
|
40
|
+
export { t as FOCUSABLE_SELECTOR, n as useFocusTrap };
|
|
41
41
|
|
|
42
42
|
//# sourceMappingURL=use-focus-trap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-focus-trap.js","names":[],"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { RefObject } from \"react\";\n\
|
|
1
|
+
{"version":3,"file":"use-focus-trap.js","names":[],"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { RefObject } from \"react\";\n\n/** Elements that can take keyboard focus — shared with the portal tab order. */\nexport const FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\n/**\n * Trap keyboard focus inside `containerRef` while `active` is true. Cycles\n * Tab and Shift+Tab between the first and last focusable descendants. Pair\n * with Modal/Drawer for fully-accessible overlays.\n */\nexport function useFocusTrap(containerRef: RefObject<HTMLElement | null>, active: boolean): void {\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n function getFocusable(): HTMLElement[] {\n return Array.from(\n container?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR) ?? [],\n ).filter((el) => {\n if (el.hasAttribute(\"aria-hidden\")) return false;\n const style = typeof window !== \"undefined\" ? window.getComputedStyle(el) : null;\n if (style && (style.display === \"none\" || style.visibility === \"hidden\")) {\n return false;\n }\n return true;\n });\n }\n\n /**\n * Keep `Tab` and `Shift+Tab` inside the container.\n *\n * A `Tab` already consumed is left alone: a portalled panel inside the\n * container (a `Popover`, say) routes its own `Tab` back next to its\n * trigger, and wrapping here as well would move focus a second time.\n */\n function handleKeydown(event: KeyboardEvent): void {\n if (event.key !== \"Tab\" || event.defaultPrevented) return;\n const elements = getFocusable();\n if (elements.length === 0) {\n event.preventDefault();\n return;\n }\n const first = elements[0]!;\n const last = elements[elements.length - 1]!;\n const current = document.activeElement as HTMLElement | null;\n if (event.shiftKey) {\n if (current === first || !current || !container?.contains(current)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n if (current === last) {\n event.preventDefault();\n first.focus();\n }\n }\n }\n\n const focusable = getFocusable();\n focusable[0]?.focus();\n document.addEventListener(\"keydown\", handleKeydown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeydown);\n previouslyFocused?.focus?.();\n };\n }, [containerRef, active]);\n}\n"],"mappings":";;AAIA,IAAa,IAAqB;CAC9B;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC,CAAC,KAAK,GAAG;AAOV,SAAgB,EAAa,GAA6C,GAAuB;CAC7F,QAAgB;EACZ,IAAI,CAAC,GAAQ;EACb,IAAM,IAAY,EAAa;EAC/B,IAAI,CAAC,GAAW;EAEhB,IAAM,IAAoB,SAAS;EAEnC,SAAS,IAA8B;GACnC,OAAO,MAAM,KACT,GAAW,iBAA8B,CAAkB,KAAK,CAAC,CACrE,CAAC,CAAC,QAAQ,MAAO;IACb,IAAI,EAAG,aAAa,aAAa,GAAG,OAAO;IAC3C,IAAM,IAAQ,OAAO,SAAW,MAAc,OAAO,iBAAiB,CAAE,IAAI;IAI5E,OAHI,MAAU,EAAM,YAAY,UAAU,EAAM,eAAe;GAInE,CAAC;EACL;EASA,SAAS,EAAc,GAA4B;GAC/C,IAAI,EAAM,QAAQ,SAAS,EAAM,kBAAkB;GACnD,IAAM,IAAW,EAAa;GAC9B,IAAI,EAAS,WAAW,GAAG;IACvB,EAAM,eAAe;IACrB;GACJ;GACA,IAAM,IAAQ,EAAS,IACjB,IAAO,EAAS,EAAS,SAAS,IAClC,IAAU,SAAS;GACzB,AAAI,EAAM,YACF,MAAY,KAAS,CAAC,KAAW,CAAC,GAAW,SAAS,CAAO,OAC7D,EAAM,eAAe,GACrB,EAAK,MAAM,KAGX,MAAY,MACZ,EAAM,eAAe,GACrB,EAAM,MAAM;EAGxB;EAKA,OAFA,EAAA,CAAA,CAAU,EAAE,EAAE,MAAM,GACpB,SAAS,iBAAiB,WAAW,CAAa,SACrC;GAET,AADA,SAAS,oBAAoB,WAAW,CAAa,GACrD,GAAmB,QAAQ;EAC/B;CACJ,GAAG,CAAC,GAAc,CAAM,CAAC;AAC7B"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("react");function t(e,t,n){if(t===n||t<0||n<0||t>=e.length||n>=e.length)return e;let r=e.slice(),[i]=r.splice(t,1);return r.splice(n,0,i),r}function n(e,t,n){if(!e)return null;let r=e=>{let r=e.getBoundingClientRect();return n>=r.top&&n<=r.bottom&&t>=r.left&&t<=r.right};for(let t of Array.from(e.querySelectorAll(`[data-sortable-index]`))){if(!r(t))continue;let e=Number(t.dataset.sortableIndex);return Number.isNaN(e)?null:{index:e,group:t.dataset.sortableGroup}}for(let t of Array.from(e.querySelectorAll(`[data-sortable-empty]`)))if(r(t))return{index:0,group:t.dataset.sortableGroup};return null}function r(t){let{itemCount:r,onReorder:i,disabled:a=!1,roleDescription:o=`Sortable item`}=t,[s,c]=(0,e.useState)(null),[l,u]=(0,e.useState)(null),d=(0,e.useRef)(null),f=(0,e.useRef)(i),p=(0,e.useRef)(null),m=(0,e.useRef)(null),[h,g]=(0,e.useState)(void 0),[_,v]=(0,e.useState)(void 0),y=(0,e.useRef)(void 0),b=(0,e.useRef)(void 0),x=(0,e.useCallback)(e=>{y.current=e,g(e)},[]),S=(0,e.useCallback)(e=>{b.current=e,v(e)},[]),C=(0,e.useCallback)(e=>{p.current=e,c(e)},[]),w=(0,e.useCallback)(e=>{m.current=e,u(e)},[]);(0,e.useEffect)(()=>{f.current=i},[i]);let T=(0,e.useCallback)(()=>{x(void 0),S(void 0),C(null),w(null)},[x,C,S,w]);(0,e.useEffect)(()=>{T()},[r,T]);let E=(0,e.useCallback)((e,t,n,r)=>{(e!==t||n!==r)&&f.current({from:e,to:t,fromGroup:n,toGroup:r}),T()},[T]),D=(0,e.useCallback)(e=>{d.current=e},[]),O=(0,e.useCallback)((e,t,n)=>{a||(n.currentTarget.setPointerCapture?.(n.pointerId),x(t),S(t),C(e),w(e))},[a,x,C,S,w]);(0,e.useEffect)(()=>{if(s===null)return;function e(e){let t=n(d.current,e.clientX,e.clientY);t&&(S(t.group),w(t.index))}function t(){let e=p.current,t=m.current;e!==null&&t!==null?E(e,t,y.current,b.current):T()}function r(e){e.key===`Escape`&&T()}return window.addEventListener(`pointermove`,e),window.addEventListener(`pointerup`,t),window.addEventListener(`pointercancel`,T),window.addEventListener(`keydown`,r),()=>{window.removeEventListener(`pointermove`,e),window.removeEventListener(`pointerup`,t),window.removeEventListener(`pointercancel`,T),window.removeEventListener(`keydown`,r)}},[s,E,T,S,w]);let k=(0,e.useCallback)((e,t,n)=>{if(a)return;let i=s!==null;switch(n.key){case` `:case`Enter`:n.preventDefault(),i?l!==null&&E(s,l,h,_):(x(t),S(t),C(e),w(e));break;case`ArrowDown`:case`ArrowRight`:if(!i)return;n.preventDefault(),m.current!==null&&w(Math.min(m.current+1,r-1));break;case`ArrowUp`:case`ArrowLeft`:if(!i)return;n.preventDefault(),m.current!==null&&w(Math.max(m.current-1,0));break;case`Escape`:if(!i)return;n.preventDefault(),T()}},[h,s,E,_,a,r,l,T,x,C,S,w]);return{activeIndex:s,overIndex:l,activeGroup:h,overGroup:_,getItemProps:(0,e.useCallback)((e,t)=>({"data-sortable-index":e,"data-sortable-group":t,"data-sortable-active":s===e&&h===t||void 0,onPointerDown:n=>O(e,t,n),onKeyDown:n=>k(e,t,n),tabIndex:a?-1:0,role:`option`,"aria-roledescription":o,"aria-selected":s===e&&h===t}),[h,s,a,k,O,o]),getEmptyGroupProps:(0,e.useCallback)(e=>({"data-sortable-group":e,"data-sortable-empty":!0}),[]),getListProps:(0,e.useCallback)(()=>({role:`listbox`,"aria-orientation":`vertical`}),[]),cancel:T,setContainer:D}}exports.moveItem=t,exports.useSortable=r;
|
|
1
|
+
let e=require("react");function t(e,t,n){if(t===n||t<0||n<0||t>=e.length||n>=e.length)return e;let r=e.slice(),[i]=r.splice(t,1);return r.splice(n,0,i),r}function n(e,t,n){if(!e)return null;let r=e=>{let r=e.getBoundingClientRect();return n>=r.top&&n<=r.bottom&&t>=r.left&&t<=r.right};for(let t of Array.from(e.querySelectorAll(`[data-sortable-index]`))){if(!r(t))continue;let e=Number(t.dataset.sortableIndex);return Number.isNaN(e)?null:{index:e,group:t.dataset.sortableGroup}}for(let t of Array.from(e.querySelectorAll(`[data-sortable-empty]`)))if(r(t))return{index:0,group:t.dataset.sortableGroup};return null}function r(t){let{itemCount:r,onReorder:i,disabled:a=!1,roleDescription:o=`Sortable item`}=t,[s,c]=(0,e.useState)(null),[l,u]=(0,e.useState)(null),d=(0,e.useRef)(null),f=(0,e.useRef)(i),p=(0,e.useRef)(null),m=(0,e.useRef)(null),[h,g]=(0,e.useState)(void 0),[_,v]=(0,e.useState)(void 0),y=(0,e.useRef)(void 0),b=(0,e.useRef)(void 0),x=(0,e.useCallback)(e=>{y.current=e,g(e)},[]),S=(0,e.useCallback)(e=>{b.current=e,v(e)},[]),C=(0,e.useCallback)(e=>{p.current=e,c(e)},[]),w=(0,e.useCallback)(e=>{m.current=e,u(e)},[]);(0,e.useEffect)(()=>{f.current=i},[i]);let T=(0,e.useCallback)(()=>{x(void 0),S(void 0),C(null),w(null)},[x,C,S,w]);(0,e.useEffect)(()=>{T()},[r,T]);let E=(0,e.useCallback)((e,t,n,r)=>{(e!==t||n!==r)&&f.current({from:e,to:t,fromGroup:n,toGroup:r}),T()},[T]),D=(0,e.useCallback)(e=>{d.current=e},[]),O=(0,e.useCallback)((e,t,n)=>{a||(n.currentTarget.setPointerCapture?.(n.pointerId),x(t),S(t),C(e),w(e))},[a,x,C,S,w]);(0,e.useEffect)(()=>{if(s===null)return;function e(e){let t=n(d.current,e.clientX,e.clientY);t&&(S(t.group),w(t.index))}function t(){let e=p.current,t=m.current;e!==null&&t!==null?E(e,t,y.current,b.current):T()}function r(e){e.key===`Escape`&&(e.preventDefault(),T())}return window.addEventListener(`pointermove`,e),window.addEventListener(`pointerup`,t),window.addEventListener(`pointercancel`,T),window.addEventListener(`keydown`,r,!0),()=>{window.removeEventListener(`pointermove`,e),window.removeEventListener(`pointerup`,t),window.removeEventListener(`pointercancel`,T),window.removeEventListener(`keydown`,r,!0)}},[s,E,T,S,w]);let k=(0,e.useCallback)((e,t,n)=>{if(a)return;let i=s!==null;switch(n.key){case` `:case`Enter`:n.preventDefault(),i?l!==null&&E(s,l,h,_):(x(t),S(t),C(e),w(e));break;case`ArrowDown`:case`ArrowRight`:if(!i)return;n.preventDefault(),m.current!==null&&w(Math.min(m.current+1,r-1));break;case`ArrowUp`:case`ArrowLeft`:if(!i)return;n.preventDefault(),m.current!==null&&w(Math.max(m.current-1,0));break;case`Escape`:if(!i)return;n.preventDefault(),T()}},[h,s,E,_,a,r,l,T,x,C,S,w]);return{activeIndex:s,overIndex:l,activeGroup:h,overGroup:_,getItemProps:(0,e.useCallback)((e,t)=>({"data-sortable-index":e,"data-sortable-group":t,"data-sortable-active":s===e&&h===t||void 0,onPointerDown:n=>O(e,t,n),onKeyDown:n=>k(e,t,n),tabIndex:a?-1:0,role:`option`,"aria-roledescription":o,"aria-selected":s===e&&h===t}),[h,s,a,k,O,o]),getEmptyGroupProps:(0,e.useCallback)(e=>({"data-sortable-group":e,"data-sortable-empty":!0}),[]),getListProps:(0,e.useCallback)(()=>({role:`listbox`,"aria-orientation":`vertical`}),[]),cancel:T,setContainer:D}}exports.moveItem=t,exports.useSortable=r;
|
|
2
2
|
//# sourceMappingURL=use-sortable.cjs.map
|