@ansh-ui/react 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/chunk-5CFQFMP5.js +178 -0
- package/dist/chunk-5CFQFMP5.js.map +1 -0
- package/dist/chunk-6VRF7X5C.cjs +164 -0
- package/dist/chunk-6VRF7X5C.cjs.map +1 -0
- package/dist/chunk-AVPEA5PB.cjs +718 -0
- package/dist/chunk-AVPEA5PB.cjs.map +1 -0
- package/dist/chunk-C32LKF23.cjs +198 -0
- package/dist/chunk-C32LKF23.cjs.map +1 -0
- package/dist/chunk-C5JG6TMM.cjs +1200 -0
- package/dist/chunk-C5JG6TMM.cjs.map +1 -0
- package/dist/chunk-DATBU46V.cjs +614 -0
- package/dist/chunk-DATBU46V.cjs.map +1 -0
- package/dist/chunk-EGG4CP7C.js +320 -0
- package/dist/chunk-EGG4CP7C.js.map +1 -0
- package/dist/chunk-H34OXXJU.js +55 -0
- package/dist/chunk-H34OXXJU.js.map +1 -0
- package/dist/chunk-HO2YSF5O.js +187 -0
- package/dist/chunk-HO2YSF5O.js.map +1 -0
- package/dist/chunk-HPGCILO4.js +760 -0
- package/dist/chunk-HPGCILO4.js.map +1 -0
- package/dist/chunk-IBXLC5IE.js +154 -0
- package/dist/chunk-IBXLC5IE.js.map +1 -0
- package/dist/chunk-LDCZQP3R.js +1185 -0
- package/dist/chunk-LDCZQP3R.js.map +1 -0
- package/dist/chunk-LXCIXR5T.js +27 -0
- package/dist/chunk-LXCIXR5T.js.map +1 -0
- package/dist/chunk-O2PX54KX.js +94 -0
- package/dist/chunk-O2PX54KX.js.map +1 -0
- package/dist/chunk-OHBWBXLO.cjs +101 -0
- package/dist/chunk-OHBWBXLO.cjs.map +1 -0
- package/dist/chunk-OJN5JBNP.cjs +776 -0
- package/dist/chunk-OJN5JBNP.cjs.map +1 -0
- package/dist/chunk-OLTS6G5M.js +606 -0
- package/dist/chunk-OLTS6G5M.js.map +1 -0
- package/dist/chunk-PO5DU3FU.cjs +29 -0
- package/dist/chunk-PO5DU3FU.cjs.map +1 -0
- package/dist/chunk-QO3E7TJS.js +2940 -0
- package/dist/chunk-QO3E7TJS.js.map +1 -0
- package/dist/chunk-RHTLNSVW.cjs +2976 -0
- package/dist/chunk-RHTLNSVW.cjs.map +1 -0
- package/dist/chunk-SFU5EPSN.cjs +61 -0
- package/dist/chunk-SFU5EPSN.cjs.map +1 -0
- package/dist/chunk-T4AK2KLM.js +699 -0
- package/dist/chunk-T4AK2KLM.js.map +1 -0
- package/dist/chunk-WGYNCSTP.cjs +333 -0
- package/dist/chunk-WGYNCSTP.cjs.map +1 -0
- package/dist/chunk-XEP6P7E6.js +279 -0
- package/dist/chunk-XEP6P7E6.js.map +1 -0
- package/dist/chunk-XMXECSQO.cjs +286 -0
- package/dist/chunk-XMXECSQO.cjs.map +1 -0
- package/dist/chunk-ZWAFLHC2.cjs +181 -0
- package/dist/chunk-ZWAFLHC2.cjs.map +1 -0
- package/dist/data.cjs +71 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.cts +316 -0
- package/dist/data.d.ts +316 -0
- package/dist/data.js +6 -0
- package/dist/data.js.map +1 -0
- package/dist/form.cjs +153 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +660 -0
- package/dist/form.d.ts +660 -0
- package/dist/form.js +8 -0
- package/dist/form.js.map +1 -0
- package/dist/index.cjs +1641 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +552 -0
- package/dist/index.d.ts +552 -0
- package/dist/index.js +956 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.cjs +68 -0
- package/dist/layout.cjs.map +1 -0
- package/dist/layout.d.cts +169 -0
- package/dist/layout.d.ts +169 -0
- package/dist/layout.js +7 -0
- package/dist/layout.js.map +1 -0
- package/dist/motion-CQmCq6_k.d.ts +395 -0
- package/dist/motion-CSX4g2N8.d.cts +395 -0
- package/dist/motion.cjs +79 -0
- package/dist/motion.cjs.map +1 -0
- package/dist/motion.d.cts +4 -0
- package/dist/motion.d.ts +4 -0
- package/dist/motion.js +6 -0
- package/dist/motion.js.map +1 -0
- package/dist/navigation.cjs +48 -0
- package/dist/navigation.cjs.map +1 -0
- package/dist/navigation.d.cts +210 -0
- package/dist/navigation.d.ts +210 -0
- package/dist/navigation.js +7 -0
- package/dist/navigation.js.map +1 -0
- package/dist/overlay.cjs +69 -0
- package/dist/overlay.cjs.map +1 -0
- package/dist/overlay.d.cts +330 -0
- package/dist/overlay.d.ts +330 -0
- package/dist/overlay.js +8 -0
- package/dist/overlay.js.map +1 -0
- package/dist/position-DVScKcaa.d.cts +66 -0
- package/dist/position-DVScKcaa.d.ts +66 -0
- package/dist/primitives.cjs +162 -0
- package/dist/primitives.cjs.map +1 -0
- package/dist/primitives.d.cts +294 -0
- package/dist/primitives.d.ts +294 -0
- package/dist/primitives.js +9 -0
- package/dist/primitives.js.map +1 -0
- package/dist/sidebar-D-PafAXw.d.cts +71 -0
- package/dist/sidebar-D-PafAXw.d.ts +71 -0
- package/dist/styles.css +5575 -0
- package/dist/use-observers-B9qtimrk.d.cts +99 -0
- package/dist/use-observers-B9qtimrk.d.ts +99 -0
- package/package.json +92 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { useState, useId, useEffect, useCallback, useLayoutEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/primitives/portal.tsx
|
|
6
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
7
|
+
function Portal({ children, container, disabled }) {
|
|
8
|
+
const [mounted, setMounted] = useState(false);
|
|
9
|
+
useIsomorphicLayoutEffect(() => setMounted(true), []);
|
|
10
|
+
if (disabled) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
11
|
+
if (!mounted) return null;
|
|
12
|
+
const target = container ?? (typeof document !== "undefined" ? document.body : null);
|
|
13
|
+
if (!target) return null;
|
|
14
|
+
return createPortal(children, target);
|
|
15
|
+
}
|
|
16
|
+
var layers = [];
|
|
17
|
+
function getLayerStack() {
|
|
18
|
+
return layers;
|
|
19
|
+
}
|
|
20
|
+
function useDismiss(ref, open, onDismiss, options = {}) {
|
|
21
|
+
const { outsidePress = true, escapeKey = true, ignore = [] } = options;
|
|
22
|
+
const id = useId();
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!open) return;
|
|
25
|
+
layers.push(id);
|
|
26
|
+
const onKeyDown = (event) => {
|
|
27
|
+
if (!escapeKey || event.key !== "Escape") return;
|
|
28
|
+
if (layers[layers.length - 1] !== id) return;
|
|
29
|
+
event.stopPropagation();
|
|
30
|
+
onDismiss();
|
|
31
|
+
};
|
|
32
|
+
const onPointerDown = (event) => {
|
|
33
|
+
if (!outsidePress) return;
|
|
34
|
+
const target = event.target;
|
|
35
|
+
if (!target) return;
|
|
36
|
+
if (!target.isConnected) return;
|
|
37
|
+
if (ref.current?.contains(target)) return;
|
|
38
|
+
for (const other of ignore) {
|
|
39
|
+
if (other.current?.contains(target)) return;
|
|
40
|
+
}
|
|
41
|
+
onDismiss();
|
|
42
|
+
};
|
|
43
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
44
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
45
|
+
return () => {
|
|
46
|
+
const index = layers.indexOf(id);
|
|
47
|
+
if (index >= 0) layers.splice(index, 1);
|
|
48
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
49
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
50
|
+
};
|
|
51
|
+
}, [open, id, outsidePress, escapeKey, onDismiss, ref]);
|
|
52
|
+
}
|
|
53
|
+
var scrollLocks = 0;
|
|
54
|
+
var restoreStyles = null;
|
|
55
|
+
function useScrollLock(active) {
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!active || typeof document === "undefined") return;
|
|
58
|
+
scrollLocks += 1;
|
|
59
|
+
if (scrollLocks === 1) {
|
|
60
|
+
const { body } = document;
|
|
61
|
+
restoreStyles = { overflow: body.style.overflow, paddingRight: body.style.paddingRight };
|
|
62
|
+
const gutter = window.innerWidth - document.documentElement.clientWidth;
|
|
63
|
+
body.style.overflow = "hidden";
|
|
64
|
+
if (gutter > 0) {
|
|
65
|
+
const current = Number.parseFloat(window.getComputedStyle(body).paddingRight) || 0;
|
|
66
|
+
body.style.paddingRight = `${current + gutter}px`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return () => {
|
|
70
|
+
scrollLocks -= 1;
|
|
71
|
+
if (scrollLocks === 0 && restoreStyles) {
|
|
72
|
+
document.body.style.overflow = restoreStyles.overflow;
|
|
73
|
+
document.body.style.paddingRight = restoreStyles.paddingRight;
|
|
74
|
+
restoreStyles = null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}, [active]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/primitives/position.ts
|
|
81
|
+
function parse(placement) {
|
|
82
|
+
const [side, align = "center"] = placement.split("-");
|
|
83
|
+
return { side, align };
|
|
84
|
+
}
|
|
85
|
+
var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
86
|
+
function space(anchor, viewport, padding) {
|
|
87
|
+
return {
|
|
88
|
+
top: anchor.y - viewport.y - padding,
|
|
89
|
+
bottom: viewport.y + viewport.height - (anchor.y + anchor.height) - padding,
|
|
90
|
+
left: anchor.x - viewport.x - padding,
|
|
91
|
+
right: viewport.x + viewport.width - (anchor.x + anchor.width) - padding
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function place(anchor, floating, side, align, offset) {
|
|
95
|
+
let x = 0;
|
|
96
|
+
let y = 0;
|
|
97
|
+
switch (side) {
|
|
98
|
+
case "top":
|
|
99
|
+
y = anchor.y - floating.height - offset;
|
|
100
|
+
break;
|
|
101
|
+
case "bottom":
|
|
102
|
+
y = anchor.y + anchor.height + offset;
|
|
103
|
+
break;
|
|
104
|
+
case "left":
|
|
105
|
+
x = anchor.x - floating.width - offset;
|
|
106
|
+
break;
|
|
107
|
+
case "right":
|
|
108
|
+
x = anchor.x + anchor.width + offset;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
const vertical = side === "top" || side === "bottom";
|
|
112
|
+
if (vertical) {
|
|
113
|
+
if (align === "start") x = anchor.x;
|
|
114
|
+
else if (align === "end") x = anchor.x + anchor.width - floating.width;
|
|
115
|
+
else x = anchor.x + anchor.width / 2 - floating.width / 2;
|
|
116
|
+
} else {
|
|
117
|
+
if (align === "start") y = anchor.y;
|
|
118
|
+
else if (align === "end") y = anchor.y + anchor.height - floating.height;
|
|
119
|
+
else y = anchor.y + anchor.height / 2 - floating.height / 2;
|
|
120
|
+
}
|
|
121
|
+
return { x, y };
|
|
122
|
+
}
|
|
123
|
+
function computePosition(anchor, floating, viewport, options = {}) {
|
|
124
|
+
const {
|
|
125
|
+
placement = "bottom",
|
|
126
|
+
offset = 8,
|
|
127
|
+
padding = 8,
|
|
128
|
+
flip = true,
|
|
129
|
+
shift = true,
|
|
130
|
+
arrowSize = 0
|
|
131
|
+
} = options;
|
|
132
|
+
const requested = parse(placement);
|
|
133
|
+
let side = requested.side;
|
|
134
|
+
const align = requested.align;
|
|
135
|
+
if (flip) {
|
|
136
|
+
const available = space(anchor, viewport, padding);
|
|
137
|
+
const needed = side === "top" || side === "bottom" ? floating.height : floating.width;
|
|
138
|
+
const opposite = OPPOSITE[side];
|
|
139
|
+
if (available[side] < needed + offset && available[opposite] > available[side]) {
|
|
140
|
+
side = opposite;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let { x, y } = place(anchor, floating, side, align, offset);
|
|
144
|
+
const minX = viewport.x + padding;
|
|
145
|
+
const maxX = viewport.x + viewport.width - floating.width - padding;
|
|
146
|
+
const minY = viewport.y + padding;
|
|
147
|
+
const maxY = viewport.y + viewport.height - floating.height - padding;
|
|
148
|
+
if (shift) {
|
|
149
|
+
if (side === "top" || side === "bottom") {
|
|
150
|
+
x = maxX < minX ? minX : Math.min(Math.max(x, minX), maxX);
|
|
151
|
+
} else {
|
|
152
|
+
y = maxY < minY ? minY : Math.min(Math.max(y, minY), maxY);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
let arrow = null;
|
|
156
|
+
if (arrowSize > 0) {
|
|
157
|
+
const half = arrowSize / 2;
|
|
158
|
+
const inset = arrowSize + 4;
|
|
159
|
+
if (side === "top" || side === "bottom") {
|
|
160
|
+
const centre = anchor.x + anchor.width / 2 - x - half;
|
|
161
|
+
const limit = Math.max(inset, floating.width - inset - arrowSize);
|
|
162
|
+
arrow = {
|
|
163
|
+
x: Math.min(Math.max(centre, inset), limit),
|
|
164
|
+
y: side === "top" ? floating.height : -arrowSize
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
const centre = anchor.y + anchor.height / 2 - y - half;
|
|
168
|
+
const limit = Math.max(inset, floating.height - inset - arrowSize);
|
|
169
|
+
arrow = {
|
|
170
|
+
x: side === "left" ? floating.width : -arrowSize,
|
|
171
|
+
y: Math.min(Math.max(centre, inset), limit)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const hidden = anchor.y + anchor.height < viewport.y || anchor.y > viewport.y + viewport.height || anchor.x + anchor.width < viewport.x || anchor.x > viewport.x + viewport.width;
|
|
176
|
+
return {
|
|
177
|
+
// Rounded to whole pixels. Sub-pixel translation makes text render through a different
|
|
178
|
+
// rasterisation path, which reads as a subtle blur on non-retina displays.
|
|
179
|
+
x: Math.round(x),
|
|
180
|
+
y: Math.round(y),
|
|
181
|
+
placement: align === "center" ? side : `${side}-${align}`,
|
|
182
|
+
side,
|
|
183
|
+
align,
|
|
184
|
+
arrow,
|
|
185
|
+
hidden
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
var HIDDEN = {
|
|
189
|
+
position: "fixed",
|
|
190
|
+
top: 0,
|
|
191
|
+
left: 0,
|
|
192
|
+
// Rendered off-screen rather than `display: none` for the first frame, so the element has real
|
|
193
|
+
// dimensions to measure. Measuring a `display: none` element returns a zero rect, which would
|
|
194
|
+
// place the surface at the anchor's corner for one frame — a visible jump on every open.
|
|
195
|
+
transform: "translate3d(0, -200vh, 0)",
|
|
196
|
+
pointerEvents: "none"
|
|
197
|
+
};
|
|
198
|
+
function useAnchoredPosition(anchorRef, floatingRef, open, options = {}) {
|
|
199
|
+
const {
|
|
200
|
+
matchWidth = false,
|
|
201
|
+
enabled = true,
|
|
202
|
+
placement = "bottom",
|
|
203
|
+
offset = 8,
|
|
204
|
+
padding = 8,
|
|
205
|
+
flip = true,
|
|
206
|
+
shift = true,
|
|
207
|
+
arrowSize = 0
|
|
208
|
+
} = options;
|
|
209
|
+
const [result, setResult] = useState(null);
|
|
210
|
+
const [anchorWidth, setAnchorWidth] = useState(null);
|
|
211
|
+
const update = useCallback(() => {
|
|
212
|
+
const anchor = anchorRef.current;
|
|
213
|
+
const floating = floatingRef.current;
|
|
214
|
+
if (!anchor || !floating) return;
|
|
215
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
216
|
+
const floatingRect = floating.getBoundingClientRect();
|
|
217
|
+
if (floatingRect.width === 0 && floatingRect.height === 0) return;
|
|
218
|
+
setAnchorWidth(matchWidth ? anchorRect.width : null);
|
|
219
|
+
setResult(
|
|
220
|
+
computePosition(
|
|
221
|
+
anchorRect,
|
|
222
|
+
floatingRect,
|
|
223
|
+
{ x: 0, y: 0, width: window.innerWidth, height: window.innerHeight },
|
|
224
|
+
{ placement, offset, padding, flip, shift, arrowSize }
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
}, [anchorRef, floatingRef, matchWidth, placement, offset, padding, flip, shift, arrowSize]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!open || !enabled) {
|
|
230
|
+
setResult(null);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
update();
|
|
234
|
+
const onScroll = () => update();
|
|
235
|
+
window.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
236
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
237
|
+
let observer;
|
|
238
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
239
|
+
observer = new ResizeObserver(() => update());
|
|
240
|
+
if (anchorRef.current) observer.observe(anchorRef.current);
|
|
241
|
+
if (floatingRef.current) observer.observe(floatingRef.current);
|
|
242
|
+
}
|
|
243
|
+
return () => {
|
|
244
|
+
window.removeEventListener("scroll", onScroll, { capture: true });
|
|
245
|
+
window.removeEventListener("resize", onScroll);
|
|
246
|
+
observer?.disconnect();
|
|
247
|
+
};
|
|
248
|
+
}, [open, enabled, update, anchorRef, floatingRef]);
|
|
249
|
+
if (!result) {
|
|
250
|
+
return {
|
|
251
|
+
style: matchWidth && anchorWidth !== null ? { ...HIDDEN, width: anchorWidth } : HIDDEN,
|
|
252
|
+
arrowStyle: void 0,
|
|
253
|
+
placement,
|
|
254
|
+
side: placement.split("-")[0],
|
|
255
|
+
update
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const style = {
|
|
259
|
+
position: "fixed",
|
|
260
|
+
top: 0,
|
|
261
|
+
left: 0,
|
|
262
|
+
// `transform` rather than `top`/`left`: it is composited, so repositioning during scroll does
|
|
263
|
+
// not invalidate layout for the rest of the page.
|
|
264
|
+
transform: `translate3d(${result.x}px, ${result.y}px, 0)`,
|
|
265
|
+
...result.hidden ? { visibility: "hidden", pointerEvents: "none" } : null,
|
|
266
|
+
...anchorWidth !== null ? { width: anchorWidth } : null
|
|
267
|
+
};
|
|
268
|
+
return {
|
|
269
|
+
style,
|
|
270
|
+
arrowStyle: result.arrow ? { position: "absolute", left: result.arrow.x, top: result.arrow.y } : void 0,
|
|
271
|
+
placement: result.placement,
|
|
272
|
+
side: result.side,
|
|
273
|
+
update
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export { Portal, computePosition, getLayerStack, useAnchoredPosition, useDismiss, useScrollLock };
|
|
278
|
+
//# sourceMappingURL=chunk-XEP6P7E6.js.map
|
|
279
|
+
//# sourceMappingURL=chunk-XEP6P7E6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/primitives/portal.tsx","../src/primitives/use-dismiss.ts","../src/primitives/position.ts","../src/primitives/use-anchored-position.ts"],"names":["useEffect","useState"],"mappings":"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","file":"chunk-XEP6P7E6.js","sourcesContent":["import { type ReactNode, useEffect, useLayoutEffect, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\n/**\n * `useLayoutEffect` in the browser, `useEffect` on the server.\n *\n * React warns that `useLayoutEffect` does nothing on the server — correctly, since there is no\n * layout to read. Swapping to `useEffect` there keeps the console clean without changing client\n * behaviour, where the distinction genuinely matters (see below).\n */\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect\n\nexport interface PortalProps {\n children: ReactNode\n /** Where to mount. Defaults to `document.body`. */\n container?: Element | null\n /** Skip the portal and render in place. Useful inside an already-portalled surface. */\n disabled?: boolean\n}\n\n/**\n * Render children elsewhere in the DOM.\n *\n * Overlays must escape their parent's stacking and clipping context. A popover inside a container\n * with `overflow: hidden` gets clipped; one inside a `transform`ed ancestor has its `position:\n * fixed` resolved against that ancestor instead of the viewport, so it lands in the wrong place.\n * Neither is fixable with z-index, and both are the single most common overlay bug.\n *\n * SSR-safe: renders nothing on the server and on the first client render, then portals after mount.\n * Portalling during SSR is impossible (no DOM), and portalling on the first client render would\n * produce markup the server didn't emit — a hydration mismatch.\n */\nexport function Portal({ children, container, disabled }: PortalProps) {\n const [mounted, setMounted] = useState(false)\n\n /**\n * A **layout** effect, not a passive one, and the difference is load-bearing.\n *\n * React flushes every layout effect during commit, then runs passive effects after paint. With\n * `useEffect` here, the portal's children do not exist yet when a parent's passive effect runs —\n * so a parent that opens an overlay and immediately focuses into it finds a null ref and focus\n * stays on `<body>`. That is not a hypothetical: it is exactly how `Select` silently stopped\n * moving focus into its listbox, which breaks `aria-activedescendant` entirely.\n *\n * Promoting this to a layout effect means the portal mounts and re-renders before any parent\n * passive effect runs, so refs into portalled content are populated by the time they are read.\n */\n useIsomorphicLayoutEffect(() => setMounted(true), [])\n\n if (disabled) return <>{children}</>\n if (!mounted) return null\n\n const target = container ?? (typeof document !== 'undefined' ? document.body : null)\n if (!target) return null\n\n return createPortal(children, target)\n}\n","import { type RefObject, useEffect, useId } from 'react'\n\n/**\n * Dismissing layered surfaces.\n *\n * The hard part isn't detecting an outside click — it's nesting. A dropdown inside a dialog, with a\n * tooltip inside that: pressing Escape must close **one** layer, the innermost. Every component\n * attaching its own `document` keydown listener closes all three at once, which is the single most\n * common overlay bug in hand-rolled implementations.\n *\n * A module-level stack fixes it. Surfaces push on open and pop on close, and only the top of the\n * stack responds to Escape.\n */\n\nconst layers: string[] = []\n\n/** Ids of the currently open dismissable layers, outermost first. Exposed for tests. */\nexport function getLayerStack(): readonly string[] {\n return layers\n}\n\nexport interface DismissOptions {\n /** Close on pointer press outside the surface. Default `true`. */\n outsidePress?: boolean\n /** Close on Escape. Only the topmost layer responds. Default `true`. */\n escapeKey?: boolean\n /**\n * Extra elements that count as \"inside\" — normally the trigger.\n *\n * Without this the trigger's own click closes the surface via the outside handler and then\n * immediately reopens it via its own onClick, so the surface appears not to close at all.\n */\n ignore?: Array<RefObject<HTMLElement | null>>\n}\n\nexport function useDismiss(\n ref: RefObject<HTMLElement | null>,\n open: boolean,\n onDismiss: () => void,\n options: DismissOptions = {},\n): void {\n const { outsidePress = true, escapeKey = true, ignore = [] } = options\n const id = useId()\n\n useEffect(() => {\n if (!open) return\n\n layers.push(id)\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (!escapeKey || event.key !== 'Escape') return\n // Innermost only.\n if (layers[layers.length - 1] !== id) return\n event.stopPropagation()\n onDismiss()\n }\n\n const onPointerDown = (event: PointerEvent | MouseEvent) => {\n if (!outsidePress) return\n const target = event.target as Node | null\n if (!target) return\n\n // A click that lands on a node already detached from the document (a menu item that removed\n // itself on click) reads as \"outside\" by containment but isn't. Ignore those.\n if (!target.isConnected) return\n\n if (ref.current?.contains(target)) return\n for (const other of ignore) {\n if (other.current?.contains(target)) return\n }\n onDismiss()\n }\n\n // `pointerdown` rather than `click`: a click fires after mouseup, so dragging a text selection\n // out of a popover and releasing outside would close it mid-selection.\n document.addEventListener('pointerdown', onPointerDown, true)\n document.addEventListener('keydown', onKeyDown, true)\n\n return () => {\n const index = layers.indexOf(id)\n if (index >= 0) layers.splice(index, 1)\n document.removeEventListener('pointerdown', onPointerDown, true)\n document.removeEventListener('keydown', onKeyDown, true)\n }\n // `ignore` is typically an inline array literal; its identity changes every render, and\n // resubscribing on every render would drop events fired in between.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, id, outsidePress, escapeKey, onDismiss, ref])\n}\n\n/**\n * Lock body scroll while a modal surface is open.\n *\n * Reference-counted, so two stacked modals don't fight over the original value — the inner one\n * closing must not restore scrolling while the outer one is still open.\n *\n * The scrollbar's width is replaced with padding. Without that, hiding the scrollbar reflows the\n * whole page a few pixels wider the instant a dialog opens, which reads as a flash.\n */\nlet scrollLocks = 0\nlet restoreStyles: { overflow: string; paddingRight: string } | null = null\n\nexport function useScrollLock(active: boolean): void {\n useEffect(() => {\n if (!active || typeof document === 'undefined') return\n\n scrollLocks += 1\n if (scrollLocks === 1) {\n const { body } = document\n restoreStyles = { overflow: body.style.overflow, paddingRight: body.style.paddingRight }\n\n const gutter = window.innerWidth - document.documentElement.clientWidth\n body.style.overflow = 'hidden'\n if (gutter > 0) {\n const current = Number.parseFloat(window.getComputedStyle(body).paddingRight) || 0\n body.style.paddingRight = `${current + gutter}px`\n }\n }\n\n return () => {\n scrollLocks -= 1\n if (scrollLocks === 0 && restoreStyles) {\n document.body.style.overflow = restoreStyles.overflow\n document.body.style.paddingRight = restoreStyles.paddingRight\n restoreStyles = null\n }\n }\n }, [active])\n}\n","/**\n * Anchored positioning.\n *\n * Popovers, tooltips, menus and comboboxes all need the same thing: place a floating box next to an\n * anchor, keep it on screen, and report where it actually ended up so the arrow can point the right\n * way. This is that calculation, as a **pure function** of rectangles.\n *\n * Keeping it pure is deliberate. Positioning bugs are notoriously hard to reproduce — they depend\n * on scroll offset, viewport size and element dimensions all at once. As a pure function the whole\n * space is testable in milliseconds without a browser, which is why the edge cases below (flipping\n * into *less* space, shifting past the anchor, both axes at once) are covered by tests rather than\n * by hoping.\n *\n * This is a focused ~200-line implementation, not a Floating UI replacement: it does offset, flip,\n * shift, size-matching and arrow placement, which is what these components need. It does not do\n * auto-placement search, virtual elements or `position: absolute` containing-block resolution —\n * everything here assumes `position: fixed` against the viewport, which is the only strategy that\n * is correct inside transformed and clipped ancestors.\n */\n\nexport type Side = 'top' | 'right' | 'bottom' | 'left'\nexport type Align = 'start' | 'center' | 'end'\nexport type Placement = Side | `${Side}-${Align}`\n\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface PositionOptions {\n /** Preferred placement. Default `'bottom'`. */\n placement?: Placement\n /** Gap between anchor and floating element, in px. Default `8`. */\n offset?: number\n /** Minimum distance to keep from the viewport edge, in px. Default `8`. */\n padding?: number\n /** Flip to the opposite side when there isn't room. Default `true`. */\n flip?: boolean\n /** Slide along the cross axis to stay on screen. Default `true`. */\n shift?: boolean\n /** Size of the arrow, in px, if one is being positioned. Default `0` (no arrow). */\n arrowSize?: number\n}\n\nexport interface PositionResult {\n /** Viewport coordinates for the floating element's top-left corner. */\n x: number\n y: number\n /** Where it actually ended up — may differ from the request if it flipped. */\n placement: Placement\n side: Side\n align: Align\n /** Arrow offset along the anchor's edge, or `null` when `arrowSize` is 0. */\n arrow: { x: number; y: number } | null\n /** True when the anchor is entirely outside the viewport — callers usually hide the surface. */\n hidden: boolean\n}\n\nfunction parse(placement: Placement): { side: Side; align: Align } {\n const [side, align = 'center'] = placement.split('-') as [Side, Align?]\n return { side, align }\n}\n\nconst OPPOSITE: Record<Side, Side> = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }\n\n/** Free space on each side of the anchor, within the padded viewport. */\nfunction space(anchor: Rect, viewport: Rect, padding: number) {\n return {\n top: anchor.y - viewport.y - padding,\n bottom: viewport.y + viewport.height - (anchor.y + anchor.height) - padding,\n left: anchor.x - viewport.x - padding,\n right: viewport.x + viewport.width - (anchor.x + anchor.width) - padding,\n }\n}\n\n/** Place `floating` next to `anchor` on `side`, aligned by `align`, without clamping. */\nfunction place(anchor: Rect, floating: Rect, side: Side, align: Align, offset: number) {\n let x = 0\n let y = 0\n\n // Main axis: the gap between the two boxes.\n switch (side) {\n case 'top':\n y = anchor.y - floating.height - offset\n break\n case 'bottom':\n y = anchor.y + anchor.height + offset\n break\n case 'left':\n x = anchor.x - floating.width - offset\n break\n case 'right':\n x = anchor.x + anchor.width + offset\n break\n }\n\n // Cross axis: alignment along the anchor's edge.\n const vertical = side === 'top' || side === 'bottom'\n if (vertical) {\n if (align === 'start') x = anchor.x\n else if (align === 'end') x = anchor.x + anchor.width - floating.width\n else x = anchor.x + anchor.width / 2 - floating.width / 2\n } else {\n if (align === 'start') y = anchor.y\n else if (align === 'end') y = anchor.y + anchor.height - floating.height\n else y = anchor.y + anchor.height / 2 - floating.height / 2\n }\n\n return { x, y }\n}\n\n/**\n * Compute where a floating element should sit.\n *\n * All rectangles are in viewport coordinates, as `getBoundingClientRect` returns them.\n */\nexport function computePosition(\n anchor: Rect,\n floating: Rect,\n viewport: Rect,\n options: PositionOptions = {},\n): PositionResult {\n const {\n placement = 'bottom',\n offset = 8,\n padding = 8,\n flip = true,\n shift = true,\n arrowSize = 0,\n } = options\n\n const requested = parse(placement)\n let side = requested.side\n const align = requested.align\n\n // --- Flip ---------------------------------------------------------------\n // Only flip if the opposite side has *more* room. Flipping unconditionally when the preferred\n // side doesn't fit is the classic bug: a tall menu near the bottom of a short viewport fits\n // nowhere, flips upward into even less space, and ends up worse than if it had stayed put.\n if (flip) {\n const available = space(anchor, viewport, padding)\n const needed = side === 'top' || side === 'bottom' ? floating.height : floating.width\n const opposite = OPPOSITE[side]\n if (available[side] < needed + offset && available[opposite] > available[side]) {\n side = opposite\n }\n }\n\n let { x, y } = place(anchor, floating, side, align, offset)\n\n // --- Shift --------------------------------------------------------------\n // Slide along the cross axis to stay within the padded viewport. Clamped so a floating element\n // wider than the viewport pins to the left edge rather than oscillating between both.\n const minX = viewport.x + padding\n const maxX = viewport.x + viewport.width - floating.width - padding\n const minY = viewport.y + padding\n const maxY = viewport.y + viewport.height - floating.height - padding\n\n if (shift) {\n if (side === 'top' || side === 'bottom') {\n x = maxX < minX ? minX : Math.min(Math.max(x, minX), maxX)\n } else {\n y = maxY < minY ? minY : Math.min(Math.max(y, minY), maxY)\n }\n }\n\n // --- Arrow --------------------------------------------------------------\n // Positioned against the *anchor's* centre, then clamped inside the floating box so it never\n // pokes out of a rounded corner after a shift. This is why `shift` runs first.\n let arrow: { x: number; y: number } | null = null\n if (arrowSize > 0) {\n const half = arrowSize / 2\n // Keep the arrow clear of the corner radius, and clamp to a valid range even when the\n // floating box is narrower than the arrow plus its inset.\n const inset = arrowSize + 4\n if (side === 'top' || side === 'bottom') {\n const centre = anchor.x + anchor.width / 2 - x - half\n const limit = Math.max(inset, floating.width - inset - arrowSize)\n arrow = {\n x: Math.min(Math.max(centre, inset), limit),\n y: side === 'top' ? floating.height : -arrowSize,\n }\n } else {\n const centre = anchor.y + anchor.height / 2 - y - half\n const limit = Math.max(inset, floating.height - inset - arrowSize)\n arrow = {\n x: side === 'left' ? floating.width : -arrowSize,\n y: Math.min(Math.max(centre, inset), limit),\n }\n }\n }\n\n // The anchor having scrolled out of its clipping container is the caller's cue to hide the\n // surface — a tooltip left floating over unrelated content is worse than no tooltip.\n const hidden =\n anchor.y + anchor.height < viewport.y ||\n anchor.y > viewport.y + viewport.height ||\n anchor.x + anchor.width < viewport.x ||\n anchor.x > viewport.x + viewport.width\n\n return {\n // Rounded to whole pixels. Sub-pixel translation makes text render through a different\n // rasterisation path, which reads as a subtle blur on non-retina displays.\n x: Math.round(x),\n y: Math.round(y),\n placement: align === 'center' ? side : (`${side}-${align}` as Placement),\n side,\n align,\n arrow,\n hidden,\n }\n}\n","import { type CSSProperties, type RefObject, useCallback, useEffect, useState } from 'react'\nimport { type PositionOptions, type PositionResult, computePosition } from './position'\n\nexport interface AnchoredPositionOptions extends PositionOptions {\n /** Set the floating element's width to the anchor's — what a combobox listbox wants. */\n matchWidth?: boolean\n /** Recompute while open. Default `true`. */\n enabled?: boolean\n}\n\nexport interface AnchoredPosition {\n style: CSSProperties\n arrowStyle: CSSProperties | undefined\n placement: PositionResult['placement']\n side: PositionResult['side']\n /** Force a recompute — call after content changes size in a way observers can't see. */\n update: () => void\n}\n\nconst HIDDEN: CSSProperties = {\n position: 'fixed',\n top: 0,\n left: 0,\n // Rendered off-screen rather than `display: none` for the first frame, so the element has real\n // dimensions to measure. Measuring a `display: none` element returns a zero rect, which would\n // place the surface at the anchor's corner for one frame — a visible jump on every open.\n transform: 'translate3d(0, -200vh, 0)',\n pointerEvents: 'none',\n}\n\n/**\n * Keep a floating element positioned against an anchor.\n *\n * Recomputes on scroll (capture phase, so nested scroll containers are caught), on viewport\n * resize, and whenever either element changes size. It deliberately does **not** poll on every\n * frame: that costs a forced layout per frame for a value that changes only on discrete events,\n * and it is the reason some popover libraries make scrolling janky.\n */\nexport function useAnchoredPosition(\n anchorRef: RefObject<HTMLElement | null>,\n floatingRef: RefObject<HTMLElement | null>,\n open: boolean,\n options: AnchoredPositionOptions = {},\n): AnchoredPosition {\n const {\n matchWidth = false,\n enabled = true,\n placement = 'bottom',\n offset = 8,\n padding = 8,\n flip = true,\n shift = true,\n arrowSize = 0,\n } = options\n\n const [result, setResult] = useState<PositionResult | null>(null)\n const [anchorWidth, setAnchorWidth] = useState<number | null>(null)\n\n const update = useCallback(() => {\n const anchor = anchorRef.current\n const floating = floatingRef.current\n if (!anchor || !floating) return\n\n const anchorRect = anchor.getBoundingClientRect()\n const floatingRect = floating.getBoundingClientRect()\n\n // jsdom reports every rect as zero. Positioning against a zero-size floating box would place\n // it at the anchor's corner, so bail and leave the off-screen style in place; tests assert on\n // `computePosition` directly, where the geometry is real.\n if (floatingRect.width === 0 && floatingRect.height === 0) return\n\n setAnchorWidth(matchWidth ? anchorRect.width : null)\n setResult(\n computePosition(\n anchorRect,\n floatingRect,\n { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight },\n { placement, offset, padding, flip, shift, arrowSize },\n ),\n )\n }, [anchorRef, floatingRef, matchWidth, placement, offset, padding, flip, shift, arrowSize])\n\n useEffect(() => {\n if (!open || !enabled) {\n setResult(null)\n return\n }\n\n update()\n\n // Capture phase catches scrolling in any ancestor container, not just the document. A popover\n // inside a scrollable panel is the common case and the one that non-capturing listeners miss.\n const onScroll = () => update()\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n window.addEventListener('resize', onScroll, { passive: true })\n\n // Content that grows — an async-loaded menu, a combobox filtering down — changes the required\n // placement. `ResizeObserver` catches that without polling.\n let observer: ResizeObserver | undefined\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(() => update())\n if (anchorRef.current) observer.observe(anchorRef.current)\n if (floatingRef.current) observer.observe(floatingRef.current)\n }\n\n return () => {\n window.removeEventListener('scroll', onScroll, { capture: true })\n window.removeEventListener('resize', onScroll)\n observer?.disconnect()\n }\n }, [open, enabled, update, anchorRef, floatingRef])\n\n if (!result) {\n return {\n style: matchWidth && anchorWidth !== null ? { ...HIDDEN, width: anchorWidth } : HIDDEN,\n arrowStyle: undefined,\n placement,\n side: placement.split('-')[0] as PositionResult['side'],\n update,\n }\n }\n\n const style: CSSProperties = {\n position: 'fixed',\n top: 0,\n left: 0,\n // `transform` rather than `top`/`left`: it is composited, so repositioning during scroll does\n // not invalidate layout for the rest of the page.\n transform: `translate3d(${result.x}px, ${result.y}px, 0)`,\n ...(result.hidden ? { visibility: 'hidden' as const, pointerEvents: 'none' as const } : null),\n ...(anchorWidth !== null ? { width: anchorWidth } : null),\n }\n\n return {\n style,\n arrowStyle: result.arrow\n ? { position: 'absolute', left: result.arrow.x, top: result.arrow.y }\n : undefined,\n placement: result.placement,\n side: result.side,\n update,\n }\n}\n"]}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactDom = require('react-dom');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/primitives/portal.tsx
|
|
8
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
9
|
+
function Portal({ children, container, disabled }) {
|
|
10
|
+
const [mounted, setMounted] = react.useState(false);
|
|
11
|
+
useIsomorphicLayoutEffect(() => setMounted(true), []);
|
|
12
|
+
if (disabled) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
13
|
+
if (!mounted) return null;
|
|
14
|
+
const target = container ?? (typeof document !== "undefined" ? document.body : null);
|
|
15
|
+
if (!target) return null;
|
|
16
|
+
return reactDom.createPortal(children, target);
|
|
17
|
+
}
|
|
18
|
+
var layers = [];
|
|
19
|
+
function getLayerStack() {
|
|
20
|
+
return layers;
|
|
21
|
+
}
|
|
22
|
+
function useDismiss(ref, open, onDismiss, options = {}) {
|
|
23
|
+
const { outsidePress = true, escapeKey = true, ignore = [] } = options;
|
|
24
|
+
const id = react.useId();
|
|
25
|
+
react.useEffect(() => {
|
|
26
|
+
if (!open) return;
|
|
27
|
+
layers.push(id);
|
|
28
|
+
const onKeyDown = (event) => {
|
|
29
|
+
if (!escapeKey || event.key !== "Escape") return;
|
|
30
|
+
if (layers[layers.length - 1] !== id) return;
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
onDismiss();
|
|
33
|
+
};
|
|
34
|
+
const onPointerDown = (event) => {
|
|
35
|
+
if (!outsidePress) return;
|
|
36
|
+
const target = event.target;
|
|
37
|
+
if (!target) return;
|
|
38
|
+
if (!target.isConnected) return;
|
|
39
|
+
if (ref.current?.contains(target)) return;
|
|
40
|
+
for (const other of ignore) {
|
|
41
|
+
if (other.current?.contains(target)) return;
|
|
42
|
+
}
|
|
43
|
+
onDismiss();
|
|
44
|
+
};
|
|
45
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
46
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
47
|
+
return () => {
|
|
48
|
+
const index = layers.indexOf(id);
|
|
49
|
+
if (index >= 0) layers.splice(index, 1);
|
|
50
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
51
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
52
|
+
};
|
|
53
|
+
}, [open, id, outsidePress, escapeKey, onDismiss, ref]);
|
|
54
|
+
}
|
|
55
|
+
var scrollLocks = 0;
|
|
56
|
+
var restoreStyles = null;
|
|
57
|
+
function useScrollLock(active) {
|
|
58
|
+
react.useEffect(() => {
|
|
59
|
+
if (!active || typeof document === "undefined") return;
|
|
60
|
+
scrollLocks += 1;
|
|
61
|
+
if (scrollLocks === 1) {
|
|
62
|
+
const { body } = document;
|
|
63
|
+
restoreStyles = { overflow: body.style.overflow, paddingRight: body.style.paddingRight };
|
|
64
|
+
const gutter = window.innerWidth - document.documentElement.clientWidth;
|
|
65
|
+
body.style.overflow = "hidden";
|
|
66
|
+
if (gutter > 0) {
|
|
67
|
+
const current = Number.parseFloat(window.getComputedStyle(body).paddingRight) || 0;
|
|
68
|
+
body.style.paddingRight = `${current + gutter}px`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return () => {
|
|
72
|
+
scrollLocks -= 1;
|
|
73
|
+
if (scrollLocks === 0 && restoreStyles) {
|
|
74
|
+
document.body.style.overflow = restoreStyles.overflow;
|
|
75
|
+
document.body.style.paddingRight = restoreStyles.paddingRight;
|
|
76
|
+
restoreStyles = null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}, [active]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/primitives/position.ts
|
|
83
|
+
function parse(placement) {
|
|
84
|
+
const [side, align = "center"] = placement.split("-");
|
|
85
|
+
return { side, align };
|
|
86
|
+
}
|
|
87
|
+
var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
88
|
+
function space(anchor, viewport, padding) {
|
|
89
|
+
return {
|
|
90
|
+
top: anchor.y - viewport.y - padding,
|
|
91
|
+
bottom: viewport.y + viewport.height - (anchor.y + anchor.height) - padding,
|
|
92
|
+
left: anchor.x - viewport.x - padding,
|
|
93
|
+
right: viewport.x + viewport.width - (anchor.x + anchor.width) - padding
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function place(anchor, floating, side, align, offset) {
|
|
97
|
+
let x = 0;
|
|
98
|
+
let y = 0;
|
|
99
|
+
switch (side) {
|
|
100
|
+
case "top":
|
|
101
|
+
y = anchor.y - floating.height - offset;
|
|
102
|
+
break;
|
|
103
|
+
case "bottom":
|
|
104
|
+
y = anchor.y + anchor.height + offset;
|
|
105
|
+
break;
|
|
106
|
+
case "left":
|
|
107
|
+
x = anchor.x - floating.width - offset;
|
|
108
|
+
break;
|
|
109
|
+
case "right":
|
|
110
|
+
x = anchor.x + anchor.width + offset;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const vertical = side === "top" || side === "bottom";
|
|
114
|
+
if (vertical) {
|
|
115
|
+
if (align === "start") x = anchor.x;
|
|
116
|
+
else if (align === "end") x = anchor.x + anchor.width - floating.width;
|
|
117
|
+
else x = anchor.x + anchor.width / 2 - floating.width / 2;
|
|
118
|
+
} else {
|
|
119
|
+
if (align === "start") y = anchor.y;
|
|
120
|
+
else if (align === "end") y = anchor.y + anchor.height - floating.height;
|
|
121
|
+
else y = anchor.y + anchor.height / 2 - floating.height / 2;
|
|
122
|
+
}
|
|
123
|
+
return { x, y };
|
|
124
|
+
}
|
|
125
|
+
function computePosition(anchor, floating, viewport, options = {}) {
|
|
126
|
+
const {
|
|
127
|
+
placement = "bottom",
|
|
128
|
+
offset = 8,
|
|
129
|
+
padding = 8,
|
|
130
|
+
flip = true,
|
|
131
|
+
shift = true,
|
|
132
|
+
arrowSize = 0
|
|
133
|
+
} = options;
|
|
134
|
+
const requested = parse(placement);
|
|
135
|
+
let side = requested.side;
|
|
136
|
+
const align = requested.align;
|
|
137
|
+
if (flip) {
|
|
138
|
+
const available = space(anchor, viewport, padding);
|
|
139
|
+
const needed = side === "top" || side === "bottom" ? floating.height : floating.width;
|
|
140
|
+
const opposite = OPPOSITE[side];
|
|
141
|
+
if (available[side] < needed + offset && available[opposite] > available[side]) {
|
|
142
|
+
side = opposite;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
let { x, y } = place(anchor, floating, side, align, offset);
|
|
146
|
+
const minX = viewport.x + padding;
|
|
147
|
+
const maxX = viewport.x + viewport.width - floating.width - padding;
|
|
148
|
+
const minY = viewport.y + padding;
|
|
149
|
+
const maxY = viewport.y + viewport.height - floating.height - padding;
|
|
150
|
+
if (shift) {
|
|
151
|
+
if (side === "top" || side === "bottom") {
|
|
152
|
+
x = maxX < minX ? minX : Math.min(Math.max(x, minX), maxX);
|
|
153
|
+
} else {
|
|
154
|
+
y = maxY < minY ? minY : Math.min(Math.max(y, minY), maxY);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let arrow = null;
|
|
158
|
+
if (arrowSize > 0) {
|
|
159
|
+
const half = arrowSize / 2;
|
|
160
|
+
const inset = arrowSize + 4;
|
|
161
|
+
if (side === "top" || side === "bottom") {
|
|
162
|
+
const centre = anchor.x + anchor.width / 2 - x - half;
|
|
163
|
+
const limit = Math.max(inset, floating.width - inset - arrowSize);
|
|
164
|
+
arrow = {
|
|
165
|
+
x: Math.min(Math.max(centre, inset), limit),
|
|
166
|
+
y: side === "top" ? floating.height : -arrowSize
|
|
167
|
+
};
|
|
168
|
+
} else {
|
|
169
|
+
const centre = anchor.y + anchor.height / 2 - y - half;
|
|
170
|
+
const limit = Math.max(inset, floating.height - inset - arrowSize);
|
|
171
|
+
arrow = {
|
|
172
|
+
x: side === "left" ? floating.width : -arrowSize,
|
|
173
|
+
y: Math.min(Math.max(centre, inset), limit)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const hidden = anchor.y + anchor.height < viewport.y || anchor.y > viewport.y + viewport.height || anchor.x + anchor.width < viewport.x || anchor.x > viewport.x + viewport.width;
|
|
178
|
+
return {
|
|
179
|
+
// Rounded to whole pixels. Sub-pixel translation makes text render through a different
|
|
180
|
+
// rasterisation path, which reads as a subtle blur on non-retina displays.
|
|
181
|
+
x: Math.round(x),
|
|
182
|
+
y: Math.round(y),
|
|
183
|
+
placement: align === "center" ? side : `${side}-${align}`,
|
|
184
|
+
side,
|
|
185
|
+
align,
|
|
186
|
+
arrow,
|
|
187
|
+
hidden
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
var HIDDEN = {
|
|
191
|
+
position: "fixed",
|
|
192
|
+
top: 0,
|
|
193
|
+
left: 0,
|
|
194
|
+
// Rendered off-screen rather than `display: none` for the first frame, so the element has real
|
|
195
|
+
// dimensions to measure. Measuring a `display: none` element returns a zero rect, which would
|
|
196
|
+
// place the surface at the anchor's corner for one frame — a visible jump on every open.
|
|
197
|
+
transform: "translate3d(0, -200vh, 0)",
|
|
198
|
+
pointerEvents: "none"
|
|
199
|
+
};
|
|
200
|
+
function useAnchoredPosition(anchorRef, floatingRef, open, options = {}) {
|
|
201
|
+
const {
|
|
202
|
+
matchWidth = false,
|
|
203
|
+
enabled = true,
|
|
204
|
+
placement = "bottom",
|
|
205
|
+
offset = 8,
|
|
206
|
+
padding = 8,
|
|
207
|
+
flip = true,
|
|
208
|
+
shift = true,
|
|
209
|
+
arrowSize = 0
|
|
210
|
+
} = options;
|
|
211
|
+
const [result, setResult] = react.useState(null);
|
|
212
|
+
const [anchorWidth, setAnchorWidth] = react.useState(null);
|
|
213
|
+
const update = react.useCallback(() => {
|
|
214
|
+
const anchor = anchorRef.current;
|
|
215
|
+
const floating = floatingRef.current;
|
|
216
|
+
if (!anchor || !floating) return;
|
|
217
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
218
|
+
const floatingRect = floating.getBoundingClientRect();
|
|
219
|
+
if (floatingRect.width === 0 && floatingRect.height === 0) return;
|
|
220
|
+
setAnchorWidth(matchWidth ? anchorRect.width : null);
|
|
221
|
+
setResult(
|
|
222
|
+
computePosition(
|
|
223
|
+
anchorRect,
|
|
224
|
+
floatingRect,
|
|
225
|
+
{ x: 0, y: 0, width: window.innerWidth, height: window.innerHeight },
|
|
226
|
+
{ placement, offset, padding, flip, shift, arrowSize }
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
}, [anchorRef, floatingRef, matchWidth, placement, offset, padding, flip, shift, arrowSize]);
|
|
230
|
+
react.useEffect(() => {
|
|
231
|
+
if (!open || !enabled) {
|
|
232
|
+
setResult(null);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
update();
|
|
236
|
+
const onScroll = () => update();
|
|
237
|
+
window.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
238
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
239
|
+
let observer;
|
|
240
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
241
|
+
observer = new ResizeObserver(() => update());
|
|
242
|
+
if (anchorRef.current) observer.observe(anchorRef.current);
|
|
243
|
+
if (floatingRef.current) observer.observe(floatingRef.current);
|
|
244
|
+
}
|
|
245
|
+
return () => {
|
|
246
|
+
window.removeEventListener("scroll", onScroll, { capture: true });
|
|
247
|
+
window.removeEventListener("resize", onScroll);
|
|
248
|
+
observer?.disconnect();
|
|
249
|
+
};
|
|
250
|
+
}, [open, enabled, update, anchorRef, floatingRef]);
|
|
251
|
+
if (!result) {
|
|
252
|
+
return {
|
|
253
|
+
style: matchWidth && anchorWidth !== null ? { ...HIDDEN, width: anchorWidth } : HIDDEN,
|
|
254
|
+
arrowStyle: void 0,
|
|
255
|
+
placement,
|
|
256
|
+
side: placement.split("-")[0],
|
|
257
|
+
update
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const style = {
|
|
261
|
+
position: "fixed",
|
|
262
|
+
top: 0,
|
|
263
|
+
left: 0,
|
|
264
|
+
// `transform` rather than `top`/`left`: it is composited, so repositioning during scroll does
|
|
265
|
+
// not invalidate layout for the rest of the page.
|
|
266
|
+
transform: `translate3d(${result.x}px, ${result.y}px, 0)`,
|
|
267
|
+
...result.hidden ? { visibility: "hidden", pointerEvents: "none" } : null,
|
|
268
|
+
...anchorWidth !== null ? { width: anchorWidth } : null
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
style,
|
|
272
|
+
arrowStyle: result.arrow ? { position: "absolute", left: result.arrow.x, top: result.arrow.y } : void 0,
|
|
273
|
+
placement: result.placement,
|
|
274
|
+
side: result.side,
|
|
275
|
+
update
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exports.Portal = Portal;
|
|
280
|
+
exports.computePosition = computePosition;
|
|
281
|
+
exports.getLayerStack = getLayerStack;
|
|
282
|
+
exports.useAnchoredPosition = useAnchoredPosition;
|
|
283
|
+
exports.useDismiss = useDismiss;
|
|
284
|
+
exports.useScrollLock = useScrollLock;
|
|
285
|
+
//# sourceMappingURL=chunk-XMXECSQO.cjs.map
|
|
286
|
+
//# sourceMappingURL=chunk-XMXECSQO.cjs.map
|