@howells/stacksheet 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,1350 @@
1
- export { createSheetStack } from "./create.js";
2
- export { resolveConfig } from "./config.js";
3
- export { getStackTransform, getSlideFrom, getPanelStyles } from "./stacking.js";
4
- export { useIsMobile, useResolvedSide } from "./media.js";
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