@howells/stacksheet 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/index.d.ts +381 -6
- package/dist/index.js +1349 -4
- package/dist/index.js.map +1 -1
- package/package.json +23 -9
- package/dist/config.d.ts +0 -3
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -37
- package/dist/config.js.map +0 -1
- package/dist/create.d.ts +0 -13
- package/dist/create.d.ts.map +0 -1
- package/dist/create.js +0 -55
- package/dist/create.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/media.d.ts +0 -9
- package/dist/media.d.ts.map +0 -1
- package/dist/media.js +0 -22
- package/dist/media.js.map +0 -1
- package/dist/renderer.d.ts +0 -10
- package/dist/renderer.d.ts.map +0 -1
- package/dist/renderer.js +0 -169
- package/dist/renderer.js.map +0 -1
- package/dist/stacking.d.ts +0 -26
- package/dist/stacking.d.ts.map +0 -1
- package/dist/stacking.js +0 -68
- package/dist/stacking.js.map +0 -1
- package/dist/store.d.ts +0 -9
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -73
- package/dist/store.js.map +0 -1
- package/dist/types.d.ts +0 -111
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,1350 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/springs.ts
|
|
2
|
+
var springs = {
|
|
3
|
+
soft: { stiffness: 120, damping: 18, mass: 1 },
|
|
4
|
+
subtle: { stiffness: 300, damping: 30, mass: 1 },
|
|
5
|
+
natural: { stiffness: 200, damping: 20, mass: 1 },
|
|
6
|
+
snappy: { stiffness: 400, damping: 28, mass: 0.8 },
|
|
7
|
+
stiff: { stiffness: 400, damping: 40, mass: 1 }
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/config.ts
|
|
11
|
+
var DEFAULT_STACKING = {
|
|
12
|
+
scaleStep: 0.04,
|
|
13
|
+
offsetStep: 36,
|
|
14
|
+
opacityStep: 0,
|
|
15
|
+
radius: 12,
|
|
16
|
+
renderThreshold: 3
|
|
17
|
+
};
|
|
18
|
+
var DEFAULT_SIDE = {
|
|
19
|
+
desktop: "right",
|
|
20
|
+
mobile: "bottom"
|
|
21
|
+
};
|
|
22
|
+
function resolveConfig(config = {}) {
|
|
23
|
+
const side = typeof config.side === "string" ? { desktop: config.side, mobile: config.side } : { ...DEFAULT_SIDE, ...config.side };
|
|
24
|
+
const spring = typeof config.spring === "string" ? springs[config.spring] : { ...springs.stiff, ...config.spring };
|
|
25
|
+
return {
|
|
26
|
+
maxDepth: config.maxDepth ?? Number.POSITIVE_INFINITY,
|
|
27
|
+
closeOnEscape: config.closeOnEscape ?? true,
|
|
28
|
+
closeOnBackdrop: config.closeOnBackdrop ?? true,
|
|
29
|
+
showOverlay: config.showOverlay ?? true,
|
|
30
|
+
lockScroll: config.lockScroll ?? true,
|
|
31
|
+
width: config.width ?? 420,
|
|
32
|
+
maxWidth: config.maxWidth ?? "90vw",
|
|
33
|
+
breakpoint: config.breakpoint ?? 768,
|
|
34
|
+
side,
|
|
35
|
+
stacking: { ...DEFAULT_STACKING, ...config.stacking },
|
|
36
|
+
spring,
|
|
37
|
+
zIndex: config.zIndex ?? 100,
|
|
38
|
+
ariaLabel: config.ariaLabel ?? "Sheet dialog",
|
|
39
|
+
onOpenComplete: config.onOpenComplete,
|
|
40
|
+
onCloseComplete: config.onCloseComplete,
|
|
41
|
+
drag: config.drag ?? true,
|
|
42
|
+
closeThreshold: config.closeThreshold ?? 0.25,
|
|
43
|
+
velocityThreshold: config.velocityThreshold ?? 0.5,
|
|
44
|
+
dismissible: config.dismissible ?? true,
|
|
45
|
+
modal: config.modal ?? true,
|
|
46
|
+
shouldScaleBackground: config.shouldScaleBackground ?? false,
|
|
47
|
+
scaleBackgroundAmount: config.scaleBackgroundAmount ?? 0.97
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/create.tsx
|
|
52
|
+
import { Portal } from "@radix-ui/react-portal";
|
|
53
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
54
|
+
import { useStore as useStore2 } from "zustand";
|
|
55
|
+
import { useShallow } from "zustand/react/shallow";
|
|
56
|
+
|
|
57
|
+
// src/renderer.tsx
|
|
58
|
+
import FocusTrap from "focus-trap-react";
|
|
59
|
+
import { AnimatePresence, motion as m } from "motion/react";
|
|
60
|
+
import {
|
|
61
|
+
useCallback as useCallback2,
|
|
62
|
+
useEffect as useEffect3,
|
|
63
|
+
useMemo,
|
|
64
|
+
useRef as useRef2,
|
|
65
|
+
useState as useState2
|
|
66
|
+
} from "react";
|
|
67
|
+
import { RemoveScroll } from "react-remove-scroll";
|
|
68
|
+
import { useStore } from "zustand";
|
|
69
|
+
|
|
70
|
+
// src/icons.tsx
|
|
71
|
+
import { jsx } from "react/jsx-runtime";
|
|
72
|
+
function ArrowLeftIcon() {
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
"svg",
|
|
75
|
+
{
|
|
76
|
+
"aria-hidden": "true",
|
|
77
|
+
fill: "none",
|
|
78
|
+
height: 16,
|
|
79
|
+
stroke: "currentColor",
|
|
80
|
+
strokeLinecap: "round",
|
|
81
|
+
strokeLinejoin: "round",
|
|
82
|
+
strokeWidth: 2,
|
|
83
|
+
viewBox: "0 0 24 24",
|
|
84
|
+
width: 16,
|
|
85
|
+
children: /* @__PURE__ */ jsx("path", { d: "M19 12H5M12 19l-7-7 7-7" })
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function XIcon() {
|
|
90
|
+
return /* @__PURE__ */ jsx(
|
|
91
|
+
"svg",
|
|
92
|
+
{
|
|
93
|
+
"aria-hidden": "true",
|
|
94
|
+
fill: "none",
|
|
95
|
+
height: 16,
|
|
96
|
+
stroke: "currentColor",
|
|
97
|
+
strokeLinecap: "round",
|
|
98
|
+
strokeLinejoin: "round",
|
|
99
|
+
strokeWidth: 2,
|
|
100
|
+
viewBox: "0 0 24 24",
|
|
101
|
+
width: 16,
|
|
102
|
+
children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/media.ts
|
|
108
|
+
import { useEffect, useState } from "react";
|
|
109
|
+
function useIsMobile(breakpoint) {
|
|
110
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
113
|
+
setIsMobile(mql.matches);
|
|
114
|
+
const handler = (e) => setIsMobile(e.matches);
|
|
115
|
+
mql.addEventListener("change", handler);
|
|
116
|
+
return () => mql.removeEventListener("change", handler);
|
|
117
|
+
}, [breakpoint]);
|
|
118
|
+
return isMobile;
|
|
119
|
+
}
|
|
120
|
+
function useResolvedSide(config) {
|
|
121
|
+
const isMobile = useIsMobile(config.breakpoint);
|
|
122
|
+
return isMobile ? config.side.mobile : config.side.desktop;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/panel-context.tsx
|
|
126
|
+
import { createContext, useContext } from "react";
|
|
127
|
+
var SheetPanelContext = createContext(
|
|
128
|
+
null
|
|
129
|
+
);
|
|
130
|
+
function useSheetPanel() {
|
|
131
|
+
const ctx = useContext(SheetPanelContext);
|
|
132
|
+
if (!ctx) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"Sheet.* components must be used inside a sheet panel. They should be rendered by a component opened via actions.open(), push(), etc."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return ctx;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/stacking.ts
|
|
141
|
+
function getStackTransform(depth, stacking) {
|
|
142
|
+
if (depth <= 0) {
|
|
143
|
+
return { scale: 1, offset: 0, opacity: 1, borderRadius: 0 };
|
|
144
|
+
}
|
|
145
|
+
const beyondThreshold = depth >= stacking.renderThreshold;
|
|
146
|
+
const visualDepth = beyondThreshold ? stacking.renderThreshold - 1 : depth;
|
|
147
|
+
return {
|
|
148
|
+
scale: Math.max(0.5, 1 - visualDepth * stacking.scaleStep),
|
|
149
|
+
offset: visualDepth * stacking.offsetStep,
|
|
150
|
+
opacity: beyondThreshold ? 0 : Math.max(0, 1 - visualDepth * stacking.opacityStep),
|
|
151
|
+
borderRadius: stacking.radius
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function getAnimatedBorderRadius(side, depth, stacking) {
|
|
155
|
+
if (side === "bottom") {
|
|
156
|
+
const radius = depth > 0 ? stacking.radius : 16;
|
|
157
|
+
return {
|
|
158
|
+
borderTopLeftRadius: radius,
|
|
159
|
+
borderTopRightRadius: radius,
|
|
160
|
+
borderBottomLeftRadius: 0,
|
|
161
|
+
borderBottomRightRadius: 0
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (depth > 0) {
|
|
165
|
+
return { borderRadius: stacking.radius };
|
|
166
|
+
}
|
|
167
|
+
return { borderRadius: 0 };
|
|
168
|
+
}
|
|
169
|
+
function getSlideFrom(side) {
|
|
170
|
+
switch (side) {
|
|
171
|
+
case "right":
|
|
172
|
+
return { x: "100%" };
|
|
173
|
+
case "left":
|
|
174
|
+
return { x: "-100%" };
|
|
175
|
+
case "bottom":
|
|
176
|
+
return { y: "100%" };
|
|
177
|
+
default:
|
|
178
|
+
return { x: "100%" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function getSlideTarget() {
|
|
182
|
+
return { x: 0, y: 0 };
|
|
183
|
+
}
|
|
184
|
+
function getPanelStyles(side, config, _depth, index) {
|
|
185
|
+
const { width, maxWidth, zIndex } = config;
|
|
186
|
+
const base = {
|
|
187
|
+
position: "fixed",
|
|
188
|
+
zIndex: zIndex + 10 + index,
|
|
189
|
+
display: "flex",
|
|
190
|
+
flexDirection: "column",
|
|
191
|
+
overflow: "hidden",
|
|
192
|
+
willChange: "transform",
|
|
193
|
+
transformOrigin: side === "bottom" ? "center bottom" : `${side} center`
|
|
194
|
+
};
|
|
195
|
+
if (side === "bottom") {
|
|
196
|
+
return {
|
|
197
|
+
...base,
|
|
198
|
+
left: 0,
|
|
199
|
+
right: 0,
|
|
200
|
+
bottom: 0,
|
|
201
|
+
maxHeight: "85vh"
|
|
202
|
+
// borderRadius is animated via Motion's animate prop for scale correction
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const sideStyles = side === "right" ? { top: 0, right: 0, bottom: 0 } : { top: 0, left: 0, bottom: 0 };
|
|
206
|
+
return {
|
|
207
|
+
...base,
|
|
208
|
+
...sideStyles,
|
|
209
|
+
width,
|
|
210
|
+
maxWidth
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/use-drag.ts
|
|
215
|
+
import { useCallback, useEffect as useEffect2, useRef } from "react";
|
|
216
|
+
var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
217
|
+
"INPUT",
|
|
218
|
+
"TEXTAREA",
|
|
219
|
+
"SELECT",
|
|
220
|
+
"BUTTON",
|
|
221
|
+
"A"
|
|
222
|
+
]);
|
|
223
|
+
function isInteractiveElement(el) {
|
|
224
|
+
if (INTERACTIVE_TAGS.has(el.tagName)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
if (el.isContentEditable) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (el.closest("button, a, input, textarea, select, [contenteditable]")) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (el.closest("[data-stacksheet-no-drag]")) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
function getDismissAxis(side) {
|
|
239
|
+
switch (side) {
|
|
240
|
+
case "right":
|
|
241
|
+
return { axis: "x", sign: 1 };
|
|
242
|
+
case "left":
|
|
243
|
+
return { axis: "x", sign: -1 };
|
|
244
|
+
case "bottom":
|
|
245
|
+
return { axis: "y", sign: 1 };
|
|
246
|
+
default:
|
|
247
|
+
return { axis: "x", sign: 1 };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
var DEAD_ZONE = 10;
|
|
251
|
+
var MAX_ANGLE_DEG = 35;
|
|
252
|
+
function classifyGesture(dx, dy, axis, sign) {
|
|
253
|
+
const absDx = Math.abs(dx);
|
|
254
|
+
const absDy = Math.abs(dy);
|
|
255
|
+
let angleDeg;
|
|
256
|
+
if (axis === "y") {
|
|
257
|
+
angleDeg = absDy === 0 ? 90 : Math.atan(absDx / absDy) * 180 / Math.PI;
|
|
258
|
+
} else {
|
|
259
|
+
angleDeg = absDx === 0 ? 90 : Math.atan(absDy / absDx) * 180 / Math.PI;
|
|
260
|
+
}
|
|
261
|
+
if (angleDeg > MAX_ANGLE_DEG) {
|
|
262
|
+
return "none";
|
|
263
|
+
}
|
|
264
|
+
const moveInAxis = axis === "x" ? dx : dy;
|
|
265
|
+
if (moveInAxis * sign < 0) {
|
|
266
|
+
return "none";
|
|
267
|
+
}
|
|
268
|
+
return "drag";
|
|
269
|
+
}
|
|
270
|
+
function getPanelDimension(panel, axis) {
|
|
271
|
+
if (!panel) {
|
|
272
|
+
return 300;
|
|
273
|
+
}
|
|
274
|
+
return axis === "x" ? panel.offsetWidth : panel.offsetHeight;
|
|
275
|
+
}
|
|
276
|
+
function useDrag(panelRef, config, onDragUpdate) {
|
|
277
|
+
const startRef = useRef(null);
|
|
278
|
+
const committedRef = useRef(null);
|
|
279
|
+
const offsetRef = useRef(0);
|
|
280
|
+
const { axis, sign } = getDismissAxis(config.side);
|
|
281
|
+
const handlePointerDown = useCallback(
|
|
282
|
+
(e) => {
|
|
283
|
+
if (!config.enabled) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (e.button !== 0) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const target = e.target;
|
|
290
|
+
if (!target) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const isHandle = !!target.closest("[data-stacksheet-handle]");
|
|
294
|
+
if (!isHandle && isInteractiveElement(target)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!isHandle) {
|
|
298
|
+
const scrollable = target.closest("[data-radix-scroll-area-viewport]");
|
|
299
|
+
if (scrollable && scrollable.scrollTop > 0 && axis === "y") {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
startRef.current = { x: e.clientX, y: e.clientY, time: Date.now() };
|
|
304
|
+
committedRef.current = null;
|
|
305
|
+
offsetRef.current = 0;
|
|
306
|
+
e.currentTarget?.setPointerCapture?.(e.pointerId);
|
|
307
|
+
},
|
|
308
|
+
[config.enabled, axis]
|
|
309
|
+
);
|
|
310
|
+
const handlePointerMove = useCallback(
|
|
311
|
+
(e) => {
|
|
312
|
+
if (!startRef.current) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const dx = e.clientX - startRef.current.x;
|
|
316
|
+
const dy = e.clientY - startRef.current.y;
|
|
317
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
318
|
+
if (committedRef.current === null && dist < DEAD_ZONE) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (committedRef.current === null) {
|
|
322
|
+
committedRef.current = classifyGesture(dx, dy, axis, sign);
|
|
323
|
+
if (committedRef.current === "none") {
|
|
324
|
+
startRef.current = null;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (committedRef.current !== "drag") {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const rawOffset = axis === "x" ? dx : dy;
|
|
332
|
+
const clampedOffset = Math.max(0, rawOffset * sign);
|
|
333
|
+
offsetRef.current = clampedOffset;
|
|
334
|
+
onDragUpdate({ offset: clampedOffset, isDragging: true });
|
|
335
|
+
e.preventDefault();
|
|
336
|
+
},
|
|
337
|
+
[axis, sign, onDragUpdate]
|
|
338
|
+
);
|
|
339
|
+
const handlePointerUp = useCallback(
|
|
340
|
+
(_e) => {
|
|
341
|
+
if (!startRef.current || committedRef.current !== "drag") {
|
|
342
|
+
startRef.current = null;
|
|
343
|
+
committedRef.current = null;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const offset = offsetRef.current;
|
|
347
|
+
const elapsed = Date.now() - startRef.current.time;
|
|
348
|
+
const velocity = elapsed > 0 ? offset / elapsed : 0;
|
|
349
|
+
startRef.current = null;
|
|
350
|
+
committedRef.current = null;
|
|
351
|
+
offsetRef.current = 0;
|
|
352
|
+
const panelSize = getPanelDimension(panelRef.current, axis);
|
|
353
|
+
const pastThreshold = offset / panelSize > config.closeThreshold;
|
|
354
|
+
const fastEnough = velocity > config.velocityThreshold;
|
|
355
|
+
if (pastThreshold || fastEnough) {
|
|
356
|
+
if (config.isNested) {
|
|
357
|
+
config.onPop();
|
|
358
|
+
} else {
|
|
359
|
+
config.onClose();
|
|
360
|
+
}
|
|
361
|
+
onDragUpdate({ offset: 0, isDragging: false });
|
|
362
|
+
} else {
|
|
363
|
+
onDragUpdate({ offset: 0, isDragging: false });
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
[
|
|
367
|
+
panelRef,
|
|
368
|
+
axis,
|
|
369
|
+
config.closeThreshold,
|
|
370
|
+
config.velocityThreshold,
|
|
371
|
+
config.isNested,
|
|
372
|
+
config.onClose,
|
|
373
|
+
config.onPop,
|
|
374
|
+
onDragUpdate,
|
|
375
|
+
config
|
|
376
|
+
]
|
|
377
|
+
);
|
|
378
|
+
const handlePointerCancel = useCallback(() => {
|
|
379
|
+
startRef.current = null;
|
|
380
|
+
committedRef.current = null;
|
|
381
|
+
offsetRef.current = 0;
|
|
382
|
+
onDragUpdate({ offset: 0, isDragging: false });
|
|
383
|
+
}, [onDragUpdate]);
|
|
384
|
+
useEffect2(() => {
|
|
385
|
+
const el = panelRef.current;
|
|
386
|
+
if (!(el && config.enabled)) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
el.addEventListener("pointerdown", handlePointerDown);
|
|
390
|
+
el.addEventListener("pointermove", handlePointerMove);
|
|
391
|
+
el.addEventListener("pointerup", handlePointerUp);
|
|
392
|
+
el.addEventListener("pointercancel", handlePointerCancel);
|
|
393
|
+
return () => {
|
|
394
|
+
el.removeEventListener("pointerdown", handlePointerDown);
|
|
395
|
+
el.removeEventListener("pointermove", handlePointerMove);
|
|
396
|
+
el.removeEventListener("pointerup", handlePointerUp);
|
|
397
|
+
el.removeEventListener("pointercancel", handlePointerCancel);
|
|
398
|
+
};
|
|
399
|
+
}, [
|
|
400
|
+
panelRef,
|
|
401
|
+
config.enabled,
|
|
402
|
+
handlePointerDown,
|
|
403
|
+
handlePointerMove,
|
|
404
|
+
handlePointerUp,
|
|
405
|
+
handlePointerCancel
|
|
406
|
+
]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/renderer.tsx
|
|
410
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
411
|
+
var BUTTON_STYLE = {
|
|
412
|
+
display: "inline-flex",
|
|
413
|
+
alignItems: "center",
|
|
414
|
+
justifyContent: "center",
|
|
415
|
+
width: 32,
|
|
416
|
+
height: 32,
|
|
417
|
+
borderRadius: "50%",
|
|
418
|
+
border: "none",
|
|
419
|
+
background: "transparent",
|
|
420
|
+
cursor: "pointer",
|
|
421
|
+
color: "inherit",
|
|
422
|
+
opacity: 0.5,
|
|
423
|
+
transition: "opacity 150ms",
|
|
424
|
+
padding: 0
|
|
425
|
+
};
|
|
426
|
+
var HANDLE_BAR_STYLE = {
|
|
427
|
+
width: 36,
|
|
428
|
+
height: 4,
|
|
429
|
+
borderRadius: 2,
|
|
430
|
+
background: "var(--muted-foreground, rgba(0, 0, 0, 0.25))"
|
|
431
|
+
};
|
|
432
|
+
function DefaultHeader({ isNested, onBack, onClose, side }) {
|
|
433
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
434
|
+
side === "bottom" && /* @__PURE__ */ jsx2(
|
|
435
|
+
"div",
|
|
436
|
+
{
|
|
437
|
+
"data-stacksheet-handle": "",
|
|
438
|
+
style: {
|
|
439
|
+
display: "flex",
|
|
440
|
+
alignItems: "center",
|
|
441
|
+
justifyContent: "center",
|
|
442
|
+
padding: "12px 0 4px",
|
|
443
|
+
flexShrink: 0,
|
|
444
|
+
cursor: "grab",
|
|
445
|
+
touchAction: "none"
|
|
446
|
+
},
|
|
447
|
+
children: /* @__PURE__ */ jsx2("div", { style: HANDLE_BAR_STYLE })
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
/* @__PURE__ */ jsxs(
|
|
451
|
+
"div",
|
|
452
|
+
{
|
|
453
|
+
style: {
|
|
454
|
+
display: "flex",
|
|
455
|
+
alignItems: "center",
|
|
456
|
+
height: 48,
|
|
457
|
+
flexShrink: 0,
|
|
458
|
+
padding: "0 12px",
|
|
459
|
+
borderBottom: "1px solid var(--border, transparent)"
|
|
460
|
+
},
|
|
461
|
+
children: [
|
|
462
|
+
isNested && /* @__PURE__ */ jsx2(
|
|
463
|
+
"button",
|
|
464
|
+
{
|
|
465
|
+
"aria-label": "Back",
|
|
466
|
+
onClick: onBack,
|
|
467
|
+
style: BUTTON_STYLE,
|
|
468
|
+
type: "button",
|
|
469
|
+
children: /* @__PURE__ */ jsx2(ArrowLeftIcon, {})
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
/* @__PURE__ */ jsx2("div", { style: { flex: 1 } }),
|
|
473
|
+
/* @__PURE__ */ jsx2(
|
|
474
|
+
"button",
|
|
475
|
+
{
|
|
476
|
+
"aria-label": "Close",
|
|
477
|
+
onClick: onClose,
|
|
478
|
+
style: BUTTON_STYLE,
|
|
479
|
+
type: "button",
|
|
480
|
+
children: /* @__PURE__ */ jsx2(XIcon, {})
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
]
|
|
484
|
+
}
|
|
485
|
+
)
|
|
486
|
+
] });
|
|
487
|
+
}
|
|
488
|
+
var EMPTY_CLASSNAMES = {
|
|
489
|
+
backdrop: "",
|
|
490
|
+
panel: "",
|
|
491
|
+
header: ""
|
|
492
|
+
};
|
|
493
|
+
function resolveClassNames(cn) {
|
|
494
|
+
if (!cn) {
|
|
495
|
+
return EMPTY_CLASSNAMES;
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
backdrop: cn.backdrop ?? "",
|
|
499
|
+
panel: cn.panel ?? "",
|
|
500
|
+
header: cn.header ?? ""
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function selectSpring(isTop, spring, stackSpring) {
|
|
504
|
+
return isTop ? spring : stackSpring;
|
|
505
|
+
}
|
|
506
|
+
function buildAriaProps(isTop, isModal, isComposable, ariaLabel, panelId) {
|
|
507
|
+
if (!isTop) {
|
|
508
|
+
return {};
|
|
509
|
+
}
|
|
510
|
+
const props = { role: "dialog" };
|
|
511
|
+
if (isModal) {
|
|
512
|
+
props["aria-modal"] = "true";
|
|
513
|
+
}
|
|
514
|
+
if (isComposable) {
|
|
515
|
+
props["aria-labelledby"] = `${panelId}-title`;
|
|
516
|
+
props["aria-describedby"] = `${panelId}-desc`;
|
|
517
|
+
} else {
|
|
518
|
+
props["aria-label"] = ariaLabel;
|
|
519
|
+
}
|
|
520
|
+
return props;
|
|
521
|
+
}
|
|
522
|
+
function getDragTransform(side, offset) {
|
|
523
|
+
if (offset === 0) {
|
|
524
|
+
return {};
|
|
525
|
+
}
|
|
526
|
+
switch (side) {
|
|
527
|
+
case "right":
|
|
528
|
+
return { x: offset };
|
|
529
|
+
case "left":
|
|
530
|
+
return { x: -offset };
|
|
531
|
+
case "bottom":
|
|
532
|
+
return { y: offset };
|
|
533
|
+
default:
|
|
534
|
+
return {};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function SheetPanel({
|
|
538
|
+
item,
|
|
539
|
+
index,
|
|
540
|
+
depth,
|
|
541
|
+
isTop,
|
|
542
|
+
isNested,
|
|
543
|
+
side,
|
|
544
|
+
config,
|
|
545
|
+
classNames,
|
|
546
|
+
Content,
|
|
547
|
+
shouldRender,
|
|
548
|
+
pop,
|
|
549
|
+
close,
|
|
550
|
+
renderHeader,
|
|
551
|
+
slideFrom,
|
|
552
|
+
slideTarget,
|
|
553
|
+
spring,
|
|
554
|
+
stackSpring,
|
|
555
|
+
exitSpring
|
|
556
|
+
}) {
|
|
557
|
+
const panelRef = useRef2(null);
|
|
558
|
+
const hasEnteredRef = useRef2(false);
|
|
559
|
+
const [dragState, setDragState] = useState2({
|
|
560
|
+
offset: 0,
|
|
561
|
+
isDragging: false
|
|
562
|
+
});
|
|
563
|
+
const transform = getStackTransform(depth, config.stacking);
|
|
564
|
+
const panelStyles = getPanelStyles(side, config, depth, index);
|
|
565
|
+
const stackOffset = getStackingOffset(side, transform.offset);
|
|
566
|
+
useEffect3(() => {
|
|
567
|
+
if (!isTop) {
|
|
568
|
+
hasEnteredRef.current = false;
|
|
569
|
+
}
|
|
570
|
+
}, [isTop]);
|
|
571
|
+
const handleAnimationComplete = useCallback2(() => {
|
|
572
|
+
if (isTop && !hasEnteredRef.current) {
|
|
573
|
+
hasEnteredRef.current = true;
|
|
574
|
+
config.onOpenComplete?.();
|
|
575
|
+
}
|
|
576
|
+
}, [isTop, config]);
|
|
577
|
+
useDrag(
|
|
578
|
+
panelRef,
|
|
579
|
+
{
|
|
580
|
+
enabled: isTop && config.drag && config.dismissible,
|
|
581
|
+
closeThreshold: config.closeThreshold,
|
|
582
|
+
velocityThreshold: config.velocityThreshold,
|
|
583
|
+
side,
|
|
584
|
+
onClose: close,
|
|
585
|
+
onPop: pop,
|
|
586
|
+
isNested
|
|
587
|
+
},
|
|
588
|
+
setDragState
|
|
589
|
+
);
|
|
590
|
+
const ariaLabel = (typeof item.data?.__ariaLabel === "string" ? item.data.__ariaLabel : void 0) ?? config.ariaLabel;
|
|
591
|
+
const panelId = `stacksheet-${item.id}`;
|
|
592
|
+
const panelContext = useMemo(
|
|
593
|
+
() => ({ close, back: pop, isNested, isTop, panelId, side }),
|
|
594
|
+
[close, pop, isNested, isTop, panelId, side]
|
|
595
|
+
);
|
|
596
|
+
const isComposable = renderHeader === false;
|
|
597
|
+
const hasPanelClass = classNames.panel !== "";
|
|
598
|
+
const dragOffset = getDragTransform(side, dragState.offset);
|
|
599
|
+
const panelStyle = {
|
|
600
|
+
...panelStyles,
|
|
601
|
+
boxShadow: isTop ? getShadow(side, false) : getShadow(side, true),
|
|
602
|
+
pointerEvents: isTop ? "auto" : "none",
|
|
603
|
+
// During drag, disable spring transition for immediate feedback
|
|
604
|
+
...dragState.isDragging ? { transition: "none" } : {},
|
|
605
|
+
...hasPanelClass ? {} : {
|
|
606
|
+
background: "var(--background, #fff)",
|
|
607
|
+
borderColor: "var(--border, transparent)"
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
const headerProps = {
|
|
611
|
+
isNested,
|
|
612
|
+
onBack: pop,
|
|
613
|
+
onClose: close,
|
|
614
|
+
side
|
|
615
|
+
};
|
|
616
|
+
const isModal = config.modal;
|
|
617
|
+
const ariaProps = buildAriaProps(
|
|
618
|
+
isTop,
|
|
619
|
+
isModal,
|
|
620
|
+
isComposable,
|
|
621
|
+
ariaLabel,
|
|
622
|
+
panelId
|
|
623
|
+
);
|
|
624
|
+
const transition = dragState.isDragging ? { type: "tween", duration: 0 } : selectSpring(isTop, spring, stackSpring);
|
|
625
|
+
const animatedRadius = getAnimatedBorderRadius(side, depth, config.stacking);
|
|
626
|
+
const animateTarget = {
|
|
627
|
+
...slideTarget,
|
|
628
|
+
...stackOffset,
|
|
629
|
+
...dragOffset,
|
|
630
|
+
scale: transform.scale,
|
|
631
|
+
opacity: transform.opacity,
|
|
632
|
+
...animatedRadius,
|
|
633
|
+
transition
|
|
634
|
+
};
|
|
635
|
+
const panelContent = /* @__PURE__ */ jsx2(
|
|
636
|
+
m.div,
|
|
637
|
+
{
|
|
638
|
+
animate: animateTarget,
|
|
639
|
+
className: classNames.panel || void 0,
|
|
640
|
+
exit: {
|
|
641
|
+
...slideFrom,
|
|
642
|
+
opacity: 0.6,
|
|
643
|
+
transition: exitSpring
|
|
644
|
+
},
|
|
645
|
+
initial: {
|
|
646
|
+
...slideFrom,
|
|
647
|
+
opacity: 0.8
|
|
648
|
+
},
|
|
649
|
+
onAnimationComplete: handleAnimationComplete,
|
|
650
|
+
ref: panelRef,
|
|
651
|
+
style: panelStyle,
|
|
652
|
+
tabIndex: isTop ? -1 : void 0,
|
|
653
|
+
transition: spring,
|
|
654
|
+
...ariaProps,
|
|
655
|
+
children: isComposable ? (
|
|
656
|
+
/* Composable mode: content fills panel directly, uses Sheet.* parts */
|
|
657
|
+
shouldRender && Content && /* @__PURE__ */ jsx2(Content, { ...item.data })
|
|
658
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
659
|
+
renderHeader ? renderHeader(headerProps) : /* @__PURE__ */ jsx2(DefaultHeader, { ...headerProps }),
|
|
660
|
+
shouldRender && Content && /* @__PURE__ */ jsx2(
|
|
661
|
+
"div",
|
|
662
|
+
{
|
|
663
|
+
"data-stacksheet-no-drag": "",
|
|
664
|
+
style: {
|
|
665
|
+
flex: 1,
|
|
666
|
+
minHeight: 0,
|
|
667
|
+
overflowY: "auto",
|
|
668
|
+
overscrollBehavior: "contain"
|
|
669
|
+
},
|
|
670
|
+
children: /* @__PURE__ */ jsx2(Content, { ...item.data })
|
|
671
|
+
}
|
|
672
|
+
)
|
|
673
|
+
] })
|
|
674
|
+
},
|
|
675
|
+
item.id
|
|
676
|
+
);
|
|
677
|
+
if (!isModal) {
|
|
678
|
+
return /* @__PURE__ */ jsx2(SheetPanelContext.Provider, { value: panelContext, children: panelContent });
|
|
679
|
+
}
|
|
680
|
+
return /* @__PURE__ */ jsx2(SheetPanelContext.Provider, { value: panelContext, children: /* @__PURE__ */ jsx2(
|
|
681
|
+
FocusTrap,
|
|
682
|
+
{
|
|
683
|
+
active: isTop,
|
|
684
|
+
focusTrapOptions: {
|
|
685
|
+
initialFocus: false,
|
|
686
|
+
returnFocusOnDeactivate: true,
|
|
687
|
+
escapeDeactivates: false,
|
|
688
|
+
allowOutsideClick: true,
|
|
689
|
+
checkCanFocusTrap: () => new Promise(
|
|
690
|
+
(resolve) => requestAnimationFrame(() => resolve())
|
|
691
|
+
),
|
|
692
|
+
fallbackFocus: () => {
|
|
693
|
+
if (panelRef.current) {
|
|
694
|
+
return panelRef.current;
|
|
695
|
+
}
|
|
696
|
+
return document.body;
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
children: panelContent
|
|
700
|
+
}
|
|
701
|
+
) });
|
|
702
|
+
}
|
|
703
|
+
function useBodyScale(config, isOpen) {
|
|
704
|
+
useEffect3(() => {
|
|
705
|
+
if (!config.shouldScaleBackground) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const wrapper = document.querySelector("[data-stacksheet-wrapper]");
|
|
709
|
+
if (!(wrapper && wrapper instanceof HTMLElement)) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (isOpen) {
|
|
713
|
+
const scale = config.scaleBackgroundAmount;
|
|
714
|
+
wrapper.style.transition = "transform 500ms cubic-bezier(0.32, 0.72, 0, 1), border-radius 500ms cubic-bezier(0.32, 0.72, 0, 1)";
|
|
715
|
+
wrapper.style.transform = `scale(${scale})`;
|
|
716
|
+
wrapper.style.borderRadius = "8px";
|
|
717
|
+
wrapper.style.overflow = "hidden";
|
|
718
|
+
wrapper.style.transformOrigin = "center top";
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
wrapper.style.transform = "";
|
|
722
|
+
wrapper.style.borderRadius = "";
|
|
723
|
+
const handleEnd = () => {
|
|
724
|
+
wrapper.style.transition = "";
|
|
725
|
+
wrapper.style.overflow = "";
|
|
726
|
+
wrapper.style.transformOrigin = "";
|
|
727
|
+
};
|
|
728
|
+
wrapper.addEventListener("transitionend", handleEnd, { once: true });
|
|
729
|
+
return () => wrapper.removeEventListener("transitionend", handleEnd);
|
|
730
|
+
}, [isOpen, config.shouldScaleBackground, config.scaleBackgroundAmount]);
|
|
731
|
+
}
|
|
732
|
+
function SheetRenderer({
|
|
733
|
+
store,
|
|
734
|
+
config,
|
|
735
|
+
sheets,
|
|
736
|
+
componentMap,
|
|
737
|
+
classNames: classNamesProp,
|
|
738
|
+
renderHeader
|
|
739
|
+
}) {
|
|
740
|
+
const isOpen = useStore(store, (s) => s.isOpen);
|
|
741
|
+
const stack = useStore(store, (s) => s.stack);
|
|
742
|
+
const close = useStore(store, (s) => s.close);
|
|
743
|
+
const pop = useStore(store, (s) => s.pop);
|
|
744
|
+
const side = useResolvedSide(config);
|
|
745
|
+
const classNames = resolveClassNames(classNamesProp);
|
|
746
|
+
useBodyScale(config, isOpen);
|
|
747
|
+
const triggerRef = useRef2(null);
|
|
748
|
+
const wasOpenRef = useRef2(false);
|
|
749
|
+
useEffect3(() => {
|
|
750
|
+
if (isOpen && !wasOpenRef.current) {
|
|
751
|
+
triggerRef.current = document.activeElement;
|
|
752
|
+
} else if (!isOpen && wasOpenRef.current) {
|
|
753
|
+
const el = triggerRef.current;
|
|
754
|
+
if (el && el instanceof HTMLElement) {
|
|
755
|
+
el.focus();
|
|
756
|
+
}
|
|
757
|
+
triggerRef.current = null;
|
|
758
|
+
}
|
|
759
|
+
wasOpenRef.current = isOpen;
|
|
760
|
+
}, [isOpen]);
|
|
761
|
+
useEffect3(() => {
|
|
762
|
+
if (!(isOpen && config.closeOnEscape && config.dismissible)) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
function handleKeyDown(e) {
|
|
766
|
+
if (e.key === "Escape") {
|
|
767
|
+
e.preventDefault();
|
|
768
|
+
if (stack.length > 1) {
|
|
769
|
+
pop();
|
|
770
|
+
} else {
|
|
771
|
+
close();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
776
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
777
|
+
}, [
|
|
778
|
+
isOpen,
|
|
779
|
+
config.closeOnEscape,
|
|
780
|
+
config.dismissible,
|
|
781
|
+
stack.length,
|
|
782
|
+
pop,
|
|
783
|
+
close
|
|
784
|
+
]);
|
|
785
|
+
const slideFrom = getSlideFrom(side);
|
|
786
|
+
const slideTarget = getSlideTarget();
|
|
787
|
+
const spring = {
|
|
788
|
+
type: "spring",
|
|
789
|
+
damping: config.spring.damping,
|
|
790
|
+
stiffness: config.spring.stiffness,
|
|
791
|
+
mass: config.spring.mass
|
|
792
|
+
};
|
|
793
|
+
const stackSpring = spring;
|
|
794
|
+
const exitSpring = spring;
|
|
795
|
+
const isModal = config.modal;
|
|
796
|
+
const showOverlay = isModal && config.showOverlay;
|
|
797
|
+
const hasBackdropClass = classNames.backdrop !== "";
|
|
798
|
+
const backdropStyle = {
|
|
799
|
+
position: "fixed",
|
|
800
|
+
inset: 0,
|
|
801
|
+
zIndex: config.zIndex,
|
|
802
|
+
cursor: config.closeOnBackdrop && config.dismissible ? "pointer" : void 0,
|
|
803
|
+
...hasBackdropClass ? {} : { background: "var(--overlay, rgba(0, 0, 0, 0.2))" }
|
|
804
|
+
};
|
|
805
|
+
const handleExitComplete = useCallback2(() => {
|
|
806
|
+
if (stack.length === 0) {
|
|
807
|
+
config.onCloseComplete?.();
|
|
808
|
+
}
|
|
809
|
+
}, [stack.length, config]);
|
|
810
|
+
const shouldLockScroll = isOpen && isModal && config.lockScroll;
|
|
811
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
812
|
+
showOverlay && /* @__PURE__ */ jsx2(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx2(
|
|
813
|
+
m.div,
|
|
814
|
+
{
|
|
815
|
+
animate: { opacity: 1 },
|
|
816
|
+
className: classNames.backdrop || void 0,
|
|
817
|
+
exit: { opacity: 0 },
|
|
818
|
+
initial: { opacity: 0 },
|
|
819
|
+
onClick: config.closeOnBackdrop && config.dismissible ? close : void 0,
|
|
820
|
+
style: backdropStyle,
|
|
821
|
+
transition: spring
|
|
822
|
+
},
|
|
823
|
+
"stacksheet-backdrop"
|
|
824
|
+
) }),
|
|
825
|
+
/* @__PURE__ */ jsx2(RemoveScroll, { enabled: shouldLockScroll, forwardProps: true, children: /* @__PURE__ */ jsx2(
|
|
826
|
+
"div",
|
|
827
|
+
{
|
|
828
|
+
style: {
|
|
829
|
+
position: "fixed",
|
|
830
|
+
inset: 0,
|
|
831
|
+
zIndex: config.zIndex + 1,
|
|
832
|
+
overflow: "hidden",
|
|
833
|
+
pointerEvents: "none"
|
|
834
|
+
},
|
|
835
|
+
children: /* @__PURE__ */ jsx2(AnimatePresence, { onExitComplete: handleExitComplete, children: stack.map((item, index) => {
|
|
836
|
+
const depth = stack.length - 1 - index;
|
|
837
|
+
const isTop = depth === 0;
|
|
838
|
+
const isNested = stack.length > 1;
|
|
839
|
+
const shouldRender = depth < config.stacking.renderThreshold;
|
|
840
|
+
const Content = componentMap.get(item.type) ?? sheets[item.type];
|
|
841
|
+
return /* @__PURE__ */ jsx2(
|
|
842
|
+
SheetPanel,
|
|
843
|
+
{
|
|
844
|
+
Content,
|
|
845
|
+
classNames,
|
|
846
|
+
close,
|
|
847
|
+
config,
|
|
848
|
+
depth,
|
|
849
|
+
exitSpring,
|
|
850
|
+
index,
|
|
851
|
+
isNested,
|
|
852
|
+
isTop,
|
|
853
|
+
item,
|
|
854
|
+
pop,
|
|
855
|
+
renderHeader,
|
|
856
|
+
shouldRender,
|
|
857
|
+
side,
|
|
858
|
+
slideFrom,
|
|
859
|
+
slideTarget,
|
|
860
|
+
spring,
|
|
861
|
+
stackSpring
|
|
862
|
+
},
|
|
863
|
+
item.id
|
|
864
|
+
);
|
|
865
|
+
}) })
|
|
866
|
+
}
|
|
867
|
+
) })
|
|
868
|
+
] });
|
|
869
|
+
}
|
|
870
|
+
function getStackingOffset(side, offset) {
|
|
871
|
+
if (offset === 0) {
|
|
872
|
+
return {};
|
|
873
|
+
}
|
|
874
|
+
switch (side) {
|
|
875
|
+
case "right":
|
|
876
|
+
return { x: -offset };
|
|
877
|
+
case "left":
|
|
878
|
+
return { x: offset };
|
|
879
|
+
case "bottom":
|
|
880
|
+
return { y: -offset };
|
|
881
|
+
default:
|
|
882
|
+
return {};
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function getShadow(side, isNested) {
|
|
886
|
+
if (side === "bottom") {
|
|
887
|
+
return isNested ? "0 -2px 16px rgba(0,0,0,0.06)" : "0 -8px 32px rgba(0,0,0,0.15)";
|
|
888
|
+
}
|
|
889
|
+
const dir = side === "right" ? -1 : 1;
|
|
890
|
+
return isNested ? `${dir * 2}px 0 16px rgba(0,0,0,0.06)` : `${dir * 8}px 0 32px rgba(0,0,0,0.15)`;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/store.ts
|
|
894
|
+
import { createStore } from "zustand";
|
|
895
|
+
function warnInlineComponent(component, componentRegistry, warnedNames) {
|
|
896
|
+
if (typeof process === "undefined" || process?.env?.NODE_ENV === "production") {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
const name = component.displayName || component.name;
|
|
900
|
+
if (!name) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
if (warnedNames.has(name)) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
for (const [existing, key] of componentRegistry) {
|
|
907
|
+
const existingName = existing.displayName || existing.name;
|
|
908
|
+
if (existingName === name) {
|
|
909
|
+
warnedNames.add(name);
|
|
910
|
+
console.warn(
|
|
911
|
+
`[stacksheet] A new component reference with name "${name}" was registered (key: ${key}), but a different reference with the same name already exists. This usually means you're passing an inline arrow function (e.g. open(() => <MySheet />)). Define the component outside of render to avoid memory leaks and broken navigate() same-type detection.`
|
|
912
|
+
);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function resolveArgs(componentRegistry, componentMap, getNextKey, warnedNames, first, second, third) {
|
|
918
|
+
if (typeof first === "function") {
|
|
919
|
+
const component = first;
|
|
920
|
+
let typeKey = componentRegistry.get(component);
|
|
921
|
+
if (!typeKey) {
|
|
922
|
+
warnInlineComponent(component, componentRegistry, warnedNames);
|
|
923
|
+
typeKey = getNextKey();
|
|
924
|
+
componentRegistry.set(component, typeKey);
|
|
925
|
+
componentMap.set(typeKey, component);
|
|
926
|
+
}
|
|
927
|
+
if (typeof second === "string") {
|
|
928
|
+
return {
|
|
929
|
+
type: typeKey,
|
|
930
|
+
id: second,
|
|
931
|
+
data: third ?? {}
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
type: typeKey,
|
|
936
|
+
id: crypto.randomUUID(),
|
|
937
|
+
data: second ?? {}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
type: first,
|
|
942
|
+
id: second,
|
|
943
|
+
data: third ?? {}
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function createSheetStore(config) {
|
|
947
|
+
const componentRegistry = /* @__PURE__ */ new Map();
|
|
948
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
949
|
+
let adhocCounter = 0;
|
|
950
|
+
function getNextKey() {
|
|
951
|
+
return `__adhoc_${adhocCounter++}`;
|
|
952
|
+
}
|
|
953
|
+
const warnedNames = /* @__PURE__ */ new Set();
|
|
954
|
+
function resolve(first, second, third) {
|
|
955
|
+
return resolveArgs(
|
|
956
|
+
componentRegistry,
|
|
957
|
+
componentMap,
|
|
958
|
+
getNextKey,
|
|
959
|
+
warnedNames,
|
|
960
|
+
first,
|
|
961
|
+
second,
|
|
962
|
+
third
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
const store = createStore()((set, get) => {
|
|
966
|
+
function _openResolved({ type, id, data }) {
|
|
967
|
+
set({
|
|
968
|
+
stack: [{ id, type, data }],
|
|
969
|
+
isOpen: true
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
function _pushResolved({ type, id, data }) {
|
|
973
|
+
set((state) => {
|
|
974
|
+
const item = { id, type, data };
|
|
975
|
+
if (Number.isFinite(config.maxDepth) && state.stack.length >= config.maxDepth) {
|
|
976
|
+
return {
|
|
977
|
+
stack: [...state.stack.slice(0, -1), item],
|
|
978
|
+
isOpen: true
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
stack: [...state.stack, item],
|
|
983
|
+
isOpen: true
|
|
984
|
+
};
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function _replaceResolved({ type, id, data }) {
|
|
988
|
+
set((state) => {
|
|
989
|
+
const item = { id, type, data };
|
|
990
|
+
if (state.stack.length === 0) {
|
|
991
|
+
return { stack: [item], isOpen: true };
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
stack: [...state.stack.slice(0, -1), item],
|
|
995
|
+
isOpen: true
|
|
996
|
+
};
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
stack: [],
|
|
1001
|
+
isOpen: false,
|
|
1002
|
+
open(first, second, third) {
|
|
1003
|
+
_openResolved(resolve(first, second, third));
|
|
1004
|
+
},
|
|
1005
|
+
push(first, second, third) {
|
|
1006
|
+
_pushResolved(resolve(first, second, third));
|
|
1007
|
+
},
|
|
1008
|
+
replace(first, second, third) {
|
|
1009
|
+
_replaceResolved(resolve(first, second, third));
|
|
1010
|
+
},
|
|
1011
|
+
swap(first, second) {
|
|
1012
|
+
let type;
|
|
1013
|
+
let data;
|
|
1014
|
+
if (typeof first === "function") {
|
|
1015
|
+
const component = first;
|
|
1016
|
+
let typeKey = componentRegistry.get(component);
|
|
1017
|
+
if (!typeKey) {
|
|
1018
|
+
warnInlineComponent(component, componentRegistry, warnedNames);
|
|
1019
|
+
typeKey = getNextKey();
|
|
1020
|
+
componentRegistry.set(component, typeKey);
|
|
1021
|
+
componentMap.set(typeKey, component);
|
|
1022
|
+
}
|
|
1023
|
+
type = typeKey;
|
|
1024
|
+
data = second ?? {};
|
|
1025
|
+
} else {
|
|
1026
|
+
type = first;
|
|
1027
|
+
data = second ?? {};
|
|
1028
|
+
}
|
|
1029
|
+
set((state) => {
|
|
1030
|
+
const top = state.stack.at(-1);
|
|
1031
|
+
if (!top) {
|
|
1032
|
+
return state;
|
|
1033
|
+
}
|
|
1034
|
+
const newStack = [...state.stack];
|
|
1035
|
+
newStack[newStack.length - 1] = { id: top.id, type, data };
|
|
1036
|
+
return { stack: newStack };
|
|
1037
|
+
});
|
|
1038
|
+
},
|
|
1039
|
+
navigate(first, second, third) {
|
|
1040
|
+
const resolved = resolve(first, second, third);
|
|
1041
|
+
const { stack } = get();
|
|
1042
|
+
const top = stack.at(-1);
|
|
1043
|
+
if (stack.length === 0) {
|
|
1044
|
+
_openResolved(resolved);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
let isSameType = top?.type === resolved.type;
|
|
1048
|
+
if (!isSameType && typeof first === "function") {
|
|
1049
|
+
const topComponent = componentMap.get(top?.type ?? "");
|
|
1050
|
+
isSameType = topComponent === first;
|
|
1051
|
+
}
|
|
1052
|
+
if (isSameType) {
|
|
1053
|
+
_replaceResolved(resolved);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
_pushResolved(resolved);
|
|
1057
|
+
},
|
|
1058
|
+
setData(first, second, third) {
|
|
1059
|
+
const { id, data } = resolve(first, second, third);
|
|
1060
|
+
set((state) => {
|
|
1061
|
+
const idx = state.stack.findIndex((item) => item.id === id);
|
|
1062
|
+
if (idx === -1) {
|
|
1063
|
+
return state;
|
|
1064
|
+
}
|
|
1065
|
+
const updated = [...state.stack];
|
|
1066
|
+
updated[idx] = { ...updated[idx], data };
|
|
1067
|
+
return { stack: updated };
|
|
1068
|
+
});
|
|
1069
|
+
},
|
|
1070
|
+
remove(id) {
|
|
1071
|
+
set((state) => {
|
|
1072
|
+
const next = state.stack.filter((item) => item.id !== id);
|
|
1073
|
+
if (next.length === state.stack.length) {
|
|
1074
|
+
return state;
|
|
1075
|
+
}
|
|
1076
|
+
if (next.length === 0) {
|
|
1077
|
+
return { stack: [], isOpen: false };
|
|
1078
|
+
}
|
|
1079
|
+
return { stack: next };
|
|
1080
|
+
});
|
|
1081
|
+
},
|
|
1082
|
+
pop() {
|
|
1083
|
+
set((state) => {
|
|
1084
|
+
if (state.stack.length <= 1) {
|
|
1085
|
+
return { stack: [], isOpen: false };
|
|
1086
|
+
}
|
|
1087
|
+
return { stack: state.stack.slice(0, -1), isOpen: true };
|
|
1088
|
+
});
|
|
1089
|
+
},
|
|
1090
|
+
close() {
|
|
1091
|
+
set({ stack: [], isOpen: false });
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
});
|
|
1095
|
+
return { store, componentRegistry, componentMap };
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/create.tsx
|
|
1099
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1100
|
+
function createStacksheet(config) {
|
|
1101
|
+
const resolved = resolveConfig(config);
|
|
1102
|
+
const { store, componentMap } = createSheetStore(resolved);
|
|
1103
|
+
const StoreContext = createContext2(null);
|
|
1104
|
+
function useStoreContext() {
|
|
1105
|
+
const ctx = useContext2(StoreContext);
|
|
1106
|
+
if (!ctx) {
|
|
1107
|
+
throw new Error(
|
|
1108
|
+
"useSheet/useStacksheetState must be used within <StacksheetProvider>"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return ctx;
|
|
1112
|
+
}
|
|
1113
|
+
const EMPTY_SHEETS = {};
|
|
1114
|
+
function StacksheetProvider({
|
|
1115
|
+
sheets = EMPTY_SHEETS,
|
|
1116
|
+
children,
|
|
1117
|
+
classNames,
|
|
1118
|
+
renderHeader
|
|
1119
|
+
}) {
|
|
1120
|
+
const value = useMemo2(() => ({ store, config: resolved }), []);
|
|
1121
|
+
return /* @__PURE__ */ jsxs2(StoreContext.Provider, { value, children: [
|
|
1122
|
+
children,
|
|
1123
|
+
/* @__PURE__ */ jsx3(Portal, { asChild: false, children: /* @__PURE__ */ jsx3(
|
|
1124
|
+
SheetRenderer,
|
|
1125
|
+
{
|
|
1126
|
+
classNames,
|
|
1127
|
+
componentMap,
|
|
1128
|
+
config: resolved,
|
|
1129
|
+
renderHeader,
|
|
1130
|
+
sheets,
|
|
1131
|
+
store
|
|
1132
|
+
}
|
|
1133
|
+
) })
|
|
1134
|
+
] });
|
|
1135
|
+
}
|
|
1136
|
+
function useSheet() {
|
|
1137
|
+
const { store: s } = useStoreContext();
|
|
1138
|
+
return useMemo2(() => {
|
|
1139
|
+
const state = s.getState();
|
|
1140
|
+
return {
|
|
1141
|
+
open: state.open,
|
|
1142
|
+
push: state.push,
|
|
1143
|
+
replace: state.replace,
|
|
1144
|
+
swap: state.swap,
|
|
1145
|
+
navigate: state.navigate,
|
|
1146
|
+
setData: state.setData,
|
|
1147
|
+
remove: state.remove,
|
|
1148
|
+
pop: state.pop,
|
|
1149
|
+
close: state.close
|
|
1150
|
+
};
|
|
1151
|
+
}, [s]);
|
|
1152
|
+
}
|
|
1153
|
+
function useStacksheetState() {
|
|
1154
|
+
const { store: s } = useStoreContext();
|
|
1155
|
+
return useStore2(
|
|
1156
|
+
s,
|
|
1157
|
+
useShallow((state) => ({
|
|
1158
|
+
stack: state.stack,
|
|
1159
|
+
isOpen: state.isOpen
|
|
1160
|
+
}))
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return { StacksheetProvider, useSheet, useStacksheetState, store };
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/parts.tsx
|
|
1167
|
+
import {
|
|
1168
|
+
Root as ScrollAreaRoot,
|
|
1169
|
+
Scrollbar as ScrollAreaScrollbar,
|
|
1170
|
+
Thumb as ScrollAreaThumb,
|
|
1171
|
+
Viewport as ScrollAreaViewport
|
|
1172
|
+
} from "@radix-ui/react-scroll-area";
|
|
1173
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
1174
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1175
|
+
var HANDLE_STYLE = {
|
|
1176
|
+
display: "flex",
|
|
1177
|
+
alignItems: "center",
|
|
1178
|
+
justifyContent: "center",
|
|
1179
|
+
padding: "12px 0 4px",
|
|
1180
|
+
flexShrink: 0,
|
|
1181
|
+
cursor: "grab",
|
|
1182
|
+
touchAction: "none"
|
|
1183
|
+
};
|
|
1184
|
+
var HANDLE_BAR_STYLE2 = {
|
|
1185
|
+
width: 36,
|
|
1186
|
+
height: 4,
|
|
1187
|
+
borderRadius: 2,
|
|
1188
|
+
background: "var(--muted-foreground, rgba(0, 0, 0, 0.25))"
|
|
1189
|
+
};
|
|
1190
|
+
function SheetHandle({
|
|
1191
|
+
asChild,
|
|
1192
|
+
className,
|
|
1193
|
+
style,
|
|
1194
|
+
children
|
|
1195
|
+
}) {
|
|
1196
|
+
const Comp = asChild ? Slot : "div";
|
|
1197
|
+
return /* @__PURE__ */ jsx4(
|
|
1198
|
+
Comp,
|
|
1199
|
+
{
|
|
1200
|
+
className,
|
|
1201
|
+
"data-stacksheet-handle": "",
|
|
1202
|
+
style: { ...HANDLE_STYLE, ...style },
|
|
1203
|
+
children: children ?? /* @__PURE__ */ jsx4("div", { style: HANDLE_BAR_STYLE2 })
|
|
1204
|
+
}
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
var HEADER_STYLE = {
|
|
1208
|
+
flexShrink: 0
|
|
1209
|
+
};
|
|
1210
|
+
function SheetHeader({
|
|
1211
|
+
asChild,
|
|
1212
|
+
className,
|
|
1213
|
+
style,
|
|
1214
|
+
children
|
|
1215
|
+
}) {
|
|
1216
|
+
const Comp = asChild ? Slot : "header";
|
|
1217
|
+
return /* @__PURE__ */ jsx4(Comp, { className, style: { ...HEADER_STYLE, ...style }, children });
|
|
1218
|
+
}
|
|
1219
|
+
function SheetTitle({ asChild, className, style, children }) {
|
|
1220
|
+
const { panelId } = useSheetPanel();
|
|
1221
|
+
const Comp = asChild ? Slot : "h2";
|
|
1222
|
+
return /* @__PURE__ */ jsx4(Comp, { className, id: `${panelId}-title`, style, children });
|
|
1223
|
+
}
|
|
1224
|
+
function SheetDescription({
|
|
1225
|
+
asChild,
|
|
1226
|
+
className,
|
|
1227
|
+
style,
|
|
1228
|
+
children
|
|
1229
|
+
}) {
|
|
1230
|
+
const { panelId } = useSheetPanel();
|
|
1231
|
+
const Comp = asChild ? Slot : "p";
|
|
1232
|
+
return /* @__PURE__ */ jsx4(Comp, { className, id: `${panelId}-desc`, style, children });
|
|
1233
|
+
}
|
|
1234
|
+
var BODY_STYLE = {
|
|
1235
|
+
flex: 1,
|
|
1236
|
+
minHeight: 0,
|
|
1237
|
+
position: "relative"
|
|
1238
|
+
};
|
|
1239
|
+
var SCROLLBAR_STYLE = {
|
|
1240
|
+
display: "flex",
|
|
1241
|
+
userSelect: "none",
|
|
1242
|
+
touchAction: "none",
|
|
1243
|
+
padding: 2,
|
|
1244
|
+
width: 8
|
|
1245
|
+
};
|
|
1246
|
+
var THUMB_STYLE = {
|
|
1247
|
+
flex: 1,
|
|
1248
|
+
borderRadius: 4,
|
|
1249
|
+
background: "var(--border, rgba(0, 0, 0, 0.15))",
|
|
1250
|
+
position: "relative"
|
|
1251
|
+
};
|
|
1252
|
+
function SheetBody({ asChild, className, style, children }) {
|
|
1253
|
+
if (asChild) {
|
|
1254
|
+
return /* @__PURE__ */ jsx4(
|
|
1255
|
+
Slot,
|
|
1256
|
+
{
|
|
1257
|
+
className,
|
|
1258
|
+
"data-stacksheet-no-drag": "",
|
|
1259
|
+
style: { ...BODY_STYLE, ...style },
|
|
1260
|
+
children
|
|
1261
|
+
}
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
return /* @__PURE__ */ jsxs3(
|
|
1265
|
+
ScrollAreaRoot,
|
|
1266
|
+
{
|
|
1267
|
+
className,
|
|
1268
|
+
"data-stacksheet-no-drag": "",
|
|
1269
|
+
style: { ...BODY_STYLE, overflow: "hidden", ...style },
|
|
1270
|
+
children: [
|
|
1271
|
+
/* @__PURE__ */ jsx4(
|
|
1272
|
+
ScrollAreaViewport,
|
|
1273
|
+
{
|
|
1274
|
+
style: { height: "100%", width: "100%", overscrollBehavior: "contain" },
|
|
1275
|
+
children
|
|
1276
|
+
}
|
|
1277
|
+
),
|
|
1278
|
+
/* @__PURE__ */ jsx4(ScrollAreaScrollbar, { orientation: "vertical", style: SCROLLBAR_STYLE, children: /* @__PURE__ */ jsx4(ScrollAreaThumb, { style: THUMB_STYLE }) })
|
|
1279
|
+
]
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
var FOOTER_STYLE = {
|
|
1284
|
+
flexShrink: 0
|
|
1285
|
+
};
|
|
1286
|
+
function SheetFooter({
|
|
1287
|
+
asChild,
|
|
1288
|
+
className,
|
|
1289
|
+
style,
|
|
1290
|
+
children
|
|
1291
|
+
}) {
|
|
1292
|
+
const Comp = asChild ? Slot : "footer";
|
|
1293
|
+
return /* @__PURE__ */ jsx4(Comp, { className, style: { ...FOOTER_STYLE, ...style }, children });
|
|
1294
|
+
}
|
|
1295
|
+
function SheetClose({ asChild, className, style, children }) {
|
|
1296
|
+
const { close } = useSheetPanel();
|
|
1297
|
+
const Comp = asChild ? Slot : "button";
|
|
1298
|
+
return /* @__PURE__ */ jsx4(
|
|
1299
|
+
Comp,
|
|
1300
|
+
{
|
|
1301
|
+
"aria-label": children ? void 0 : "Close",
|
|
1302
|
+
className,
|
|
1303
|
+
onClick: close,
|
|
1304
|
+
style,
|
|
1305
|
+
type: asChild ? void 0 : "button",
|
|
1306
|
+
children: children ?? /* @__PURE__ */ jsx4(XIcon, {})
|
|
1307
|
+
}
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
function SheetBack({ asChild, className, style, children }) {
|
|
1311
|
+
const { back, isNested } = useSheetPanel();
|
|
1312
|
+
if (!isNested) {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
const Comp = asChild ? Slot : "button";
|
|
1316
|
+
return /* @__PURE__ */ jsx4(
|
|
1317
|
+
Comp,
|
|
1318
|
+
{
|
|
1319
|
+
"aria-label": children ? void 0 : "Back",
|
|
1320
|
+
className,
|
|
1321
|
+
onClick: back,
|
|
1322
|
+
style,
|
|
1323
|
+
type: asChild ? void 0 : "button",
|
|
1324
|
+
children: children ?? /* @__PURE__ */ jsx4(ArrowLeftIcon, {})
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
var Sheet = {
|
|
1329
|
+
Handle: SheetHandle,
|
|
1330
|
+
Header: SheetHeader,
|
|
1331
|
+
Title: SheetTitle,
|
|
1332
|
+
Description: SheetDescription,
|
|
1333
|
+
Body: SheetBody,
|
|
1334
|
+
Footer: SheetFooter,
|
|
1335
|
+
Close: SheetClose,
|
|
1336
|
+
Back: SheetBack
|
|
1337
|
+
};
|
|
1338
|
+
export {
|
|
1339
|
+
Sheet,
|
|
1340
|
+
createStacksheet,
|
|
1341
|
+
getPanelStyles,
|
|
1342
|
+
getSlideFrom,
|
|
1343
|
+
getStackTransform,
|
|
1344
|
+
resolveConfig,
|
|
1345
|
+
springs,
|
|
1346
|
+
useIsMobile,
|
|
1347
|
+
useResolvedSide,
|
|
1348
|
+
useSheetPanel
|
|
1349
|
+
};
|
|
5
1350
|
//# sourceMappingURL=index.js.map
|