@patternmode/stacksheet 1.3.5 → 1.3.7

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.
Files changed (123) hide show
  1. package/README.md +2 -8
  2. package/dist/Drag/{DragConstants.d.ts → drag-constants.d.ts} +1 -1
  3. package/dist/Drag/drag-constants.d.ts.map +1 -0
  4. package/dist/Drag/drag-geometry.d.ts +22 -0
  5. package/dist/Drag/drag-geometry.d.ts.map +1 -0
  6. package/dist/Drag/{DragTypes.d.ts → drag-types.d.ts} +1 -1
  7. package/dist/Drag/drag-types.d.ts.map +1 -0
  8. package/dist/Drag/{useDrag.d.ts → use-drag.d.ts} +4 -4
  9. package/dist/Drag/use-drag.d.ts.map +1 -0
  10. package/dist/SheetPanel/default-header.d.ts +5 -0
  11. package/dist/SheetPanel/default-header.d.ts.map +1 -0
  12. package/dist/SheetPanel/sheet-panel-content.d.ts +15 -0
  13. package/dist/SheetPanel/sheet-panel-content.d.ts.map +1 -0
  14. package/dist/SheetPanel/sheet-panel-focus.d.ts +8 -0
  15. package/dist/SheetPanel/sheet-panel-focus.d.ts.map +1 -0
  16. package/dist/SheetPanel/sheet-panel-handles.d.ts +10 -0
  17. package/dist/SheetPanel/sheet-panel-handles.d.ts.map +1 -0
  18. package/dist/SheetPanel/sheet-panel-layout.d.ts +4 -0
  19. package/dist/SheetPanel/sheet-panel-layout.d.ts.map +1 -0
  20. package/dist/SheetPanel/{SheetPanelTypes.d.ts → sheet-panel-types.d.ts} +2 -2
  21. package/dist/SheetPanel/sheet-panel-types.d.ts.map +1 -0
  22. package/dist/SheetPanel/sheet-panel.d.ts +3 -0
  23. package/dist/SheetPanel/sheet-panel.d.ts.map +1 -0
  24. package/dist/SheetParts/sheet-back.d.ts +3 -0
  25. package/dist/SheetParts/sheet-back.d.ts.map +1 -0
  26. package/dist/SheetParts/sheet-body.d.ts +3 -0
  27. package/dist/SheetParts/sheet-body.d.ts.map +1 -0
  28. package/dist/SheetParts/sheet-close.d.ts +3 -0
  29. package/dist/SheetParts/sheet-close.d.ts.map +1 -0
  30. package/dist/SheetParts/sheet-description.d.ts +3 -0
  31. package/dist/SheetParts/sheet-description.d.ts.map +1 -0
  32. package/dist/SheetParts/sheet-footer.d.ts +3 -0
  33. package/dist/SheetParts/sheet-footer.d.ts.map +1 -0
  34. package/dist/SheetParts/sheet-handle.d.ts +3 -0
  35. package/dist/SheetParts/sheet-handle.d.ts.map +1 -0
  36. package/dist/SheetParts/sheet-header.d.ts +3 -0
  37. package/dist/SheetParts/sheet-header.d.ts.map +1 -0
  38. package/dist/SheetParts/{SheetPartTypes.d.ts → sheet-part-types.d.ts} +1 -1
  39. package/dist/SheetParts/sheet-part-types.d.ts.map +1 -0
  40. package/dist/SheetParts/sheet-parts.d.ts +20 -0
  41. package/dist/SheetParts/sheet-parts.d.ts.map +1 -0
  42. package/dist/SheetParts/sheet-title.d.ts +3 -0
  43. package/dist/SheetParts/sheet-title.d.ts.map +1 -0
  44. package/dist/Store/{createSheetStore.d.ts → create-sheet-store.d.ts} +3 -3
  45. package/dist/Store/create-sheet-store.d.ts.map +1 -0
  46. package/dist/Store/store-args.d.ts +26 -0
  47. package/dist/Store/store-args.d.ts.map +1 -0
  48. package/dist/Store/{StoreTypes.d.ts → store-types.d.ts} +3 -4
  49. package/dist/Store/store-types.d.ts.map +1 -0
  50. package/dist/config.d.ts +1 -1
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/create.d.ts +1 -1
  53. package/dist/create.d.ts.map +1 -1
  54. package/dist/icons.d.ts +2 -2
  55. package/dist/icons.d.ts.map +1 -1
  56. package/dist/index.mjs +1066 -893
  57. package/dist/index.mjs.map +1 -1
  58. package/dist/media.d.ts +2 -2
  59. package/dist/media.d.ts.map +1 -1
  60. package/dist/panel-context.d.ts +1 -1
  61. package/dist/panel-context.d.ts.map +1 -1
  62. package/dist/parts.d.ts +10 -10
  63. package/dist/parts.d.ts.map +1 -1
  64. package/dist/renderer-effects.d.ts +4 -4
  65. package/dist/renderer-effects.d.ts.map +1 -1
  66. package/dist/renderer-helpers.d.ts +19 -19
  67. package/dist/renderer-helpers.d.ts.map +1 -1
  68. package/dist/renderer.d.ts +8 -7
  69. package/dist/renderer.d.ts.map +1 -1
  70. package/dist/sheet-panel.d.ts +2 -2
  71. package/dist/sheet-panel.d.ts.map +1 -1
  72. package/dist/snap-points.d.ts +3 -3
  73. package/dist/snap-points.d.ts.map +1 -1
  74. package/dist/springs.d.ts +11 -17
  75. package/dist/springs.d.ts.map +1 -1
  76. package/dist/stacking.d.ts +6 -6
  77. package/dist/stacking.d.ts.map +1 -1
  78. package/dist/store.d.ts +2 -2
  79. package/dist/store.d.ts.map +1 -1
  80. package/dist/types.d.ts +31 -35
  81. package/dist/types.d.ts.map +1 -1
  82. package/package.json +13 -12
  83. package/dist/Drag/DragConstants.d.ts.map +0 -1
  84. package/dist/Drag/DragGeometry.d.ts +0 -22
  85. package/dist/Drag/DragGeometry.d.ts.map +0 -1
  86. package/dist/Drag/DragTypes.d.ts.map +0 -1
  87. package/dist/Drag/useDrag.d.ts.map +0 -1
  88. package/dist/SheetPanel/DefaultHeader.d.ts +0 -5
  89. package/dist/SheetPanel/DefaultHeader.d.ts.map +0 -1
  90. package/dist/SheetPanel/SheetPanel.d.ts +0 -3
  91. package/dist/SheetPanel/SheetPanel.d.ts.map +0 -1
  92. package/dist/SheetPanel/SheetPanelContent.d.ts +0 -12
  93. package/dist/SheetPanel/SheetPanelContent.d.ts.map +0 -1
  94. package/dist/SheetPanel/SheetPanelFocus.d.ts +0 -8
  95. package/dist/SheetPanel/SheetPanelFocus.d.ts.map +0 -1
  96. package/dist/SheetPanel/SheetPanelHandles.d.ts +0 -10
  97. package/dist/SheetPanel/SheetPanelHandles.d.ts.map +0 -1
  98. package/dist/SheetPanel/SheetPanelLayout.d.ts +0 -4
  99. package/dist/SheetPanel/SheetPanelLayout.d.ts.map +0 -1
  100. package/dist/SheetPanel/SheetPanelTypes.d.ts.map +0 -1
  101. package/dist/SheetParts/SheetBack.d.ts +0 -3
  102. package/dist/SheetParts/SheetBack.d.ts.map +0 -1
  103. package/dist/SheetParts/SheetBody.d.ts +0 -3
  104. package/dist/SheetParts/SheetBody.d.ts.map +0 -1
  105. package/dist/SheetParts/SheetClose.d.ts +0 -3
  106. package/dist/SheetParts/SheetClose.d.ts.map +0 -1
  107. package/dist/SheetParts/SheetDescription.d.ts +0 -3
  108. package/dist/SheetParts/SheetDescription.d.ts.map +0 -1
  109. package/dist/SheetParts/SheetFooter.d.ts +0 -3
  110. package/dist/SheetParts/SheetFooter.d.ts.map +0 -1
  111. package/dist/SheetParts/SheetHandle.d.ts +0 -3
  112. package/dist/SheetParts/SheetHandle.d.ts.map +0 -1
  113. package/dist/SheetParts/SheetHeader.d.ts +0 -3
  114. package/dist/SheetParts/SheetHeader.d.ts.map +0 -1
  115. package/dist/SheetParts/SheetPartTypes.d.ts.map +0 -1
  116. package/dist/SheetParts/SheetParts.d.ts +0 -28
  117. package/dist/SheetParts/SheetParts.d.ts.map +0 -1
  118. package/dist/SheetParts/SheetTitle.d.ts +0 -3
  119. package/dist/SheetParts/SheetTitle.d.ts.map +0 -1
  120. package/dist/Store/StoreArgs.d.ts +0 -15
  121. package/dist/Store/StoreArgs.d.ts.map +0 -1
  122. package/dist/Store/StoreTypes.d.ts.map +0 -1
  123. package/dist/Store/createSheetStore.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { Portal } from "@radix-ui/react-portal";
3
- import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
+ import { createContext, use, useEffect, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { createStore, useStore } from "zustand";
5
5
  import { useShallow } from "zustand/react/shallow";
6
+ import { springs as springs$1 } from "@howells/motion";
6
7
  import { AnimatePresence, LazyMotion, domMax, m, useReducedMotion } from "motion/react";
7
8
  import { RemoveScroll } from "react-remove-scroll";
8
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -11,45 +12,65 @@ import { joinClassNames } from "@patternmode/system";
11
12
  import { Slot } from "@radix-ui/react-slot";
12
13
  import { Root, Scrollbar, Thumb, Viewport } from "@radix-ui/react-scroll-area";
13
14
  //#region src/springs.ts
15
+ /** Strips the shared token's `type: "spring"` down to Stacksheet's numeric SpringConfig. */
16
+ const toSpringConfig = (spring) => ({
17
+ damping: spring.damping,
18
+ mass: spring.mass,
19
+ stiffness: spring.stiffness
20
+ });
14
21
  /**
15
22
  * Spring presets inspired by iOS animation feel.
16
23
  *
17
- * - `subtle` — Barely noticeable bounce, professional.
18
- * - `snappy` — Quick, responsive for interactions.
19
- * - `stiff` — Very quick, controlled. Panels, drawers. **(default)**
24
+ * - `subtle` — Barely noticeable bounce, professional. (shared @howells/motion token)
25
+ * - `snappy` — Quick, responsive for interactions. (shared @howells/motion token)
26
+ * - `stiff` — Very quick, controlled. Panels, drawers. **(default)** Intentional
27
+ * fork: damping 40 (tighter than the shared `stiff`) for drawer control.
20
28
  */
21
29
  const springs = {
22
- subtle: {
23
- stiffness: 300,
24
- damping: 30,
25
- mass: 1
26
- },
27
- snappy: {
28
- stiffness: 400,
29
- damping: 28,
30
- mass: .8
31
- },
30
+ snappy: toSpringConfig(springs$1.snappy),
32
31
  stiff: {
33
- stiffness: 400,
34
32
  damping: 40,
35
- mass: 1
36
- }
33
+ mass: 1,
34
+ stiffness: 400
35
+ },
36
+ subtle: toSpringConfig(springs$1.subtle)
37
37
  };
38
38
  //#endregion
39
39
  //#region src/config.ts
40
40
  const DEFAULT_STACKING = {
41
- scaleStep: .04,
42
41
  offsetStep: 16,
43
42
  opacityStep: 0,
44
43
  radius: 12,
45
- renderThreshold: 3
44
+ renderThreshold: 3,
45
+ scaleStep: .04
46
46
  };
47
47
  const DEFAULT_SIDE = {
48
48
  desktop: "right",
49
49
  mobile: "bottom"
50
50
  };
51
+ const DEFAULT_CONFIG = {
52
+ ariaLabel: "Sheet dialog",
53
+ breakpoint: 768,
54
+ closeOnBackdrop: true,
55
+ closeOnEscape: true,
56
+ closeThreshold: .25,
57
+ dismissible: true,
58
+ drag: true,
59
+ lockScroll: true,
60
+ maxDepth: Number.POSITIVE_INFINITY,
61
+ maxWidth: "90vw",
62
+ modal: true,
63
+ scaleBackgroundAmount: .97,
64
+ shouldScaleBackground: false,
65
+ showOverlay: true,
66
+ snapPoints: [],
67
+ snapToSequentialPoints: false,
68
+ velocityThreshold: .5,
69
+ width: 420,
70
+ zIndex: 100
71
+ };
51
72
  /** Normalize a string side to a responsive object, merging with defaults. */
52
- function resolveSide(side) {
73
+ const resolveSide = (side) => {
53
74
  if (typeof side === "string") return {
54
75
  desktop: side,
55
76
  mobile: side
@@ -58,163 +79,168 @@ function resolveSide(side) {
58
79
  ...DEFAULT_SIDE,
59
80
  ...side
60
81
  };
61
- }
82
+ };
62
83
  /** Resolve a preset name to a SpringConfig, or merge partial config with defaults. */
63
- function resolveSpring(spring) {
84
+ const resolveSpring = (spring) => {
64
85
  if (typeof spring === "string") return springs[spring];
65
86
  return {
66
87
  ...springs.stiff,
67
88
  ...spring
68
89
  };
69
- }
90
+ };
70
91
  /** Merge user-provided config with defaults. Resolves union types (side, spring) to concrete values. */
71
- function resolveConfig(config = {}) {
92
+ const resolveConfig = (config = {}) => {
93
+ const { side, spring, stacking, ...scalarConfig } = config;
94
+ const definedConfig = Object.fromEntries(Object.entries(scalarConfig).filter(([, value]) => value !== void 0));
72
95
  return {
73
- maxDepth: config.maxDepth ?? Number.POSITIVE_INFINITY,
74
- closeOnEscape: config.closeOnEscape ?? true,
75
- closeOnBackdrop: config.closeOnBackdrop ?? true,
76
- showOverlay: config.showOverlay ?? true,
77
- lockScroll: config.lockScroll ?? true,
78
- width: config.width ?? 420,
79
- maxWidth: config.maxWidth ?? "90vw",
80
- breakpoint: config.breakpoint ?? 768,
81
- side: resolveSide(config.side),
96
+ ...DEFAULT_CONFIG,
97
+ ...definedConfig,
98
+ side: resolveSide(side),
99
+ spring: resolveSpring(spring),
82
100
  stacking: {
83
101
  ...DEFAULT_STACKING,
84
- ...config.stacking
85
- },
86
- spring: resolveSpring(config.spring),
87
- zIndex: config.zIndex ?? 100,
88
- ariaLabel: config.ariaLabel ?? "Sheet dialog",
89
- onOpenComplete: config.onOpenComplete,
90
- onCloseComplete: config.onCloseComplete,
91
- snapPoints: config.snapPoints ?? [],
92
- snapPointIndex: config.snapPointIndex,
93
- onSnapPointChange: config.onSnapPointChange,
94
- snapToSequentialPoints: config.snapToSequentialPoints ?? false,
95
- drag: config.drag ?? true,
96
- closeThreshold: config.closeThreshold ?? .25,
97
- velocityThreshold: config.velocityThreshold ?? .5,
98
- dismissible: config.dismissible ?? true,
99
- modal: config.modal ?? true,
100
- shouldScaleBackground: config.shouldScaleBackground ?? false,
101
- scaleBackgroundAmount: config.scaleBackgroundAmount ?? .97
102
+ ...stacking
103
+ }
102
104
  };
103
- }
105
+ };
104
106
  //#endregion
105
107
  //#region src/media.ts
106
108
  /**
107
109
  * Returns true when viewport width is at or below the breakpoint.
108
110
  * SSR-safe: defaults to false (desktop).
109
111
  */
110
- function useIsMobile(breakpoint) {
112
+ const useIsMobile = (breakpoint) => {
111
113
  const [isMobile, setIsMobile] = useState(false);
112
114
  useEffect(() => {
113
115
  const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
114
116
  setIsMobile(mql.matches);
115
- const handler = (e) => setIsMobile(e.matches);
117
+ const handler = (e) => {
118
+ setIsMobile(e.matches);
119
+ };
116
120
  mql.addEventListener("change", handler);
117
- return () => mql.removeEventListener("change", handler);
121
+ return () => {
122
+ mql.removeEventListener("change", handler);
123
+ };
118
124
  }, [breakpoint]);
119
125
  return isMobile;
120
- }
126
+ };
121
127
  /** Resolve the current side from config + viewport. */
122
- function useResolvedSide(config) {
128
+ const useResolvedSide = (config) => {
123
129
  return useIsMobile(config.breakpoint) ? config.side.mobile : config.side.desktop;
124
- }
130
+ };
125
131
  //#endregion
126
132
  //#region src/renderer-effects.ts
127
- function usePanelHeight(panelRef, hasSnapPoints) {
133
+ const usePanelHeight = (panelRef, hasSnapPoints) => {
128
134
  const [height, setHeight] = useState(0);
129
135
  useEffect(() => {
130
136
  const el = panelRef.current;
131
- if (!(el && hasSnapPoints)) return;
132
- setHeight(el.offsetHeight);
133
- const observer = new ResizeObserver(([entry]) => {
134
- if (entry) setHeight(entry.contentRect.height);
135
- });
136
- observer.observe(el);
137
- return () => observer.disconnect();
137
+ let observer;
138
+ if (el !== null && hasSnapPoints) {
139
+ setHeight(el.offsetHeight);
140
+ observer = new ResizeObserver(([entry]) => {
141
+ if (entry) setHeight(entry.contentRect.height);
142
+ });
143
+ observer.observe(el);
144
+ }
145
+ return () => {
146
+ observer?.disconnect();
147
+ };
138
148
  }, [panelRef, hasSnapPoints]);
139
149
  return height;
140
- }
141
- function useViewportHeight(active) {
142
- const getHeight = useCallback(() => typeof window === "undefined" ? 0 : window.visualViewport?.height ?? window.innerHeight, []);
143
- const [height, setHeight] = useState(() => getHeight());
150
+ };
151
+ const getViewportHeight = () => typeof window === "undefined" ? 0 : window.visualViewport?.height ?? window.innerHeight;
152
+ const useViewportHeight = (active) => {
153
+ const [height, setHeight] = useState(() => typeof window === "undefined" ? void 0 : getViewportHeight());
144
154
  useEffect(() => {
145
- if (typeof window === "undefined") return;
146
- const update = () => setHeight(getHeight());
147
- update();
148
- window.addEventListener("resize", update);
149
- window.visualViewport?.addEventListener("resize", update);
155
+ const update = () => {
156
+ setHeight(getViewportHeight());
157
+ };
158
+ const canListen = typeof window !== "undefined";
159
+ if (canListen) {
160
+ window.addEventListener("resize", update);
161
+ window.visualViewport?.addEventListener("resize", update);
162
+ }
150
163
  return () => {
151
- window.removeEventListener("resize", update);
152
- window.visualViewport?.removeEventListener("resize", update);
164
+ if (canListen) {
165
+ window.removeEventListener("resize", update);
166
+ window.visualViewport?.removeEventListener("resize", update);
167
+ }
153
168
  };
154
- }, [getHeight]);
155
- return active ? height : 0;
156
- }
157
- function useBodyScale(config, isOpen, prefersReducedMotion) {
169
+ }, []);
170
+ return active ? height ?? 0 : 0;
171
+ };
172
+ const useBodyScale = (config, isOpen, prefersReducedMotion) => {
158
173
  useEffect(() => {
159
- if (!config.shouldScaleBackground || prefersReducedMotion) return;
160
174
  const wrapper = document.querySelector("[data-stacksheet-wrapper]");
161
- if (!(wrapper && wrapper instanceof HTMLElement)) return;
162
- if (isOpen) {
175
+ const resetWrapper = () => {
176
+ if (wrapper instanceof HTMLElement) {
177
+ wrapper.style.transform = "";
178
+ wrapper.style.borderRadius = "";
179
+ wrapper.style.transition = "";
180
+ wrapper.style.overflow = "";
181
+ wrapper.style.transformOrigin = "";
182
+ }
183
+ };
184
+ const canScale = config.shouldScaleBackground && !prefersReducedMotion && wrapper instanceof HTMLElement;
185
+ if (canScale && isOpen) {
163
186
  const scale = config.scaleBackgroundAmount;
164
187
  wrapper.style.transition = "transform 500ms cubic-bezier(0.32, 0.72, 0, 1), border-radius 500ms cubic-bezier(0.32, 0.72, 0, 1)";
165
188
  wrapper.style.transform = `scale(${scale})`;
166
189
  wrapper.style.borderRadius = "8px";
167
190
  wrapper.style.overflow = "hidden";
168
191
  wrapper.style.transformOrigin = "center top";
169
- return;
192
+ return resetWrapper;
170
193
  }
171
- wrapper.style.transform = "";
172
- wrapper.style.borderRadius = "";
173
194
  const handleEnd = () => {
174
- wrapper.style.transition = "";
175
- wrapper.style.overflow = "";
176
- wrapper.style.transformOrigin = "";
195
+ resetWrapper();
196
+ };
197
+ if (canScale) {
198
+ wrapper.style.transform = "";
199
+ wrapper.style.borderRadius = "";
200
+ wrapper.addEventListener("transitionend", handleEnd, { once: true });
201
+ }
202
+ return () => {
203
+ if (wrapper instanceof HTMLElement) wrapper.removeEventListener("transitionend", handleEnd);
177
204
  };
178
- wrapper.addEventListener("transitionend", handleEnd, { once: true });
179
- return () => wrapper.removeEventListener("transitionend", handleEnd);
180
205
  }, [
181
206
  isOpen,
182
207
  config.shouldScaleBackground,
183
208
  config.scaleBackgroundAmount,
184
209
  prefersReducedMotion
185
210
  ]);
186
- }
211
+ };
187
212
  //#endregion
188
213
  //#region src/snap-points.ts
189
- const SNAP_POINT_RE = /^(\d+(?:\.\d+)?)(px|rem|em|vh|%)$/;
214
+ const SNAP_POINT_RE = /^(?<value>\d+(?:\.\d+)?)(?<unit>px|rem|em|vh|%)$/u;
215
+ const getRootFontSize = () => typeof document === "undefined" ? 16 : Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
216
+ const resolveUnitPx = (value, unit, viewportHeight) => {
217
+ if (unit === "px") return value;
218
+ if (unit === "rem" || unit === "em") return value * getRootFontSize();
219
+ if (unit === "vh" || unit === "%") return value / 100 * viewportHeight;
220
+ return 0;
221
+ };
190
222
  /**
191
223
  * Resolve a snap point value to pixels given the viewport height.
192
224
  * - number 0-1: fraction of viewport (0.5 → 50% of vh)
193
225
  * - number > 1: pixel value (300 → 300px)
194
226
  * - string: parsed via regex for CSS unit support
195
227
  */
196
- function resolveSnapPointPx(point, viewportHeight) {
228
+ const resolveSnapPointPx = (point, viewportHeight) => {
197
229
  if (typeof point === "number") return point <= 1 ? point * viewportHeight : point;
198
230
  if (typeof point === "string") {
199
- const match = point.match(SNAP_POINT_RE);
200
- if (!match?.[1]) return 0;
201
- const value = Number.parseFloat(match[1]);
202
- switch (match[2]) {
203
- case "px": return value;
204
- case "rem":
205
- case "em": return value * (typeof document === "undefined" ? 16 : Number.parseFloat(getComputedStyle(document.documentElement).fontSize));
206
- case "vh":
207
- case "%": return value / 100 * viewportHeight;
208
- default: return 0;
209
- }
231
+ const match = SNAP_POINT_RE.exec(point);
232
+ const rawValue = match?.groups?.value;
233
+ const unit = match?.groups?.unit;
234
+ if (rawValue === void 0 || unit === void 0) return 0;
235
+ return resolveUnitPx(Number.parseFloat(rawValue), unit, viewportHeight);
210
236
  }
211
237
  return 0;
212
- }
238
+ };
213
239
  /**
214
240
  * Resolve all snap points to sorted pixel heights (ascending).
215
241
  * Returns an array of heights in px that the drawer should snap to.
216
242
  */
217
- function resolveSnapPoints(points, viewportHeight) {
243
+ const resolveSnapPoints = (points, viewportHeight) => {
218
244
  if (points.length === 0) return [];
219
245
  const resolved = [];
220
246
  for (const point of points) {
@@ -228,7 +254,7 @@ function resolveSnapPoints(points, viewportHeight) {
228
254
  if (last === void 0 || Math.abs(px - last) > 1) deduped.push(px);
229
255
  }
230
256
  return deduped;
231
- }
257
+ };
232
258
  /** Velocity threshold (px/ms) for skipping intermediate snap points */
233
259
  const SNAP_VELOCITY_THRESHOLD = .4;
234
260
  /** Max velocity to consider for snap offset calculation */
@@ -244,7 +270,7 @@ const SNAP_VELOCITY_MULTIPLIER = 150;
244
270
  *
245
271
  * Returns -1 if the gesture indicates full dismissal.
246
272
  */
247
- function findSnapTarget(dragOffset, panelHeight, snapHeights, velocity, currentIndex, sequential) {
273
+ const findSnapTarget = (dragOffset, panelHeight, snapHeights, velocity, currentIndex, sequential) => {
248
274
  if (snapHeights.length === 0) return -1;
249
275
  const snapOffsets = snapHeights.map((h) => panelHeight - h);
250
276
  const currentPos = dragOffset;
@@ -258,7 +284,7 @@ function findSnapTarget(dragOffset, panelHeight, snapHeights, velocity, currentI
258
284
  const first = snapOffsets[0] ?? 0;
259
285
  let bestIndex = 0;
260
286
  let bestDist = Math.abs(projectedPos - first);
261
- for (let i = 1; i < snapOffsets.length; i++) {
287
+ for (let i = 1; i < snapOffsets.length; i += 1) {
262
288
  const offset = snapOffsets[i] ?? 0;
263
289
  const dist = Math.abs(projectedPos - offset);
264
290
  if (dist < bestDist) {
@@ -268,31 +294,31 @@ function findSnapTarget(dragOffset, panelHeight, snapHeights, velocity, currentI
268
294
  }
269
295
  if (Math.abs(projectedPos - panelHeight) <= bestDist) return -1;
270
296
  return bestIndex;
271
- }
297
+ };
272
298
  /**
273
299
  * Get the Y offset for a snap point relative to fully open (0 offset).
274
300
  * Returns the number of pixels the drawer should be translated down from fully open.
275
301
  */
276
- function getSnapOffset(snapIndex, snapHeights, panelHeight) {
302
+ const getSnapOffset = (snapIndex, snapHeights, panelHeight) => {
277
303
  if (snapIndex < 0 || snapIndex >= snapHeights.length) return 0;
278
304
  return panelHeight - (snapHeights[snapIndex] ?? 0);
279
- }
305
+ };
280
306
  //#endregion
281
307
  //#region src/renderer-helpers.ts
282
308
  const EMPTY_CLASSNAMES = {
283
309
  backdrop: "",
284
- panel: "",
285
- header: ""
310
+ header: "",
311
+ panel: ""
286
312
  };
287
- function resolveClassNames(cn) {
313
+ const resolveClassNames = (cn) => {
288
314
  if (!cn) return EMPTY_CLASSNAMES;
289
315
  return {
290
316
  backdrop: cn.backdrop ?? "",
291
- panel: cn.panel ?? "",
292
- header: cn.header ?? ""
317
+ header: cn.header ?? "",
318
+ panel: cn.panel ?? ""
293
319
  };
294
- }
295
- function buildAriaProps(isTop, isModal, isComposable, ariaLabel, panelId, hasDescription) {
320
+ };
321
+ const buildAriaProps = (isTop, isModal, isComposable, ariaLabel, panelId, hasDescription) => {
296
322
  if (!isTop) return {};
297
323
  const props = { role: "dialog" };
298
324
  if (isModal) props["aria-modal"] = "true";
@@ -301,8 +327,8 @@ function buildAriaProps(isTop, isModal, isComposable, ariaLabel, panelId, hasDes
301
327
  if (hasDescription) props["aria-describedby"] = `${panelId}-desc`;
302
328
  } else props["aria-label"] = ariaLabel;
303
329
  return props;
304
- }
305
- function getDragTransform(side, offset) {
330
+ };
331
+ const getDragTransform = (side, offset) => {
306
332
  if (offset === 0) return {};
307
333
  switch (side) {
308
334
  case "right": return { x: offset };
@@ -310,57 +336,58 @@ function getDragTransform(side, offset) {
310
336
  case "bottom": return { y: offset };
311
337
  default: return {};
312
338
  }
313
- }
339
+ };
314
340
  const VISUAL_TWEEN = {
315
- type: "tween",
316
341
  duration: .25,
317
- ease: "easeOut"
342
+ ease: "easeOut",
343
+ type: "tween"
318
344
  };
319
- function buildPanelStyle(panelStyles, isTop, hasPanelClass, isDragging) {
320
- return {
321
- ...panelStyles,
322
- pointerEvents: isTop ? "auto" : "none",
323
- ...isTop ? {} : { contain: "layout style paint" },
324
- ...isDragging ? { transition: "none" } : {},
325
- ...hasPanelClass ? {} : {
326
- background: "var(--background, #fff)",
327
- borderColor: "var(--border, transparent)"
328
- }
329
- };
330
- }
331
- function buildPanelTransition(isDragging, isTop, spring, stackSpring) {
345
+ const SHADOW_SM = "0px 1px 3px 0px rgba(0,0,0,0.06), 0px 6px 12px 0px rgba(0,0,0,0.06)";
346
+ const SHADOW_LG = "0px 8px 24px 0px rgba(0,0,0,0.06), 0px 24px 48px 0px rgba(0,0,0,0.04), 0px 48px 96px 0px rgba(0,0,0,0.03)";
347
+ const getShadow = (isNested) => isNested ? SHADOW_SM : SHADOW_LG;
348
+ const buildPanelStyle = (panelStyles, isTop, hasPanelClass, isDragging) => ({
349
+ ...panelStyles,
350
+ pointerEvents: isTop ? "auto" : "none",
351
+ ...isTop ? {} : { contain: "layout style paint" },
352
+ ...isDragging ? { transition: "none" } : {},
353
+ ...hasPanelClass ? {} : {
354
+ background: "var(--background, #fff)",
355
+ borderColor: "var(--border, transparent)"
356
+ }
357
+ });
358
+ const buildPanelTransition = (isDragging, isTop, spring, stackSpring) => {
332
359
  if (isDragging) return {
333
- type: "tween",
334
- duration: 0
360
+ duration: 0,
361
+ type: "tween"
335
362
  };
336
363
  return {
337
364
  ...isTop ? spring : stackSpring,
338
365
  borderRadius: VISUAL_TWEEN,
339
366
  boxShadow: VISUAL_TWEEN
340
367
  };
341
- }
342
- function computeSnapYOffset(side, snapHeights, activeSnapIndex, measuredHeight) {
368
+ };
369
+ const computeSnapYOffset = (side, snapHeights, activeSnapIndex, measuredHeight) => {
343
370
  if (side !== "bottom" || snapHeights.length === 0 || measuredHeight <= 0) return 0;
344
371
  return getSnapOffset(activeSnapIndex, snapHeights, measuredHeight);
345
- }
346
- function getBottomSlideDistance(measuredHeight) {
372
+ };
373
+ const getBottomSlideDistance = (measuredHeight) => {
347
374
  if (measuredHeight > 0) return measuredHeight;
348
375
  if (typeof window !== "undefined") return window.innerHeight;
349
376
  return 1e3;
350
- }
351
- function resolveSlideFrom(side, slideFrom, measuredHeight) {
377
+ };
378
+ const resolveSlideFrom = (side, slideFrom, measuredHeight) => {
352
379
  if (side !== "bottom") return slideFrom;
353
380
  return { y: getBottomSlideDistance(measuredHeight) };
354
- }
355
- function buildAnimateTarget(slideTarget, stackOffset, dragOffset, transform, animatedRadius, transition, snapYOffset, isTop) {
381
+ };
382
+ const buildAnimateTarget = (slideTarget, stackOffset, dragOffset, transform, animatedRadius, transition, snapYOffset, isTop) => {
356
383
  const base = {
357
384
  ...slideTarget,
358
385
  ...stackOffset,
359
386
  ...dragOffset,
360
- scale: transform.scale,
361
- opacity: transform.opacity,
362
387
  ...animatedRadius,
363
388
  boxShadow: getShadow(!isTop),
389
+ opacity: transform.opacity,
390
+ scale: transform.scale,
364
391
  transition
365
392
  };
366
393
  if (snapYOffset > 0) return {
@@ -368,23 +395,18 @@ function buildAnimateTarget(slideTarget, stackOffset, dragOffset, transform, ani
368
395
  y: (dragOffset.y ?? 0) + snapYOffset
369
396
  };
370
397
  return base;
371
- }
372
- function getInitialRadius(side) {
398
+ };
399
+ const getInitialRadius = (side) => {
373
400
  if (side === "bottom") return {
374
- borderTopLeftRadius: 0,
375
- borderTopRightRadius: 0,
376
401
  borderBottomLeftRadius: 0,
377
- borderBottomRightRadius: 0
402
+ borderBottomRightRadius: 0,
403
+ borderTopLeftRadius: 0,
404
+ borderTopRightRadius: 0
378
405
  };
379
406
  return { borderRadius: 0 };
380
- }
381
- const SHADOW_SM = "0px 1px 3px 0px rgba(0,0,0,0.06), 0px 6px 12px 0px rgba(0,0,0,0.06)";
382
- const SHADOW_LG = "0px 8px 24px 0px rgba(0,0,0,0.06), 0px 24px 48px 0px rgba(0,0,0,0.04), 0px 48px 96px 0px rgba(0,0,0,0.03)";
383
- function getShadow(isNested) {
384
- return isNested ? SHADOW_SM : SHADOW_LG;
385
- }
407
+ };
386
408
  //#endregion
387
- //#region src/Drag/DragConstants.ts
409
+ //#region src/Drag/drag-constants.ts
388
410
  /** Elements that should never initiate a drag */
389
411
  const INTERACTIVE_TAGS = new Set([
390
412
  "INPUT",
@@ -396,19 +418,19 @@ const INTERACTIVE_TAGS = new Set([
396
418
  /** Rubber-band resistance factor for dragging past resting position */
397
419
  const RUBBER_BAND_FACTOR = .6;
398
420
  //#endregion
399
- //#region src/Drag/DragGeometry.ts
400
- function isInteractiveElement(el) {
421
+ //#region src/Drag/drag-geometry.ts
422
+ const isInteractiveElement = (el) => {
401
423
  if (INTERACTIVE_TAGS.has(el.tagName)) return true;
402
- if (el.isContentEditable) return true;
424
+ if (el instanceof HTMLElement && el.isContentEditable) return true;
403
425
  if (el.closest("button, a, input, textarea, select, [contenteditable]")) return true;
404
426
  if (el.closest("[data-stacksheet-no-drag]")) return true;
405
427
  return false;
406
- }
428
+ };
407
429
  /**
408
430
  * Walk up from `el` to find the nearest scrollable ancestor.
409
431
  * Returns null if nothing is scrollable in the dismiss axis.
410
432
  */
411
- function findScrollableAncestor(el, axis) {
433
+ const findScrollableAncestor = (el, axis) => {
412
434
  let current = el;
413
435
  while (current) {
414
436
  if (current instanceof HTMLElement) {
@@ -421,23 +443,23 @@ function findScrollableAncestor(el, axis) {
421
443
  current = current.parentElement;
422
444
  }
423
445
  return null;
424
- }
446
+ };
425
447
  /**
426
448
  * Check if a scrollable element is at its edge in the dismiss direction.
427
449
  * For bottom sheets (sign=1, axis=y), "at edge" means scrolled to top.
428
450
  * For left panels (sign=-1, axis=x), "at edge" means scrolled to right end.
429
451
  */
430
- function isAtScrollEdge(el, axis, sign) {
452
+ const isAtScrollEdge = (el, axis, sign) => {
431
453
  if (axis === "y") return sign === 1 ? el.scrollTop <= 0 : el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
432
454
  return sign === 1 ? el.scrollLeft <= 0 : el.scrollLeft + el.clientWidth >= el.scrollWidth - 1;
433
- }
455
+ };
434
456
  /**
435
457
  * Get the dismiss direction axis and sign for a given side.
436
458
  * - right panel → dismiss by dragging right (+x)
437
459
  * - left panel → dismiss by dragging left (-x)
438
460
  * - bottom panel → dismiss by dragging down (+y)
439
461
  */
440
- function getDismissAxis(side) {
462
+ const getDismissAxis = (side) => {
441
463
  switch (side) {
442
464
  case "right": return {
443
465
  axis: "x",
@@ -456,13 +478,13 @@ function getDismissAxis(side) {
456
478
  sign: 1
457
479
  };
458
480
  }
459
- }
481
+ };
460
482
  /**
461
483
  * Decide whether a gesture past the dead zone qualifies as a dismiss drag.
462
484
  * Returns "drag" if it's a valid dismiss gesture, "none" if it's off-axis
463
485
  * or moving in the wrong direction.
464
486
  */
465
- function classifyGesture(dx, dy, axis, sign) {
487
+ const classifyGesture = (dx, dy, axis, sign) => {
466
488
  const absDx = Math.abs(dx);
467
489
  const absDy = Math.abs(dy);
468
490
  let angleDeg;
@@ -471,103 +493,77 @@ function classifyGesture(dx, dy, axis, sign) {
471
493
  if (angleDeg > 35) return "none";
472
494
  if ((axis === "x" ? dx : dy) * sign < 0) return "none";
473
495
  return "drag";
474
- }
496
+ };
475
497
  /** Decide whether a pointer gesture commits as a drag or should be ignored. */
476
- function commitGesture(dx, dy, axis, sign, scrollEl) {
498
+ const commitGesture = (dx, dy, axis, sign, scrollEl) => {
477
499
  if (classifyGesture(dx, dy, axis, sign) === "none") return "none";
478
- if (scrollEl && !isAtScrollEdge(scrollEl, axis, sign)) return "none";
500
+ if (scrollEl !== null && !isAtScrollEdge(scrollEl, axis, sign)) return "none";
479
501
  return "drag";
480
- }
481
- function getPanelDimension(panel, axis) {
502
+ };
503
+ const getPanelDimension = (panel, axis) => {
482
504
  if (!panel) return 300;
483
505
  return axis === "x" ? panel.offsetWidth : panel.offsetHeight;
484
- }
506
+ };
485
507
  //#endregion
486
- //#region src/Drag/useDrag.ts
487
- /**
488
- * Hook that manages drag gestures that can dismiss a sheet panel.
489
- *
490
- * Gesture pipeline:
491
- * 1. Dead zone (10px) — ignores micro-movements
492
- * 2. Angle check (35°) — must be roughly aligned with dismiss axis
493
- * 3. Scroll conflict yields to scrollable containers not at edge
494
- * 4. Commit drag is active, applies offset via `onDragUpdate`
495
- * 5. Release — velocity + threshold determine close/snap/bounce-back
496
- *
497
- * Opposite-direction drag uses √(offset) damping for elastic
498
- * rubber-band resistance (same physics as iOS over-scroll).
499
- *
500
- * When `snapHeights` is provided, release targeting uses
501
- * `findSnapTarget()` instead of the simple threshold check.
502
- */
503
- function useDrag(panelRef, config, onDragUpdate) {
504
- const startRef = useRef(null);
505
- const committedRef = useRef(null);
506
- const offsetRef = useRef(0);
507
- const scrollTargetRef = useRef(null);
508
- const { axis, sign } = getDismissAxis(config.side);
509
- const handlePointerDown = useCallback((e) => {
510
- if (!config.enabled) return;
511
- if (e.button !== 0) return;
512
- const target = e.target;
513
- if (!target) return;
514
- const isHandle = !!target.closest("[data-stacksheet-handle]");
508
+ //#region src/Drag/use-drag.ts
509
+ const resetDragRefs = ({ committedRef, offsetRef, scrollTargetRef, startRef }) => {
510
+ startRef.current = null;
511
+ committedRef.current = null;
512
+ offsetRef.current = 0;
513
+ scrollTargetRef.current = null;
514
+ };
515
+ const createDragHandlers = ({ axis, config, onDragUpdate, panelRef, refs, sign }) => {
516
+ const dismiss = () => {
517
+ if (config.isNested) config.onPop();
518
+ else config.onClose();
519
+ };
520
+ const handlePointerDown = (e) => {
521
+ if (!config.enabled || e.button !== 0 || !(e.target instanceof Element)) return;
522
+ const { target } = e;
523
+ const isHandle = target.closest("[data-stacksheet-handle]") !== null;
515
524
  if (!isHandle && isInteractiveElement(target)) return;
516
- scrollTargetRef.current = isHandle ? null : findScrollableAncestor(target, axis);
517
- startRef.current = {
525
+ refs.scrollTargetRef.current = isHandle ? null : findScrollableAncestor(target, axis);
526
+ refs.startRef.current = {
527
+ time: Date.now(),
518
528
  x: e.clientX,
519
- y: e.clientY,
520
- time: Date.now()
529
+ y: e.clientY
521
530
  };
522
- committedRef.current = null;
523
- offsetRef.current = 0;
524
- e.currentTarget?.setPointerCapture?.(e.pointerId);
525
- }, [config.enabled, axis]);
526
- const handlePointerMove = useCallback((e) => {
527
- if (!startRef.current) return;
528
- const dx = e.clientX - startRef.current.x;
529
- const dy = e.clientY - startRef.current.y;
530
- const dist = Math.sqrt(dx * dx + dy * dy);
531
- if (committedRef.current === null && dist < 10) return;
532
- if (committedRef.current === null) {
533
- committedRef.current = commitGesture(dx, dy, axis, sign, scrollTargetRef.current);
534
- if (committedRef.current !== "drag") {
535
- startRef.current = null;
531
+ refs.committedRef.current = null;
532
+ refs.offsetRef.current = 0;
533
+ if (e.currentTarget instanceof HTMLElement) e.currentTarget.setPointerCapture(e.pointerId);
534
+ };
535
+ const handlePointerMove = (e) => {
536
+ if (refs.startRef.current === null) return;
537
+ const dx = e.clientX - refs.startRef.current.x;
538
+ const dy = e.clientY - refs.startRef.current.y;
539
+ const dist = Math.hypot(dx, dy);
540
+ if (refs.committedRef.current === null && dist < 10) return;
541
+ if (refs.committedRef.current === null) {
542
+ refs.committedRef.current = commitGesture(dx, dy, axis, sign, refs.scrollTargetRef.current);
543
+ if (refs.committedRef.current !== "drag") {
544
+ refs.startRef.current = null;
536
545
  return;
537
546
  }
538
547
  }
539
- if (committedRef.current !== "drag") return;
548
+ if (refs.committedRef.current !== "drag") return;
540
549
  const directional = (axis === "x" ? dx : dy) * sign;
541
550
  const clampedOffset = directional >= 0 ? directional : -Math.sqrt(Math.abs(directional)) * RUBBER_BAND_FACTOR;
542
- offsetRef.current = clampedOffset;
551
+ refs.offsetRef.current = clampedOffset;
543
552
  onDragUpdate({
544
- offset: clampedOffset,
545
- isDragging: true
553
+ isDragging: true,
554
+ offset: clampedOffset
546
555
  });
547
556
  e.preventDefault();
548
- }, [
549
- axis,
550
- sign,
551
- onDragUpdate
552
- ]);
553
- const dismiss = useCallback(() => {
554
- if (config.isNested) config.onPop();
555
- else config.onClose();
556
- }, [config]);
557
- const handlePointerUp = useCallback((_e) => {
558
- if (!startRef.current || committedRef.current !== "drag") {
559
- startRef.current = null;
560
- committedRef.current = null;
561
- scrollTargetRef.current = null;
557
+ };
558
+ const handlePointerUp = () => {
559
+ if (refs.startRef.current === null || refs.committedRef.current !== "drag") {
560
+ resetDragRefs(refs);
562
561
  return;
563
562
  }
564
- const offset = Math.max(0, offsetRef.current);
565
- const elapsed = Date.now() - startRef.current.time;
563
+ const offset = Math.max(0, refs.offsetRef.current);
564
+ const elapsed = Date.now() - refs.startRef.current.time;
566
565
  const velocity = elapsed > 0 ? offset / elapsed : 0;
567
- startRef.current = null;
568
- committedRef.current = null;
569
- offsetRef.current = 0;
570
- scrollTargetRef.current = null;
566
+ resetDragRefs(refs);
571
567
  const panelSize = getPanelDimension(panelRef.current, axis);
572
568
  if (config.snapHeights.length > 0) {
573
569
  const targetIndex = findSnapTarget(offset, panelSize, config.snapHeights, velocity, config.activeSnapIndex, config.sequential);
@@ -575,8 +571,8 @@ function useDrag(panelRef, config, onDragUpdate) {
575
571
  else {
576
572
  config.onSnap(targetIndex);
577
573
  onDragUpdate({
578
- offset: 0,
579
- isDragging: false
574
+ isDragging: false,
575
+ offset: 0
580
576
  });
581
577
  }
582
578
  return;
@@ -585,26 +581,59 @@ function useDrag(panelRef, config, onDragUpdate) {
585
581
  const fastEnough = velocity > config.velocityThreshold;
586
582
  if (pastThreshold || fastEnough) dismiss();
587
583
  else onDragUpdate({
588
- offset: 0,
589
- isDragging: false
584
+ isDragging: false,
585
+ offset: 0
590
586
  });
591
- }, [
592
- panelRef,
587
+ };
588
+ const handlePointerCancel = () => {
589
+ resetDragRefs(refs);
590
+ onDragUpdate({
591
+ isDragging: false,
592
+ offset: 0
593
+ });
594
+ };
595
+ return {
596
+ handlePointerCancel,
597
+ handlePointerDown,
598
+ handlePointerMove,
599
+ handlePointerUp
600
+ };
601
+ };
602
+ /**
603
+ * Hook that manages drag gestures that can dismiss a sheet panel.
604
+ *
605
+ * Gesture pipeline:
606
+ * 1. Dead zone (10px) — ignores micro-movements
607
+ * 2. Angle check (35°) — must be roughly aligned with dismiss axis
608
+ * 3. Scroll conflict — yields to scrollable containers not at edge
609
+ * 4. Commit — drag is active, applies offset via `onDragUpdate`
610
+ * 5. Release — velocity + threshold determine close/snap/bounce-back
611
+ *
612
+ * Opposite-direction drag uses √(offset) damping for elastic
613
+ * rubber-band resistance (same physics as iOS over-scroll).
614
+ *
615
+ * When `snapHeights` is provided, release targeting uses
616
+ * `findSnapTarget()` instead of the simple threshold check.
617
+ */
618
+ const useDrag = (panelRef, config, onDragUpdate) => {
619
+ const startRef = useRef(null);
620
+ const committedRef = useRef(null);
621
+ const offsetRef = useRef(0);
622
+ const scrollTargetRef = useRef(null);
623
+ const { axis, sign } = getDismissAxis(config.side);
624
+ const { handlePointerCancel, handlePointerDown, handlePointerMove, handlePointerUp } = createDragHandlers({
593
625
  axis,
594
626
  config,
595
627
  onDragUpdate,
596
- dismiss
597
- ]);
598
- const handlePointerCancel = useCallback(() => {
599
- startRef.current = null;
600
- committedRef.current = null;
601
- offsetRef.current = 0;
602
- scrollTargetRef.current = null;
603
- onDragUpdate({
604
- offset: 0,
605
- isDragging: false
606
- });
607
- }, [onDragUpdate]);
628
+ panelRef,
629
+ refs: {
630
+ committedRef,
631
+ offsetRef,
632
+ scrollTargetRef,
633
+ startRef
634
+ },
635
+ sign
636
+ });
608
637
  const handlersRef = useRef({
609
638
  handlePointerCancel,
610
639
  handlePointerDown,
@@ -619,23 +648,35 @@ function useDrag(panelRef, config, onDragUpdate) {
619
648
  };
620
649
  useEffect(() => {
621
650
  const el = panelRef.current;
622
- if (!(el && config.enabled)) return;
623
- const onPointerDown = (event) => handlersRef.current.handlePointerDown(event);
624
- const onPointerMove = (event) => handlersRef.current.handlePointerMove(event);
625
- const onPointerUp = (event) => handlersRef.current.handlePointerUp(event);
626
- const onPointerCancel = () => handlersRef.current.handlePointerCancel();
627
- el.addEventListener("pointerdown", onPointerDown);
628
- el.addEventListener("pointermove", onPointerMove);
629
- el.addEventListener("pointerup", onPointerUp);
630
- el.addEventListener("pointercancel", onPointerCancel);
651
+ const onPointerDown = (event) => {
652
+ handlersRef.current.handlePointerDown(event);
653
+ };
654
+ const onPointerMove = (event) => {
655
+ handlersRef.current.handlePointerMove(event);
656
+ };
657
+ const onPointerUp = () => {
658
+ handlersRef.current.handlePointerUp();
659
+ };
660
+ const onPointerCancel = () => {
661
+ handlersRef.current.handlePointerCancel();
662
+ };
663
+ const canListen = el !== null && config.enabled;
664
+ if (canListen) {
665
+ el.addEventListener("pointerdown", onPointerDown);
666
+ el.addEventListener("pointermove", onPointerMove);
667
+ el.addEventListener("pointerup", onPointerUp);
668
+ el.addEventListener("pointercancel", onPointerCancel);
669
+ }
631
670
  return () => {
632
- el.removeEventListener("pointerdown", onPointerDown);
633
- el.removeEventListener("pointermove", onPointerMove);
634
- el.removeEventListener("pointerup", onPointerUp);
635
- el.removeEventListener("pointercancel", onPointerCancel);
671
+ if (canListen) {
672
+ el.removeEventListener("pointerdown", onPointerDown);
673
+ el.removeEventListener("pointermove", onPointerMove);
674
+ el.removeEventListener("pointerup", onPointerUp);
675
+ el.removeEventListener("pointercancel", onPointerCancel);
676
+ }
636
677
  };
637
678
  }, [panelRef, config.enabled]);
638
- }
679
+ };
639
680
  //#endregion
640
681
  //#region src/panel-context.tsx
641
682
  const SheetPanelContext = createContext(null);
@@ -643,11 +684,11 @@ const SheetPanelContext = createContext(null);
643
684
  * Access the current sheet panel's context.
644
685
  * Must be called inside a component rendered by the sheet stack.
645
686
  */
646
- function useSheetPanel() {
647
- const ctx = useContext(SheetPanelContext);
687
+ const useSheetPanel = () => {
688
+ const ctx = use(SheetPanelContext);
648
689
  if (!ctx) throw new Error("Sheet.* components must be used inside a sheet panel. They should be rendered by a component opened via actions.open(), push(), etc.");
649
690
  return ctx;
650
- }
691
+ };
651
692
  //#endregion
652
693
  //#region src/stacking.ts
653
694
  /**
@@ -655,58 +696,56 @@ function useSheetPanel() {
655
696
  * depth=0 is the top (foreground) panel.
656
697
  * Panels beyond renderThreshold are clamped to the edge position and faded out.
657
698
  */
658
- function getStackTransform(depth, stacking) {
699
+ const getStackTransform = (depth, stacking) => {
659
700
  if (depth <= 0) return {
660
- scale: 1,
701
+ borderRadius: 0,
661
702
  offset: 0,
662
703
  opacity: 1,
663
- borderRadius: 0
704
+ scale: 1
664
705
  };
665
706
  const beyondThreshold = depth >= stacking.renderThreshold;
666
707
  const visualDepth = beyondThreshold ? stacking.renderThreshold - 1 : depth;
667
708
  return {
668
- scale: Math.max(.5, 1 - visualDepth * stacking.scaleStep),
709
+ borderRadius: stacking.radius,
669
710
  offset: visualDepth * stacking.offsetStep,
670
711
  opacity: beyondThreshold ? 0 : Math.max(0, 1 - visualDepth * stacking.opacityStep),
671
- borderRadius: stacking.radius
712
+ scale: Math.max(.5, 1 - visualDepth * stacking.scaleStep)
672
713
  };
673
- }
714
+ };
674
715
  /**
675
716
  * Border radius values for the animate target.
676
717
  * Must be animated (not static CSS) so Motion applies scale correction
677
718
  * when panels are scaled. See: https://motion.dev/docs/react-layout-animations#scale-correction
678
719
  */
679
- function getAnimatedBorderRadius(side, depth, stacking) {
720
+ const getAnimatedBorderRadius = (side, depth, stacking) => {
680
721
  if (side === "bottom") {
681
722
  const radius = depth > 0 ? stacking.radius : 16;
682
723
  return {
683
- borderTopLeftRadius: radius,
684
- borderTopRightRadius: radius,
685
724
  borderBottomLeftRadius: 0,
686
- borderBottomRightRadius: 0
725
+ borderBottomRightRadius: 0,
726
+ borderTopLeftRadius: radius,
727
+ borderTopRightRadius: radius
687
728
  };
688
729
  }
689
730
  if (depth > 0) return { borderRadius: stacking.radius };
690
731
  return { borderRadius: 0 };
691
- }
732
+ };
692
733
  /** Motion initial/exit values for sliding from the given side. */
693
- function getSlideFrom(side) {
734
+ const getSlideFrom = (side) => {
694
735
  switch (side) {
695
736
  case "right": return { x: "100%" };
696
737
  case "left": return { x: "-100%" };
697
738
  case "bottom": return { y: "100%" };
698
739
  default: return { x: "100%" };
699
740
  }
700
- }
741
+ };
701
742
  /** Motion animate target — the resting position. */
702
- function getSlideTarget() {
703
- return {
704
- x: 0,
705
- y: 0
706
- };
707
- }
743
+ const getSlideTarget = () => ({
744
+ x: 0,
745
+ y: 0
746
+ });
708
747
  /** Translate offset that pushes stacked panels away from the stack edge. */
709
- function getStackOffset(side, offset) {
748
+ const getStackOffset = (side, offset) => {
710
749
  if (offset === 0) return {};
711
750
  switch (side) {
712
751
  case "right": return { x: -offset };
@@ -714,286 +753,319 @@ function getStackOffset(side, offset) {
714
753
  case "bottom": return { y: -offset };
715
754
  default: return {};
716
755
  }
717
- }
756
+ };
718
757
  /** Opposite-side origin so stacked panels recede away from the stack edge. */
719
- function getTransformOrigin(side) {
758
+ const getTransformOrigin = (side) => {
720
759
  if (side === "right") return "left center";
721
760
  if (side === "left") return "right center";
722
761
  return "center top";
723
- }
762
+ };
724
763
  /**
725
764
  * Fixed-position styles for a panel, accounting for side, width, and depth.
726
765
  */
727
- function getPanelStyles(side, config, index) {
766
+ const getPanelStyles = (side, config, index) => {
728
767
  const { width, maxWidth, zIndex } = config;
729
768
  const base = {
730
- position: "fixed",
731
- zIndex: zIndex + 10 + index,
732
769
  display: "flex",
733
770
  flexDirection: "column",
771
+ position: "fixed",
772
+ transformOrigin: getTransformOrigin(side),
734
773
  willChange: "transform",
735
- transformOrigin: getTransformOrigin(side)
774
+ zIndex: zIndex + 10 + index
736
775
  };
737
776
  if (side === "bottom") return {
738
777
  ...base,
739
- left: 0,
740
- right: 0,
741
778
  bottom: 0,
742
779
  height: "85dvh",
743
- maxHeight: "85dvh"
780
+ left: 0,
781
+ maxHeight: "85dvh",
782
+ right: 0
744
783
  };
745
784
  const sideStyles = side === "right" ? {
746
- top: 0,
785
+ bottom: 0,
747
786
  right: 0,
748
- bottom: 0
787
+ top: 0
749
788
  } : {
750
- top: 0,
789
+ bottom: 0,
751
790
  left: 0,
752
- bottom: 0
791
+ top: 0
753
792
  };
754
793
  return {
755
794
  ...base,
756
795
  ...sideStyles,
757
- width,
758
- maxWidth
796
+ maxWidth,
797
+ width
759
798
  };
760
- }
799
+ };
761
800
  //#endregion
762
801
  //#region src/icons.tsx
763
802
  /** Inline SVG icons — no external dependency */
764
- function ArrowLeftIcon() {
765
- return /* @__PURE__ */ jsx("svg", {
766
- "aria-hidden": "true",
767
- fill: "none",
768
- height: 16,
769
- stroke: "currentColor",
770
- strokeLinecap: "round",
771
- strokeLinejoin: "round",
772
- strokeWidth: 2,
773
- viewBox: "0 0 24 24",
774
- width: 16,
775
- children: /* @__PURE__ */ jsx("path", { d: "M19 12H5M12 19l-7-7 7-7" })
776
- });
777
- }
778
- function XIcon() {
779
- return /* @__PURE__ */ jsx("svg", {
780
- "aria-hidden": "true",
781
- fill: "none",
782
- height: 16,
783
- stroke: "currentColor",
784
- strokeLinecap: "round",
785
- strokeLinejoin: "round",
786
- strokeWidth: 2,
787
- viewBox: "0 0 24 24",
788
- width: 16,
789
- children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
790
- });
791
- }
803
+ const ArrowLeftIcon = () => /* @__PURE__ */ jsx("svg", {
804
+ "aria-hidden": "true",
805
+ fill: "none",
806
+ height: 16,
807
+ stroke: "currentColor",
808
+ strokeLinecap: "round",
809
+ strokeLinejoin: "round",
810
+ strokeWidth: 2,
811
+ viewBox: "0 0 24 24",
812
+ width: 16,
813
+ children: /* @__PURE__ */ jsx("path", { d: "M19 12H5M12 19l-7-7 7-7" })
814
+ });
815
+ const XIcon = () => /* @__PURE__ */ jsx("svg", {
816
+ "aria-hidden": "true",
817
+ fill: "none",
818
+ height: 16,
819
+ stroke: "currentColor",
820
+ strokeLinecap: "round",
821
+ strokeLinejoin: "round",
822
+ strokeWidth: 2,
823
+ viewBox: "0 0 24 24",
824
+ width: 16,
825
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
826
+ });
792
827
  //#endregion
793
- //#region src/SheetPanel/DefaultHeader.tsx
794
- function DefaultHeader({ isNested, onBack, onClose, className }) {
795
- return /* @__PURE__ */ jsxs("div", {
796
- className: `flex shrink-0 items-center justify-between px-4 pt-4 pb-2 ${className ?? ""}`,
797
- children: [/* @__PURE__ */ jsx("div", {
798
- className: "flex items-center gap-2",
799
- children: isNested && /* @__PURE__ */ jsx("button", {
800
- "aria-label": "Back",
801
- className: "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-full border-none bg-black/5 p-0 text-inherit opacity-70 transition-opacity duration-150 hover:opacity-100",
802
- onClick: onBack,
803
- type: "button",
804
- children: /* @__PURE__ */ jsx(ArrowLeftIcon, {})
805
- })
806
- }), /* @__PURE__ */ jsx("button", {
807
- "aria-label": "Close",
828
+ //#region src/SheetPanel/default-header.tsx
829
+ const DefaultHeader = ({ isNested, onBack, onClose, className }) => /* @__PURE__ */ jsxs("div", {
830
+ className: `flex shrink-0 items-center justify-between px-4 pt-4 pb-2 ${className ?? ""}`,
831
+ children: [/* @__PURE__ */ jsx("div", {
832
+ className: "flex items-center gap-2",
833
+ children: isNested && /* @__PURE__ */ jsx("button", {
834
+ "aria-label": "Back",
808
835
  className: "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-full border-none bg-black/5 p-0 text-inherit opacity-70 transition-opacity duration-150 hover:opacity-100",
809
- onClick: onClose,
836
+ onClick: onBack,
810
837
  type: "button",
811
- children: /* @__PURE__ */ jsx(XIcon, {})
812
- })]
813
- });
814
- }
838
+ children: /* @__PURE__ */ jsx(ArrowLeftIcon, {})
839
+ })
840
+ }), /* @__PURE__ */ jsx("button", {
841
+ "aria-label": "Close",
842
+ className: "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-full border-none bg-black/5 p-0 text-inherit opacity-70 transition-opacity duration-150 hover:opacity-100",
843
+ onClick: onClose,
844
+ type: "button",
845
+ children: /* @__PURE__ */ jsx(XIcon, {})
846
+ })]
847
+ });
815
848
  //#endregion
816
- //#region src/SheetPanel/SheetPanelContent.tsx
817
- const PanelInnerContent = memo(function PanelInnerContent({ isComposable, shouldRender, Content, data, renderHeader, headerProps, headerClassName }) {
818
- if (isComposable) return shouldRender && Content ? /* @__PURE__ */ jsx(Content, { ...data }) : null;
819
- return /* @__PURE__ */ jsxs(Fragment, { children: [renderHeader ? renderHeader(headerProps) : /* @__PURE__ */ jsx(DefaultHeader, {
849
+ //#region src/SheetPanel/sheet-panel-content.tsx
850
+ const PanelInnerContent = ({ isComposable, shouldRender, Content, data, renderHeader, headerProps, headerClassName }) => {
851
+ if (isComposable) return shouldRender && Content !== void 0 ? /* @__PURE__ */ jsx(Content, { ...data }) : null;
852
+ const customHeader = renderHeader !== void 0 && renderHeader !== false ? renderHeader : void 0;
853
+ return /* @__PURE__ */ jsxs(Fragment, { children: [customHeader === void 0 ? /* @__PURE__ */ jsx(DefaultHeader, {
820
854
  ...headerProps,
821
855
  className: headerClassName
822
- }), shouldRender && Content && /* @__PURE__ */ jsx("div", {
856
+ }) : customHeader(headerProps), shouldRender && Content !== void 0 && /* @__PURE__ */ jsx("div", {
823
857
  className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
824
858
  "data-stacksheet-no-drag": "",
825
859
  children: /* @__PURE__ */ jsx(Content, { ...data })
826
860
  })] });
827
- });
861
+ };
828
862
  PanelInnerContent.displayName = "PanelInnerContent";
829
863
  //#endregion
830
- //#region src/SheetPanel/SheetPanelFocus.tsx
864
+ //#region src/SheetPanel/sheet-panel-focus.tsx
831
865
  const LAYERED_MODAL_SELECTORS = [
832
866
  "[role=\"dialog\"][data-state=\"open\"]",
833
867
  "[role=\"alertdialog\"][data-state=\"open\"]",
834
868
  "[data-radix-popper-content-wrapper]",
835
869
  "[data-radix-focus-guard]"
836
870
  ].join(", ");
837
- function ModalFocusTrap({ enabled, active, fallbackRef, children }) {
871
+ const subscribeToFocusTarget = (onStoreChange) => {
872
+ document.addEventListener("focusin", onStoreChange, true);
873
+ return () => {
874
+ document.removeEventListener("focusin", onStoreChange, true);
875
+ };
876
+ };
877
+ const getServerLayeredModalFocused = () => false;
878
+ const getLayeredModalFocused = () => {
879
+ if (typeof document === "undefined") return false;
880
+ const target = document.activeElement;
881
+ return !!target && target !== document.body && target instanceof Element && target.closest(LAYERED_MODAL_SELECTORS) !== null;
882
+ };
883
+ const useLayeredModalFocused = (active) => {
884
+ const layered = useSyncExternalStore(subscribeToFocusTarget, getLayeredModalFocused, getServerLayeredModalFocused);
885
+ return active && layered;
886
+ };
887
+ const ModalFocusTrap = ({ enabled, active, fallbackRef, children }) => {
838
888
  const paused = useLayeredModalFocused(enabled && active);
839
889
  if (!enabled) return children;
840
890
  return /* @__PURE__ */ jsx(FocusTrap, {
841
891
  active,
842
892
  focusTrapOptions: {
843
- initialFocus: false,
844
- returnFocusOnDeactivate: true,
845
- escapeDeactivates: false,
846
893
  allowOutsideClick: true,
847
- checkCanFocusTrap: () => new Promise((resolve) => requestAnimationFrame(() => resolve())),
894
+ escapeDeactivates: false,
848
895
  fallbackFocus: () => {
849
- if (fallbackRef.current) return fallbackRef.current;
896
+ if (fallbackRef.current !== null) return fallbackRef.current;
850
897
  return document.body;
851
- }
898
+ },
899
+ initialFocus: false,
900
+ returnFocusOnDeactivate: true
852
901
  },
853
902
  paused,
854
903
  children
855
904
  });
856
- }
857
- function useLayeredModalFocused(active) {
858
- const layered = useSyncExternalStore(subscribeToFocusTarget, getLayeredModalFocused, getServerLayeredModalFocused);
859
- return active && layered;
860
- }
861
- function subscribeToFocusTarget(onStoreChange) {
862
- document.addEventListener("focusin", onStoreChange, true);
863
- return () => document.removeEventListener("focusin", onStoreChange, true);
864
- }
865
- function getServerLayeredModalFocused() {
866
- return false;
867
- }
868
- function getLayeredModalFocused() {
869
- if (typeof document === "undefined") return false;
870
- const target = document.activeElement;
871
- return !!target && target !== document.body && target instanceof Element && target.closest(LAYERED_MODAL_SELECTORS) !== null;
872
- }
905
+ };
873
906
  //#endregion
874
- //#region src/SheetPanel/SheetPanelHandles.tsx
875
- function BottomHandle({ onDismiss }) {
876
- return /* @__PURE__ */ jsx("button", {
877
- "aria-label": "Dismiss",
878
- className: "absolute inset-x-0 top-0 z-10 flex w-full cursor-grab touch-none items-center justify-center border-none bg-transparent pt-2.5 pb-2",
879
- "data-stacksheet-handle": "",
880
- onClick: onDismiss,
881
- type: "button",
882
- children: /* @__PURE__ */ jsx("div", {
883
- "aria-hidden": "true",
884
- className: "h-[5px] w-9 rounded-full bg-current/15"
885
- })
886
- });
887
- }
888
- function SideHandle({ side, isHovered, onDismiss }) {
907
+ //#region src/SheetPanel/sheet-panel-handles.tsx
908
+ const BottomHandle = ({ onDismiss }) => /* @__PURE__ */ jsx("button", {
909
+ "aria-label": "Dismiss",
910
+ className: "absolute inset-x-0 top-0 z-10 flex w-full cursor-grab touch-none items-center justify-center border-none bg-transparent pt-2.5 pb-2",
911
+ "data-stacksheet-handle": "",
912
+ onClick: onDismiss,
913
+ type: "button",
914
+ children: /* @__PURE__ */ jsx("div", {
915
+ "aria-hidden": "true",
916
+ className: "h-[5px] w-9 rounded-full bg-current/15"
917
+ })
918
+ });
919
+ const SideHandle = ({ side, isHovered, onDismiss }) => {
889
920
  const position = side === "right" ? { right: "100%" } : { left: "100%" };
890
- return /* @__PURE__ */ jsx(m.div, {
921
+ return /* @__PURE__ */ jsx(m.button, {
891
922
  animate: { opacity: isHovered ? 1 : 0 },
892
923
  "aria-label": "Dismiss",
893
- className: "absolute top-0 bottom-0 flex w-6 cursor-grab touch-none items-center justify-center",
924
+ className: "absolute top-0 bottom-0 flex w-6 cursor-grab touch-none items-center justify-center border-none bg-transparent p-0 text-inherit",
894
925
  "data-stacksheet-handle": "",
895
926
  onClick: onDismiss,
896
- onKeyDown: (e) => {
897
- if (e.key === "Enter" || e.key === " ") {
898
- e.preventDefault();
899
- onDismiss?.();
900
- }
901
- },
902
- role: "button",
903
927
  style: position,
904
- tabIndex: 0,
905
928
  transition: {
906
929
  duration: isHovered ? .15 : .4,
907
930
  ease: "easeOut"
908
931
  },
932
+ type: "button",
909
933
  children: /* @__PURE__ */ jsx("div", {
910
934
  "aria-hidden": "true",
911
935
  className: "h-8 w-1 rounded-full bg-current/20"
912
936
  })
913
937
  });
914
- }
938
+ };
915
939
  //#endregion
916
- //#region src/SheetPanel/SheetPanelLayout.ts
917
- function resolvePanelLayout(layout, renderHeader) {
940
+ //#region src/SheetPanel/sheet-panel-layout.ts
941
+ const resolvePanelLayout = (layout, renderHeader) => {
918
942
  if (layout) return layout;
919
943
  return renderHeader === false ? "composable" : "classic";
920
- }
944
+ };
921
945
  //#endregion
922
- //#region src/SheetPanel/SheetPanel.tsx
923
- const SheetPanel = memo(function SheetPanel({ item, index, depth, isTop, isNested, side, config, classNames, Content, shouldRender, pop, close, swipeClose, swipePop, snapHeights, activeSnapIndex, onSnap, layout, renderHeader, slideFrom, slideTarget, spring, stackSpring, prefersReducedMotion }) {
924
- const panelRef = useRef(null);
946
+ //#region src/SheetPanel/sheet-panel.tsx
947
+ const getPanelAriaLabel = (item, fallbackLabel) => item.ariaLabel ?? (typeof item.data?.__ariaLabel === "string" ? item.data.__ariaLabel : void 0) ?? fallbackLabel;
948
+ const getPanelHoverProps = (enabled, setIsHovered) => enabled ? {
949
+ onBlur: () => {
950
+ setIsHovered(false);
951
+ },
952
+ onFocus: () => {
953
+ setIsHovered(true);
954
+ },
955
+ onMouseEnter: () => {
956
+ setIsHovered(true);
957
+ },
958
+ onMouseLeave: () => {
959
+ setIsHovered(false);
960
+ }
961
+ } : {};
962
+ const getInactivePanelProps = (isTop) => isTop ? {} : {
963
+ "aria-hidden": "true",
964
+ inert: true
965
+ };
966
+ const getHeaderProps = ({ close, isNested, pop, side }) => ({
967
+ isNested,
968
+ onBack: pop,
969
+ onClose: close,
970
+ side
971
+ });
972
+ const getPanelContext = ({ close, hasDescription, isNested, isTop, panelId, pop, registerDescription, side }) => ({
973
+ back: pop,
974
+ close,
975
+ hasDescription,
976
+ isNested,
977
+ isTop,
978
+ panelId,
979
+ registerDescription,
980
+ side
981
+ });
982
+ const getOptionalSideHandle = ({ isHovered, onDismiss, show, side }) => show ? /* @__PURE__ */ jsx(SideHandle, {
983
+ isHovered,
984
+ onDismiss,
985
+ side
986
+ }) : null;
987
+ const getOptionalBottomHandle = (show, onDismiss) => show ? /* @__PURE__ */ jsx(BottomHandle, { onDismiss }) : null;
988
+ const completeOpeningAnimation = (hasEnteredRef, isTop, onOpenCompleteRef) => {
989
+ if (isTop && !hasEnteredRef.current) {
990
+ hasEnteredRef.current = true;
991
+ onOpenCompleteRef.current?.();
992
+ }
993
+ };
994
+ const useOpeningCompletion = (isTop, onOpenComplete) => {
925
995
  const hasEnteredRef = useRef(false);
996
+ const onOpenCompleteRef = useRef(onOpenComplete);
997
+ if (!isTop && hasEnteredRef.current) hasEnteredRef.current = false;
998
+ useEffect(() => {
999
+ onOpenCompleteRef.current = onOpenComplete;
1000
+ }, [onOpenComplete]);
1001
+ return () => {
1002
+ completeOpeningAnimation(hasEnteredRef, isTop, onOpenCompleteRef);
1003
+ };
1004
+ };
1005
+ const useSheetPanelDrag = ({ activeSnapIndex, config, isNested, isTop, onSnap, prefersReducedMotion, side, snapHeights, swipeClose, swipePop }, panelRef) => {
926
1006
  const [dragState, setDragState] = useState({
927
- offset: 0,
928
- isDragging: false
1007
+ isDragging: false,
1008
+ offset: 0
929
1009
  });
930
- const [isHovered, setIsHovered] = useState(false);
931
- const measuredHeight = usePanelHeight(panelRef, snapHeights.length > 0);
932
- const transform = getStackTransform(depth, config.stacking);
933
- const panelStyles = getPanelStyles(side, config, index);
934
- useEffect(() => {
935
- if (!isTop) hasEnteredRef.current = false;
936
- }, [isTop]);
937
- const handleAnimationComplete = useCallback(() => {
938
- if (isTop && !hasEnteredRef.current) {
939
- hasEnteredRef.current = true;
940
- config.onOpenComplete?.();
941
- }
942
- }, [isTop, config]);
943
1010
  useDrag(panelRef, {
944
- enabled: isTop && config.drag && config.dismissible && !prefersReducedMotion,
1011
+ activeSnapIndex,
945
1012
  closeThreshold: config.closeThreshold,
946
- velocityThreshold: config.velocityThreshold,
947
- side,
1013
+ enabled: isTop && config.drag && config.dismissible && !prefersReducedMotion,
1014
+ isNested,
948
1015
  onClose: swipeClose,
949
1016
  onPop: swipePop,
950
- isNested,
951
- snapHeights,
952
- activeSnapIndex,
953
1017
  onSnap,
954
- sequential: config.snapToSequentialPoints
1018
+ sequential: config.snapToSequentialPoints,
1019
+ side,
1020
+ snapHeights,
1021
+ velocityThreshold: config.velocityThreshold
955
1022
  }, setDragState);
956
- const ariaLabel = item.ariaLabel ?? (typeof item.data?.__ariaLabel === "string" ? item.data.__ariaLabel : void 0) ?? config.ariaLabel;
957
- const panelId = `stacksheet-${item.id}`;
1023
+ return dragState;
1024
+ };
1025
+ const useSheetPanelContext = ({ close, isNested, isTop, pop, side }, panelId) => {
958
1026
  const [hasDescription, setHasDescription] = useState(false);
959
- const registerDescription = useCallback(() => {
1027
+ const registerDescription = () => {
960
1028
  setHasDescription(true);
961
- return () => setHasDescription(false);
962
- }, []);
963
- const panelContext = useMemo(() => ({
964
- close,
965
- back: pop,
966
- isNested,
967
- isTop,
968
- panelId,
969
- side,
970
- hasDescription,
971
- registerDescription
972
- }), [
973
- close,
974
- pop,
975
- isNested,
976
- isTop,
977
- panelId,
978
- side,
1029
+ return () => {
1030
+ setHasDescription(false);
1031
+ };
1032
+ };
1033
+ return {
979
1034
  hasDescription,
980
- registerDescription
981
- ]);
1035
+ panelContext: getPanelContext({
1036
+ close,
1037
+ hasDescription,
1038
+ isNested,
1039
+ isTop,
1040
+ panelId,
1041
+ pop,
1042
+ registerDescription,
1043
+ side
1044
+ })
1045
+ };
1046
+ };
1047
+ const useSheetPanelModel = (props) => {
1048
+ const { item, index, depth, isTop, isNested, side, config, classNames, pop, close, snapHeights, activeSnapIndex, layout, renderHeader, slideFrom, slideTarget, spring, stackSpring } = props;
1049
+ const panelRef = useRef(null);
1050
+ const [isHovered, setIsHovered] = useState(false);
1051
+ const measuredHeight = usePanelHeight(panelRef, snapHeights.length > 0);
1052
+ const transform = getStackTransform(depth, config.stacking);
1053
+ const panelStyles = getPanelStyles(side, config, index);
1054
+ const handleAnimationComplete = useOpeningCompletion(isTop, config.onOpenComplete);
1055
+ const dragState = useSheetPanelDrag(props, panelRef);
1056
+ const ariaLabel = getPanelAriaLabel(item, config.ariaLabel);
1057
+ const panelId = `stacksheet-${item.id}`;
1058
+ const { hasDescription, panelContext } = useSheetPanelContext(props, panelId);
982
1059
  const isComposable = resolvePanelLayout(layout, renderHeader) === "composable";
983
1060
  const hasPanelClass = classNames.panel !== "";
984
1061
  const dragOffset = getDragTransform(side, dragState.offset);
985
1062
  const panelStyle = buildPanelStyle(panelStyles, isTop, hasPanelClass, dragState.isDragging);
986
- const headerProps = useMemo(() => ({
987
- isNested,
988
- onBack: pop,
989
- onClose: close,
990
- side
991
- }), [
1063
+ const headerProps = getHeaderProps({
992
1064
  close,
993
1065
  isNested,
994
1066
  pop,
995
1067
  side
996
- ]);
1068
+ });
997
1069
  const ariaProps = buildAriaProps(isTop, config.modal, isComposable, ariaLabel, panelId, hasDescription);
998
1070
  const transition = buildPanelTransition(dragState.isDragging, isTop, spring, stackSpring);
999
1071
  const animatedRadius = getAnimatedBorderRadius(side, depth, config.stacking);
@@ -1003,18 +1075,45 @@ const SheetPanel = memo(function SheetPanel({ item, index, depth, isTop, isNeste
1003
1075
  const initialRadius = getInitialRadius(side);
1004
1076
  const showSideHandle = isTop && side !== "bottom";
1005
1077
  const showBottomHandle = isTop && side === "bottom";
1078
+ const dismiss = isNested ? pop : close;
1079
+ const sideHandle = getOptionalSideHandle({
1080
+ isHovered,
1081
+ onDismiss: dismiss,
1082
+ show: showSideHandle,
1083
+ side
1084
+ });
1085
+ return {
1086
+ animateTarget,
1087
+ ariaProps,
1088
+ bottomHandle: getOptionalBottomHandle(showBottomHandle, dismiss),
1089
+ handleAnimationComplete,
1090
+ headerProps,
1091
+ hoverProps: getPanelHoverProps(showSideHandle, setIsHovered),
1092
+ inactivePanelProps: getInactivePanelProps(isTop),
1093
+ initialRadius,
1094
+ isComposable,
1095
+ panelContext,
1096
+ panelRef,
1097
+ panelStyle,
1098
+ resolvedSlideFrom,
1099
+ sideHandle
1100
+ };
1101
+ };
1102
+ const SheetPanel = (props) => {
1103
+ const { item, isTop, config, classNames, Content, shouldRender, renderHeader, prefersReducedMotion } = props;
1104
+ const { animateTarget, ariaProps, bottomHandle, handleAnimationComplete, headerProps, hoverProps, inactivePanelProps, initialRadius, isComposable, panelContext, panelRef, panelStyle, resolvedSlideFrom, sideHandle } = useSheetPanelModel(props);
1006
1105
  const panelContent = /* @__PURE__ */ jsxs(m.div, {
1007
1106
  animate: animateTarget,
1008
1107
  className: classNames.panel || void 0,
1009
1108
  exit: {
1010
1109
  ...resolvedSlideFrom,
1011
- opacity: .6,
1012
1110
  boxShadow: getShadow(false),
1111
+ opacity: .6,
1013
1112
  transition: {
1014
- type: "tween",
1113
+ boxShadow: VISUAL_TWEEN,
1015
1114
  duration: prefersReducedMotion ? 0 : .24,
1016
1115
  ease: "easeOut",
1017
- boxShadow: VISUAL_TWEEN
1116
+ type: "tween"
1018
1117
  }
1019
1118
  },
1020
1119
  initial: {
@@ -1024,25 +1123,15 @@ const SheetPanel = memo(function SheetPanel({ item, index, depth, isTop, isNeste
1024
1123
  boxShadow: getShadow(false)
1025
1124
  },
1026
1125
  onAnimationComplete: handleAnimationComplete,
1027
- onBlur: showSideHandle ? () => setIsHovered(false) : void 0,
1028
- onFocus: showSideHandle ? () => setIsHovered(true) : void 0,
1029
- onMouseEnter: showSideHandle ? () => setIsHovered(true) : void 0,
1030
- onMouseLeave: showSideHandle ? () => setIsHovered(false) : void 0,
1031
1126
  ref: panelRef,
1032
1127
  style: panelStyle,
1033
1128
  tabIndex: isTop ? -1 : void 0,
1034
- ...isTop ? {} : {
1035
- "aria-hidden": "true",
1036
- inert: true
1037
- },
1129
+ ...hoverProps,
1130
+ ...inactivePanelProps,
1038
1131
  ...ariaProps,
1039
- children: [showSideHandle && /* @__PURE__ */ jsx(SideHandle, {
1040
- isHovered,
1041
- onDismiss: isNested ? pop : close,
1042
- side
1043
- }), /* @__PURE__ */ jsxs("div", {
1132
+ children: [sideHandle, /* @__PURE__ */ jsxs("div", {
1044
1133
  className: "relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[inherit]",
1045
- children: [showBottomHandle && /* @__PURE__ */ jsx(BottomHandle, { onDismiss: isNested ? pop : close }), /* @__PURE__ */ jsx(PanelInnerContent, {
1134
+ children: [bottomHandle, /* @__PURE__ */ jsx(PanelInnerContent, {
1046
1135
  Content,
1047
1136
  data: item.data,
1048
1137
  headerClassName: classNames.header || void 0,
@@ -1062,30 +1151,54 @@ const SheetPanel = memo(function SheetPanel({ item, index, depth, isTop, isNeste
1062
1151
  children: panelContent
1063
1152
  })
1064
1153
  });
1065
- });
1154
+ };
1066
1155
  //#endregion
1067
1156
  //#region src/renderer.tsx
1068
- /**
1069
- * Root renderer component — manages the backdrop, scroll lock, snap points,
1070
- * close reasons, focus restoration, keyboard/CloseWatcher dismissal, and
1071
- * delegates per-panel rendering to `SheetPanel`.
1072
- *
1073
- * Mounted inside a Portal by `StacksheetProvider`.
1074
- */
1075
- function SheetRenderer({ store, config, sheets, componentMap, classNames: classNamesProp, layout, renderHeader }) {
1157
+ const handleBackdropExitComplete = () => {
1158
+ requestAnimationFrame(() => {
1159
+ document.body.offsetHeight;
1160
+ });
1161
+ };
1162
+ const getResolvedSnapHeights = (side, snapPoints, viewportHeight) => side === "bottom" && snapPoints.length > 0 ? resolveSnapPoints(snapPoints, viewportHeight) : [];
1163
+ const getActiveInternalSnapIndex = ({ defaultSnapIndex, internalSnap, snapContext, snapHeights }) => internalSnap.context === snapContext && internalSnap.snapCount === snapHeights.length ? internalSnap.index : defaultSnapIndex;
1164
+ const getMotionSpring = (prefersReducedMotion, config) => prefersReducedMotion ? {
1165
+ duration: 0,
1166
+ type: "tween"
1167
+ } : {
1168
+ damping: config.spring.damping,
1169
+ mass: config.spring.mass,
1170
+ stiffness: config.spring.stiffness,
1171
+ type: "spring"
1172
+ };
1173
+ const getBackdropStyle = (config, hasBackdropClass) => ({
1174
+ cursor: config.closeOnBackdrop && config.dismissible ? "pointer" : void 0,
1175
+ willChange: "opacity",
1176
+ zIndex: config.zIndex,
1177
+ ...hasBackdropClass ? {} : { background: "var(--overlay, rgba(0, 0, 0, 0.15))" }
1178
+ });
1179
+ const isContentComponent = (content) => typeof content === "function";
1180
+ const getStaticContent = (sheets, type) => {
1181
+ if (sheets === void 0) return;
1182
+ const content = Object.entries(sheets).find(([sheetType]) => sheetType === type)?.[1];
1183
+ return isContentComponent(content) ? content : void 0;
1184
+ };
1185
+ const useRendererStore = (store) => {
1076
1186
  const isOpen = useStore(store, (s) => s.isOpen);
1077
1187
  const stack = useStore(store, (s) => s.stack);
1078
- const rawClose = useStore(store, (s) => s.close);
1079
- const rawPop = useStore(store, (s) => s.pop);
1080
- const side = useResolvedSide(config);
1081
- const prefersReducedMotion = useReducedMotion() ?? false;
1082
- const classNames = useMemo(() => resolveClassNames(classNamesProp), [classNamesProp]);
1188
+ const { rawClose, rawPop } = useStore(store, useShallow((s) => ({
1189
+ rawClose: s.close,
1190
+ rawPop: s.pop
1191
+ })));
1192
+ return {
1193
+ isOpen,
1194
+ rawClose,
1195
+ rawPop,
1196
+ stack
1197
+ };
1198
+ };
1199
+ const useSnapState = (config, isOpen, side, stack) => {
1083
1200
  const viewportHeight = useViewportHeight(isOpen && side === "bottom" && config.snapPoints.length > 0);
1084
- const snapHeights = useMemo(() => side === "bottom" && config.snapPoints.length > 0 ? resolveSnapPoints(config.snapPoints, viewportHeight) : [], [
1085
- side,
1086
- config.snapPoints,
1087
- viewportHeight
1088
- ]);
1201
+ const snapHeights = getResolvedSnapHeights(side, config.snapPoints, viewportHeight);
1089
1202
  const snapContext = isOpen ? stack.map((item) => item.id).join("\0") : "";
1090
1203
  const defaultSnapIndex = snapHeights.length > 0 ? snapHeights.length - 1 : 0;
1091
1204
  const [internalSnap, setInternalSnap] = useState({
@@ -1093,32 +1206,50 @@ function SheetRenderer({ store, config, sheets, componentMap, classNames: classN
1093
1206
  index: defaultSnapIndex,
1094
1207
  snapCount: snapHeights.length
1095
1208
  });
1096
- const internalSnapIndex = internalSnap.context === snapContext && internalSnap.snapCount === snapHeights.length ? internalSnap.index : defaultSnapIndex;
1209
+ const internalSnapIndex = getActiveInternalSnapIndex({
1210
+ defaultSnapIndex,
1211
+ internalSnap,
1212
+ snapContext,
1213
+ snapHeights
1214
+ });
1097
1215
  const activeSnapIndex = config.snapPointIndex ?? internalSnapIndex;
1098
- const handleSnap = useCallback((index) => {
1216
+ const handleSnap = (index) => {
1099
1217
  setInternalSnap({
1100
1218
  context: snapContext,
1101
1219
  index,
1102
1220
  snapCount: snapHeights.length
1103
1221
  });
1104
1222
  config.onSnapPointChange?.(index);
1105
- }, [
1106
- config,
1107
- snapContext,
1108
- snapHeights.length
1109
- ]);
1223
+ };
1224
+ return {
1225
+ activeSnapIndex,
1226
+ handleSnap,
1227
+ snapHeights
1228
+ };
1229
+ };
1230
+ const useCloseControls = (rawClose, rawPop) => {
1110
1231
  const closeReasonRef = useRef("programmatic");
1111
- const closeWith = useCallback((reason) => {
1232
+ const closeWith = (reason) => {
1112
1233
  closeReasonRef.current = reason;
1113
1234
  rawClose();
1114
- }, [rawClose]);
1115
- const popWith = useCallback((reason) => {
1235
+ };
1236
+ const popWith = (reason) => {
1116
1237
  closeReasonRef.current = reason;
1117
1238
  rawPop();
1118
- }, [rawPop]);
1119
- const close = useCallback(() => closeWith("programmatic"), [closeWith]);
1120
- const pop = useCallback(() => popWith("programmatic"), [popWith]);
1121
- useBodyScale(config, isOpen, prefersReducedMotion);
1239
+ };
1240
+ return {
1241
+ close: () => {
1242
+ closeWith("programmatic");
1243
+ },
1244
+ closeReasonRef,
1245
+ closeWith,
1246
+ pop: () => {
1247
+ popWith("programmatic");
1248
+ },
1249
+ popWith
1250
+ };
1251
+ };
1252
+ const useFocusRestore = (isOpen) => {
1122
1253
  const triggerRef = useRef(null);
1123
1254
  const wasOpenRef = useRef(false);
1124
1255
  useEffect(() => {
@@ -1130,78 +1261,112 @@ function SheetRenderer({ store, config, sheets, componentMap, classNames: classN
1130
1261
  }
1131
1262
  wasOpenRef.current = isOpen;
1132
1263
  }, [isOpen]);
1133
- const stackLengthRef = useRef(stack.length);
1264
+ };
1265
+ const dismissFromEscape = ({ closeReasonRef, rawClose, rawPop, stackLengthRef }) => {
1266
+ closeReasonRef.current = "escape";
1267
+ if (stackLengthRef.current > 1) rawPop();
1268
+ else rawClose();
1269
+ };
1270
+ const useDismissalEffects = ({ closeReasonRef, config, isOpen, rawClose, rawPop, stackLength }) => {
1271
+ const stackLengthRef = useRef(stackLength);
1134
1272
  useEffect(() => {
1135
- stackLengthRef.current = stack.length;
1136
- }, [stack.length]);
1273
+ stackLengthRef.current = stackLength;
1274
+ }, [stackLength]);
1137
1275
  useEffect(() => {
1138
- if (!(isOpen && config.closeOnEscape && config.dismissible)) return;
1139
- function handleKeyDown(e) {
1276
+ const shouldListen = isOpen && config.closeOnEscape && config.dismissible;
1277
+ const handleKeyDown = (e) => {
1140
1278
  if (e.key === "Escape") {
1141
1279
  e.preventDefault();
1142
- if (stackLengthRef.current > 1) popWith("escape");
1143
- else closeWith("escape");
1280
+ dismissFromEscape({
1281
+ closeReasonRef,
1282
+ rawClose,
1283
+ rawPop,
1284
+ stackLengthRef
1285
+ });
1144
1286
  }
1145
- }
1146
- document.addEventListener("keydown", handleKeyDown);
1147
- return () => document.removeEventListener("keydown", handleKeyDown);
1287
+ };
1288
+ if (shouldListen) document.addEventListener("keydown", handleKeyDown);
1289
+ return () => {
1290
+ if (shouldListen) document.removeEventListener("keydown", handleKeyDown);
1291
+ };
1148
1292
  }, [
1149
1293
  isOpen,
1150
1294
  config.closeOnEscape,
1151
1295
  config.dismissible,
1152
- popWith,
1153
- closeWith
1296
+ rawPop,
1297
+ rawClose,
1298
+ closeReasonRef
1154
1299
  ]);
1155
1300
  useEffect(() => {
1156
- if (!(isOpen && config.dismissible) || typeof globalThis.CloseWatcher === "undefined") return;
1157
- const watcher = new globalThis.CloseWatcher();
1158
- watcher.onclose = () => {
1159
- if (stackLengthRef.current > 1) popWith("escape");
1160
- else closeWith("escape");
1301
+ const CloseWatcherConstructor = globalThis.CloseWatcher;
1302
+ const shouldListen = isOpen && config.dismissible && CloseWatcherConstructor !== void 0;
1303
+ let watcher;
1304
+ const handleClose = () => {
1305
+ dismissFromEscape({
1306
+ closeReasonRef,
1307
+ rawClose,
1308
+ rawPop,
1309
+ stackLengthRef
1310
+ });
1311
+ };
1312
+ if (shouldListen) {
1313
+ watcher = new CloseWatcherConstructor();
1314
+ watcher.addEventListener("close", handleClose);
1315
+ }
1316
+ return () => {
1317
+ if (watcher !== void 0) {
1318
+ watcher.removeEventListener("close", handleClose);
1319
+ watcher.destroy();
1320
+ }
1161
1321
  };
1162
- return () => watcher.destroy();
1163
1322
  }, [
1164
1323
  isOpen,
1165
1324
  config.dismissible,
1166
- popWith,
1167
- closeWith
1168
- ]);
1169
- const slideFrom = useMemo(() => getSlideFrom(side), [side]);
1170
- const slideTarget = useMemo(() => getSlideTarget(), []);
1171
- const spring = useMemo(() => prefersReducedMotion ? {
1172
- type: "tween",
1173
- duration: 0
1174
- } : {
1175
- type: "spring",
1176
- damping: config.spring.damping,
1177
- stiffness: config.spring.stiffness,
1178
- mass: config.spring.mass
1179
- }, [
1180
- prefersReducedMotion,
1181
- config.spring.damping,
1182
- config.spring.stiffness,
1183
- config.spring.mass
1325
+ rawPop,
1326
+ rawClose,
1327
+ closeReasonRef
1184
1328
  ]);
1329
+ };
1330
+ /**
1331
+ * Root renderer component — manages the backdrop, scroll lock, snap points,
1332
+ * close reasons, focus restoration, keyboard/CloseWatcher dismissal, and
1333
+ * delegates per-panel rendering to `SheetPanel`.
1334
+ *
1335
+ * Mounted inside a Portal by `StacksheetProvider`.
1336
+ */
1337
+ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classNamesProp, layout, renderHeader }) => {
1338
+ const { isOpen, rawClose, rawPop, stack } = useRendererStore(store);
1339
+ const side = useResolvedSide(config);
1340
+ const prefersReducedMotion = useReducedMotion() ?? false;
1341
+ const classNames = resolveClassNames(classNamesProp);
1342
+ const { activeSnapIndex, handleSnap, snapHeights } = useSnapState(config, isOpen, side, stack);
1343
+ const { close, closeReasonRef, closeWith, pop, popWith } = useCloseControls(rawClose, rawPop);
1344
+ useBodyScale(config, isOpen, prefersReducedMotion);
1345
+ useFocusRestore(isOpen);
1346
+ useDismissalEffects({
1347
+ closeReasonRef,
1348
+ config,
1349
+ isOpen,
1350
+ rawClose,
1351
+ rawPop,
1352
+ stackLength: stack.length
1353
+ });
1354
+ const slideFrom = getSlideFrom(side);
1355
+ const slideTarget = getSlideTarget();
1356
+ const spring = getMotionSpring(prefersReducedMotion, config);
1185
1357
  const stackSpring = spring;
1186
1358
  const isModal = config.modal;
1187
1359
  const showOverlay = isModal && config.showOverlay;
1188
- const hasBackdropClass = classNames.backdrop !== "";
1189
- const backdropStyle = {
1190
- zIndex: config.zIndex,
1191
- willChange: "opacity",
1192
- cursor: config.closeOnBackdrop && config.dismissible ? "pointer" : void 0,
1193
- ...hasBackdropClass ? {} : { background: "var(--overlay, rgba(0, 0, 0, 0.15))" }
1194
- };
1195
- const handleExitComplete = useCallback(() => {
1360
+ const backdropStyle = getBackdropStyle(config, classNames.backdrop !== "");
1361
+ const handleExitComplete = () => {
1196
1362
  if (stack.length === 0) config.onCloseComplete?.(closeReasonRef.current);
1197
- }, [stack.length, config]);
1198
- const handleBackdropExitComplete = useCallback(() => {
1199
- requestAnimationFrame(() => {
1200
- document.body.offsetHeight;
1201
- });
1202
- }, []);
1203
- const swipeClose = useCallback(() => closeWith("swipe"), [closeWith]);
1204
- const swipePop = useCallback(() => popWith("swipe"), [popWith]);
1363
+ };
1364
+ const swipeClose = () => {
1365
+ closeWith("swipe");
1366
+ };
1367
+ const swipePop = () => {
1368
+ popWith("swipe");
1369
+ };
1205
1370
  const shouldLockScroll = isOpen && isModal && config.lockScroll;
1206
1371
  return /* @__PURE__ */ jsxs(LazyMotion, {
1207
1372
  features: domMax,
@@ -1212,7 +1377,9 @@ function SheetRenderer({ store, config, sheets, componentMap, classNames: classN
1212
1377
  className: `fixed inset-0 ${classNames.backdrop || ""}`,
1213
1378
  exit: { opacity: 0 },
1214
1379
  initial: { opacity: 0 },
1215
- onClick: config.closeOnBackdrop && config.dismissible ? () => closeWith("backdrop") : void 0,
1380
+ onClick: config.closeOnBackdrop && config.dismissible ? () => {
1381
+ closeWith("backdrop");
1382
+ } : void 0,
1216
1383
  style: backdropStyle,
1217
1384
  transition: spring
1218
1385
  }, "stacksheet-backdrop")
@@ -1231,7 +1398,7 @@ function SheetRenderer({ store, config, sheets, componentMap, classNames: classN
1231
1398
  const shouldRender = depth <= config.stacking.renderThreshold;
1232
1399
  return /* @__PURE__ */ jsx(SheetPanel, {
1233
1400
  activeSnapIndex,
1234
- Content: componentMap.get(item.type) ?? sheets[item.type],
1401
+ Content: componentMap.get(item.type) ?? getStaticContent(sheets, item.type),
1235
1402
  classNames,
1236
1403
  close,
1237
1404
  config,
@@ -1260,67 +1427,229 @@ function SheetRenderer({ store, config, sheets, componentMap, classNames: classN
1260
1427
  })
1261
1428
  })]
1262
1429
  });
1263
- }
1430
+ };
1264
1431
  //#endregion
1265
- //#region src/Store/StoreArgs.ts
1266
- function resolvePresentationOptions(value) {
1267
- if (!(value && typeof value === "object")) return;
1268
- const candidate = value;
1269
- if (candidate.ariaLabel !== void 0 && typeof candidate.ariaLabel !== "string") return;
1270
- return candidate;
1271
- }
1432
+ //#region src/Store/store-args.ts
1433
+ const isRecord = (value) => typeof value === "object" && value !== null;
1434
+ const toRecord = (value) => isRecord(value) ? value : {};
1435
+ const isComponent = (value) => typeof value === "function";
1436
+ const getComponentName = (component) => {
1437
+ const name = component.displayName ?? component.name;
1438
+ return name === "" ? void 0 : name;
1439
+ };
1440
+ const getNodeEnv = () => globalThis.process?.env?.NODE_ENV;
1441
+ const getStringArg = (value, name) => {
1442
+ if (typeof value !== "string") throw new TypeError(`Expected ${name} to be a string.`);
1443
+ return value;
1444
+ };
1445
+ const resolvePresentationOptions = (value) => {
1446
+ if (!isRecord(value)) return;
1447
+ const { ariaLabel } = value;
1448
+ if (ariaLabel !== void 0 && typeof ariaLabel !== "string") return;
1449
+ return ariaLabel === void 0 ? {} : { ariaLabel };
1450
+ };
1272
1451
  /**
1273
1452
  * Dev-mode warning: detect likely inline arrow functions passed as ad-hoc components.
1274
1453
  * When a new component reference has the same displayName/name as an existing one,
1275
1454
  * it's almost always an inline arrow being re-created every render.
1276
1455
  */
1277
- function warnInlineComponent(component, componentRegistry, warnedNames) {
1278
- if (typeof process === "undefined" || process?.env?.NODE_ENV === "production") return;
1279
- const name = component.displayName || component.name;
1280
- if (!name) return;
1456
+ const warnInlineComponent = (component, componentRegistry, warnedNames) => {
1457
+ if (getNodeEnv() === "production") return;
1458
+ const name = getComponentName(component);
1459
+ if (name === void 0) return;
1281
1460
  if (warnedNames.has(name)) return;
1282
- for (const [existing, key] of componentRegistry) if ((existing.displayName || existing.name) === name) {
1461
+ for (const [existing, key] of componentRegistry) if (getComponentName(existing) === name) {
1283
1462
  warnedNames.add(name);
1284
1463
  console.warn(`[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.`);
1285
1464
  return;
1286
1465
  }
1287
- }
1466
+ };
1467
+ const registerComponent = (component, componentRegistry, componentMap, getNextKey, warnedNames) => {
1468
+ const existingKey = componentRegistry.get(component);
1469
+ if (existingKey !== void 0) return existingKey;
1470
+ warnInlineComponent(component, componentRegistry, warnedNames);
1471
+ const nextKey = getNextKey();
1472
+ componentRegistry.set(component, nextKey);
1473
+ componentMap.set(nextKey, component);
1474
+ return nextKey;
1475
+ };
1288
1476
  /**
1289
1477
  * If `first` is a function (component), register it and return { type, id, data }.
1290
1478
  * Otherwise, pass through the string-based (type, id, data) args unchanged.
1291
1479
  */
1292
- function resolveArgs(componentRegistry, componentMap, getNextKey, warnedNames, first, second, third, fourth) {
1293
- if (typeof first === "function") {
1294
- const component = first;
1295
- let typeKey = componentRegistry.get(component);
1296
- if (!typeKey) {
1297
- warnInlineComponent(component, componentRegistry, warnedNames);
1298
- typeKey = getNextKey();
1299
- componentRegistry.set(component, typeKey);
1300
- componentMap.set(typeKey, component);
1301
- }
1480
+ const resolveArgs = (componentRegistry, componentMap, getNextKey, warnedNames, first, second, third, fourth) => {
1481
+ if (isComponent(first)) {
1482
+ const typeKey = registerComponent(first, componentRegistry, componentMap, getNextKey, warnedNames);
1302
1483
  if (typeof second === "string") return {
1303
1484
  ariaLabel: resolvePresentationOptions(fourth)?.ariaLabel,
1304
- type: typeKey,
1485
+ data: toRecord(third),
1305
1486
  id: second,
1306
- data: third ?? {}
1487
+ type: typeKey
1307
1488
  };
1308
1489
  return {
1309
1490
  ariaLabel: resolvePresentationOptions(third)?.ariaLabel,
1310
- type: typeKey,
1491
+ data: toRecord(second),
1311
1492
  id: crypto.randomUUID(),
1312
- data: second ?? {}
1493
+ type: typeKey
1313
1494
  };
1314
1495
  }
1315
1496
  return {
1316
1497
  ariaLabel: resolvePresentationOptions(fourth)?.ariaLabel,
1317
- type: first,
1318
- id: second,
1319
- data: third ?? {}
1498
+ data: toRecord(third),
1499
+ id: getStringArg(second, "sheet id"),
1500
+ type: getStringArg(first, "sheet type")
1320
1501
  };
1321
- }
1502
+ };
1322
1503
  //#endregion
1323
- //#region src/Store/createSheetStore.ts
1504
+ //#region src/Store/create-sheet-store.ts
1505
+ const createItem = ({ type, id, data, ariaLabel }) => ({
1506
+ ariaLabel,
1507
+ data,
1508
+ id,
1509
+ type
1510
+ });
1511
+ const createResolvedWriters = (set, config) => ({
1512
+ openResolved: (resolved) => {
1513
+ set({
1514
+ isOpen: true,
1515
+ stack: [createItem(resolved)]
1516
+ });
1517
+ },
1518
+ pushResolved: (resolved) => {
1519
+ set((state) => {
1520
+ const item = createItem(resolved);
1521
+ if (Number.isFinite(config.maxDepth) && state.stack.length >= config.maxDepth) return {
1522
+ isOpen: true,
1523
+ stack: [...state.stack.slice(0, -1), item]
1524
+ };
1525
+ return {
1526
+ isOpen: true,
1527
+ stack: [...state.stack, item]
1528
+ };
1529
+ });
1530
+ },
1531
+ replaceResolved: (resolved) => {
1532
+ set((state) => {
1533
+ const item = createItem(resolved);
1534
+ if (state.stack.length === 0) return {
1535
+ isOpen: true,
1536
+ stack: [item]
1537
+ };
1538
+ return {
1539
+ isOpen: true,
1540
+ stack: [...state.stack.slice(0, -1), item]
1541
+ };
1542
+ });
1543
+ }
1544
+ });
1545
+ const createNavigate = (componentMap, get, resolve, writers) => (first, second, third, fourth) => {
1546
+ const resolved = resolve(first, second, third, fourth);
1547
+ const { stack } = get();
1548
+ const top = stack.at(-1);
1549
+ if (stack.length === 0) {
1550
+ writers.openResolved(resolved);
1551
+ return;
1552
+ }
1553
+ let isSameType = top?.type === resolved.type;
1554
+ if (!isSameType && isComponent(first)) isSameType = componentMap.get(top?.type ?? "") === first;
1555
+ if (isSameType) {
1556
+ writers.replaceResolved(resolved);
1557
+ return;
1558
+ }
1559
+ writers.pushResolved(resolved);
1560
+ };
1561
+ const createPop = (set, pruneRegistry) => () => {
1562
+ set((state) => {
1563
+ if (state.stack.length <= 1) {
1564
+ pruneRegistry([]);
1565
+ return {
1566
+ isOpen: false,
1567
+ stack: []
1568
+ };
1569
+ }
1570
+ const next = state.stack.slice(0, -1);
1571
+ pruneRegistry(next);
1572
+ return {
1573
+ isOpen: true,
1574
+ stack: next
1575
+ };
1576
+ });
1577
+ };
1578
+ const createRemove = (set, pruneRegistry) => (id) => {
1579
+ set((state) => {
1580
+ const next = state.stack.filter((item) => item.id !== id);
1581
+ if (next.length === state.stack.length) return state;
1582
+ pruneRegistry(next);
1583
+ return next.length === 0 ? {
1584
+ isOpen: false,
1585
+ stack: []
1586
+ } : { stack: next };
1587
+ });
1588
+ };
1589
+ const createSetData = (set, resolve) => (first, second, third) => {
1590
+ const { id, data } = resolve(first, second, third);
1591
+ set((state) => {
1592
+ const index = state.stack.findIndex((item) => item.id === id);
1593
+ const existing = state.stack[index];
1594
+ if (existing === void 0) return state;
1595
+ const updated = [...state.stack];
1596
+ updated[index] = {
1597
+ ...existing,
1598
+ data
1599
+ };
1600
+ return { stack: updated };
1601
+ });
1602
+ };
1603
+ const createSwap = (set, internals) => (first, second, third) => {
1604
+ const type = isComponent(first) ? registerComponent(first, internals.componentRegistry, internals.componentMap, internals.getNextKey, internals.warnedNames) : getStringArg(first, "sheet type");
1605
+ const data = toRecord(second);
1606
+ const ariaLabel = resolvePresentationOptions(third)?.ariaLabel;
1607
+ set((state) => {
1608
+ const top = state.stack.at(-1);
1609
+ if (top === void 0) return state;
1610
+ const newStack = [...state.stack];
1611
+ newStack[newStack.length - 1] = {
1612
+ ariaLabel: ariaLabel ?? top.ariaLabel,
1613
+ data,
1614
+ id: top.id,
1615
+ type
1616
+ };
1617
+ return { stack: newStack };
1618
+ });
1619
+ };
1620
+ const createStoreState = ({ componentMap, componentRegistry, config, getNextKey, pruneRegistry, resolve, warnedNames }) => (set, get) => {
1621
+ const writers = createResolvedWriters(set, config);
1622
+ return {
1623
+ close: () => {
1624
+ pruneRegistry([]);
1625
+ set({
1626
+ isOpen: false,
1627
+ stack: []
1628
+ });
1629
+ },
1630
+ isOpen: false,
1631
+ navigate: createNavigate(componentMap, get, resolve, writers),
1632
+ open: (first, second, third, fourth) => {
1633
+ writers.openResolved(resolve(first, second, third, fourth));
1634
+ },
1635
+ pop: createPop(set, pruneRegistry),
1636
+ push: (first, second, third, fourth) => {
1637
+ writers.pushResolved(resolve(first, second, third, fourth));
1638
+ },
1639
+ remove: createRemove(set, pruneRegistry),
1640
+ replace: (first, second, third, fourth) => {
1641
+ writers.replaceResolved(resolve(first, second, third, fourth));
1642
+ },
1643
+ setData: createSetData(set, resolve),
1644
+ stack: [],
1645
+ swap: createSwap(set, {
1646
+ componentMap,
1647
+ componentRegistry,
1648
+ getNextKey,
1649
+ warnedNames
1650
+ })
1651
+ };
1652
+ };
1324
1653
  /**
1325
1654
  * Create an isolated Zustand store for a sheet stack instance.
1326
1655
  *
@@ -1332,189 +1661,39 @@ function resolveArgs(componentRegistry, componentMap, getNextKey, warnedNames, f
1332
1661
  * The ad-hoc counter is scoped per instance to prevent identity leaks across
1333
1662
  * multiple `createStacksheet()` calls or test runs.
1334
1663
  */
1335
- function createSheetStore(config) {
1664
+ const createSheetStore = (config) => {
1336
1665
  const componentRegistry = /* @__PURE__ */ new Map();
1337
1666
  const componentMap = /* @__PURE__ */ new Map();
1338
1667
  let adhocCounter = 0;
1339
- function getNextKey() {
1340
- return `__adhoc_${adhocCounter++}`;
1341
- }
1668
+ const getNextKey = () => {
1669
+ const key = `__adhoc_${adhocCounter}`;
1670
+ adhocCounter += 1;
1671
+ return key;
1672
+ };
1342
1673
  const warnedNames = /* @__PURE__ */ new Set();
1343
- function resolve(first, second, third, fourth) {
1344
- return resolveArgs(componentRegistry, componentMap, getNextKey, warnedNames, first, second, third, fourth);
1345
- }
1674
+ const resolve = (first, second, third, fourth) => resolveArgs(componentRegistry, componentMap, getNextKey, warnedNames, first, second, third, fourth);
1346
1675
  /** Remove registry entries for type keys no longer in the stack */
1347
- function pruneRegistry(remainingStack) {
1676
+ const pruneRegistry = (remainingStack) => {
1348
1677
  const usedTypes = new Set(remainingStack.map((item) => item.type));
1349
1678
  for (const [component, typeKey] of componentRegistry) if (!usedTypes.has(typeKey)) {
1350
1679
  componentRegistry.delete(component);
1351
1680
  componentMap.delete(typeKey);
1352
1681
  }
1353
- }
1682
+ };
1354
1683
  return {
1355
- store: createStore()((set, get) => {
1356
- function _openResolved({ type, id, data, ariaLabel }) {
1357
- set({
1358
- stack: [{
1359
- id,
1360
- type,
1361
- data,
1362
- ariaLabel
1363
- }],
1364
- isOpen: true
1365
- });
1366
- }
1367
- function _pushResolved({ type, id, data, ariaLabel }) {
1368
- set((state) => {
1369
- const item = {
1370
- id,
1371
- type,
1372
- data,
1373
- ariaLabel
1374
- };
1375
- if (Number.isFinite(config.maxDepth) && state.stack.length >= config.maxDepth) return {
1376
- stack: [...state.stack.slice(0, -1), item],
1377
- isOpen: true
1378
- };
1379
- return {
1380
- stack: [...state.stack, item],
1381
- isOpen: true
1382
- };
1383
- });
1384
- }
1385
- function _replaceResolved({ type, id, data, ariaLabel }) {
1386
- set((state) => {
1387
- const item = {
1388
- id,
1389
- type,
1390
- data,
1391
- ariaLabel
1392
- };
1393
- if (state.stack.length === 0) return {
1394
- stack: [item],
1395
- isOpen: true
1396
- };
1397
- return {
1398
- stack: [...state.stack.slice(0, -1), item],
1399
- isOpen: true
1400
- };
1401
- });
1402
- }
1403
- return {
1404
- stack: [],
1405
- isOpen: false,
1406
- open(first, second, third, fourth) {
1407
- _openResolved(resolve(first, second, third, fourth));
1408
- },
1409
- push(first, second, third, fourth) {
1410
- _pushResolved(resolve(first, second, third, fourth));
1411
- },
1412
- replace(first, second, third, fourth) {
1413
- _replaceResolved(resolve(first, second, third, fourth));
1414
- },
1415
- swap(first, second, third) {
1416
- let type;
1417
- let data;
1418
- const ariaLabel = resolvePresentationOptions(third)?.ariaLabel;
1419
- if (typeof first === "function") {
1420
- const component = first;
1421
- let typeKey = componentRegistry.get(component);
1422
- if (!typeKey) {
1423
- warnInlineComponent(component, componentRegistry, warnedNames);
1424
- typeKey = getNextKey();
1425
- componentRegistry.set(component, typeKey);
1426
- componentMap.set(typeKey, component);
1427
- }
1428
- type = typeKey;
1429
- data = second ?? {};
1430
- } else {
1431
- type = first;
1432
- data = second ?? {};
1433
- }
1434
- set((state) => {
1435
- const top = state.stack.at(-1);
1436
- if (!top) return state;
1437
- const newStack = [...state.stack];
1438
- newStack[newStack.length - 1] = {
1439
- id: top.id,
1440
- type,
1441
- data,
1442
- ariaLabel: ariaLabel ?? top.ariaLabel
1443
- };
1444
- return { stack: newStack };
1445
- });
1446
- },
1447
- navigate(first, second, third, fourth) {
1448
- const resolved = resolve(first, second, third, fourth);
1449
- const { stack } = get();
1450
- const top = stack.at(-1);
1451
- if (stack.length === 0) {
1452
- _openResolved(resolved);
1453
- return;
1454
- }
1455
- let isSameType = top?.type === resolved.type;
1456
- if (!isSameType && typeof first === "function") isSameType = componentMap.get(top?.type ?? "") === first;
1457
- if (isSameType) {
1458
- _replaceResolved(resolved);
1459
- return;
1460
- }
1461
- _pushResolved(resolved);
1462
- },
1463
- setData(first, second, third) {
1464
- const { id, data } = resolve(first, second, third);
1465
- set((state) => {
1466
- const idx = state.stack.findIndex((item) => item.id === id);
1467
- if (idx === -1) return state;
1468
- const updated = [...state.stack];
1469
- updated[idx] = {
1470
- ...updated[idx],
1471
- data
1472
- };
1473
- return { stack: updated };
1474
- });
1475
- },
1476
- remove(id) {
1477
- set((state) => {
1478
- const next = state.stack.filter((item) => item.id !== id);
1479
- if (next.length === state.stack.length) return state;
1480
- pruneRegistry(next);
1481
- if (next.length === 0) return {
1482
- stack: [],
1483
- isOpen: false
1484
- };
1485
- return { stack: next };
1486
- });
1487
- },
1488
- pop() {
1489
- set((state) => {
1490
- if (state.stack.length <= 1) {
1491
- pruneRegistry([]);
1492
- return {
1493
- stack: [],
1494
- isOpen: false
1495
- };
1496
- }
1497
- const next = state.stack.slice(0, -1);
1498
- pruneRegistry(next);
1499
- return {
1500
- stack: next,
1501
- isOpen: true
1502
- };
1503
- });
1504
- },
1505
- close() {
1506
- pruneRegistry([]);
1507
- set({
1508
- stack: [],
1509
- isOpen: false
1510
- });
1511
- }
1512
- };
1513
- }),
1684
+ componentMap,
1514
1685
  componentRegistry,
1515
- componentMap
1686
+ store: createStore()(createStoreState({
1687
+ componentMap,
1688
+ componentRegistry,
1689
+ config,
1690
+ getNextKey,
1691
+ pruneRegistry,
1692
+ resolve,
1693
+ warnedNames
1694
+ }))
1516
1695
  };
1517
- }
1696
+ };
1518
1697
  //#endregion
1519
1698
  //#region src/create.tsx
1520
1699
  /**
@@ -1547,86 +1726,82 @@ function createSheetStore(config) {
1547
1726
  *
1548
1727
  * Use `useSheetPanel()` inside content components to access `close()` and `back()`.
1549
1728
  */
1550
- function createStacksheet(config) {
1729
+ const createStacksheet = (config) => {
1551
1730
  const resolved = resolveConfig(config);
1552
1731
  const { store, componentMap } = createSheetStore(resolved);
1553
1732
  const StoreContext = createContext(null);
1554
- function useStoreContext() {
1555
- const ctx = useContext(StoreContext);
1733
+ const useStoreContext = () => {
1734
+ const ctx = use(StoreContext);
1556
1735
  if (!ctx) throw new Error("useSheet/useStacksheetState must be used within <StacksheetProvider>");
1557
1736
  return ctx;
1558
- }
1559
- const EMPTY_SHEETS = {};
1560
- function StacksheetProvider({ sheets = EMPTY_SHEETS, children, classNames, layout, renderHeader }) {
1561
- const value = useMemo(() => ({
1562
- store,
1563
- config: resolved
1564
- }), []);
1565
- return /* @__PURE__ */ jsxs(StoreContext.Provider, {
1566
- value,
1567
- children: [children, /* @__PURE__ */ jsx(Portal, {
1568
- asChild: false,
1569
- children: /* @__PURE__ */ jsx(SheetRenderer, {
1570
- classNames,
1571
- componentMap,
1572
- config: resolved,
1573
- layout,
1574
- renderHeader,
1575
- sheets,
1576
- store
1577
- })
1578
- })]
1579
- });
1580
- }
1581
- function useSheet() {
1737
+ };
1738
+ const providerValue = {
1739
+ config: resolved,
1740
+ store
1741
+ };
1742
+ const StacksheetProvider = ({ sheets, children, classNames, layout, renderHeader }) => /* @__PURE__ */ jsxs(StoreContext.Provider, {
1743
+ value: providerValue,
1744
+ children: [children, /* @__PURE__ */ jsx(Portal, {
1745
+ asChild: false,
1746
+ children: /* @__PURE__ */ jsx(SheetRenderer, {
1747
+ classNames,
1748
+ componentMap,
1749
+ config: resolved,
1750
+ layout,
1751
+ renderHeader,
1752
+ sheets,
1753
+ store
1754
+ })
1755
+ })]
1756
+ });
1757
+ const useSheet = () => {
1582
1758
  const { store: s } = useStoreContext();
1583
- return useMemo(() => {
1584
- const state = s.getState();
1585
- return {
1586
- open: state.open,
1587
- push: state.push,
1588
- replace: state.replace,
1589
- swap: state.swap,
1590
- navigate: state.navigate,
1591
- setData: state.setData,
1592
- remove: state.remove,
1593
- pop: state.pop,
1594
- close: state.close
1595
- };
1596
- }, [s]);
1597
- }
1598
- function useStacksheetState() {
1759
+ const state = s.getState();
1760
+ return {
1761
+ close: state.close,
1762
+ navigate: state.navigate,
1763
+ open: state.open,
1764
+ pop: state.pop,
1765
+ push: state.push,
1766
+ remove: state.remove,
1767
+ replace: state.replace,
1768
+ setData: state.setData,
1769
+ swap: state.swap
1770
+ };
1771
+ };
1772
+ const useStacksheetState = () => {
1599
1773
  const { store: s } = useStoreContext();
1600
1774
  return useStore(s, useShallow((state) => ({
1601
- stack: state.stack,
1602
- isOpen: state.isOpen
1775
+ isOpen: state.isOpen,
1776
+ stack: state.stack
1603
1777
  })));
1604
- }
1778
+ };
1605
1779
  return {
1606
1780
  StacksheetProvider,
1781
+ store,
1607
1782
  useSheet,
1608
- useStacksheetState,
1609
- store
1783
+ useStacksheetState
1610
1784
  };
1611
- }
1785
+ };
1612
1786
  //#endregion
1613
- //#region src/SheetParts/SheetBack.tsx
1614
- function SheetBack({ asChild, className, style, children }) {
1787
+ //#region src/SheetParts/sheet-back.tsx
1788
+ const SheetBack = ({ asChild, className, style, children }) => {
1615
1789
  const { back, isNested } = useSheetPanel();
1616
1790
  if (!isNested) return null;
1617
- return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
1618
- "aria-label": children ? void 0 : "Back",
1619
- className: joinClassNames(asChild ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
1791
+ const isAsChild = asChild === true;
1792
+ return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
1793
+ "aria-label": children === void 0 || children === null ? "Back" : void 0,
1794
+ className: joinClassNames(isAsChild ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
1620
1795
  onClick: back,
1621
1796
  style,
1622
- type: asChild ? void 0 : "button",
1797
+ type: isAsChild ? void 0 : "button",
1623
1798
  children: children ?? /* @__PURE__ */ jsx(ArrowLeftIcon, {})
1624
1799
  });
1625
- }
1800
+ };
1626
1801
  //#endregion
1627
- //#region src/SheetParts/SheetBody.tsx
1628
- function SheetBody({ asChild, className, style, children }) {
1629
- if (asChild) return /* @__PURE__ */ jsx(Slot, {
1802
+ //#region src/SheetParts/sheet-body.tsx
1803
+ const SheetBody = ({ asChild, className, style, children }) => {
1804
+ if (asChild === true) return /* @__PURE__ */ jsx(Slot, {
1630
1805
  className: joinClassNames("relative min-h-0 flex-1", className),
1631
1806
  "data-stacksheet-no-drag": "",
1632
1807
  style,
@@ -1645,88 +1820,86 @@ function SheetBody({ asChild, className, style, children }) {
1645
1820
  children: /* @__PURE__ */ jsx(Thumb, { className: "relative flex-1 rounded bg-current/15" })
1646
1821
  })]
1647
1822
  });
1648
- }
1823
+ };
1649
1824
  //#endregion
1650
- //#region src/SheetParts/SheetClose.tsx
1651
- function SheetClose({ asChild, className, style, children }) {
1825
+ //#region src/SheetParts/sheet-close.tsx
1826
+ const SheetClose = ({ asChild, className, style, children }) => {
1652
1827
  const { close } = useSheetPanel();
1653
- return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
1654
- "aria-label": children ? void 0 : "Close",
1655
- className: joinClassNames(asChild ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
1828
+ const isAsChild = asChild === true;
1829
+ return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
1830
+ "aria-label": children === void 0 || children === null ? "Close" : void 0,
1831
+ className: joinClassNames(isAsChild ? void 0 : "flex min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-inherit opacity-60 transition-opacity duration-150 hover:opacity-100", className),
1656
1832
  onClick: close,
1657
1833
  style,
1658
- type: asChild ? void 0 : "button",
1834
+ type: isAsChild ? void 0 : "button",
1659
1835
  children: children ?? /* @__PURE__ */ jsx(XIcon, {})
1660
1836
  });
1661
- }
1837
+ };
1662
1838
  //#endregion
1663
- //#region src/SheetParts/SheetDescription.tsx
1664
- function SheetDescription({ asChild, className, style, children }) {
1839
+ //#region src/SheetParts/sheet-description.tsx
1840
+ const SheetDescription = ({ asChild, className, style, children }) => {
1665
1841
  const { panelId, registerDescription } = useSheetPanel();
1666
1842
  useEffect(() => registerDescription(), [registerDescription]);
1667
- return /* @__PURE__ */ jsx(asChild ? Slot : "p", {
1843
+ return /* @__PURE__ */ jsx(asChild === true ? Slot : "p", {
1668
1844
  className,
1669
1845
  id: `${panelId}-desc`,
1670
1846
  style,
1671
1847
  children
1672
1848
  });
1673
- }
1849
+ };
1674
1850
  //#endregion
1675
- //#region src/SheetParts/SheetFooter.tsx
1676
- function SheetFooter({ asChild, className, style, children }) {
1677
- return /* @__PURE__ */ jsx(asChild ? Slot : "footer", {
1678
- className: joinClassNames(asChild ? "shrink-0" : "flex shrink-0 items-center gap-2 border-t px-6 py-3", className),
1851
+ //#region src/SheetParts/sheet-footer.tsx
1852
+ const SheetFooter = ({ asChild, className, style, children }) => {
1853
+ const isAsChild = asChild === true;
1854
+ return /* @__PURE__ */ jsx(isAsChild ? Slot : "footer", {
1855
+ className: joinClassNames(isAsChild ? "shrink-0" : "flex shrink-0 items-center gap-2 border-t px-6 py-3", className),
1679
1856
  style,
1680
1857
  children
1681
1858
  });
1682
- }
1859
+ };
1683
1860
  //#endregion
1684
- //#region src/SheetParts/SheetHandle.tsx
1685
- function SheetHandle({ asChild, className, style, children }) {
1861
+ //#region src/SheetParts/sheet-handle.tsx
1862
+ const SheetHandle = ({ asChild, className, style, children }) => {
1686
1863
  const { close, back, isNested } = useSheetPanel();
1687
1864
  const dismiss = isNested ? back : close;
1688
- return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
1865
+ const isAsChild = asChild === true;
1866
+ return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
1689
1867
  "aria-label": "Dismiss",
1690
- className: joinClassNames(asChild ? void 0 : "flex shrink-0 cursor-grab touch-none items-center justify-center pt-4 pb-1", className),
1868
+ className: joinClassNames(isAsChild ? void 0 : "flex shrink-0 cursor-grab touch-none items-center justify-center border-none bg-transparent pt-4 pb-1 text-inherit", className),
1691
1869
  "data-stacksheet-handle": "",
1692
1870
  onClick: dismiss,
1693
- onKeyDown: (e) => {
1694
- if (e.key === "Enter" || e.key === " ") {
1695
- e.preventDefault();
1696
- dismiss();
1697
- }
1698
- },
1699
- role: "button",
1700
1871
  style,
1701
- tabIndex: 0,
1872
+ type: isAsChild ? void 0 : "button",
1702
1873
  children: children ?? /* @__PURE__ */ jsx("div", {
1703
1874
  "aria-hidden": "true",
1704
1875
  className: "h-1 w-9 rounded-sm bg-current/25"
1705
1876
  })
1706
1877
  });
1707
- }
1878
+ };
1708
1879
  //#endregion
1709
- //#region src/SheetParts/SheetHeader.tsx
1710
- function SheetHeader({ asChild, className, style, children }) {
1711
- return /* @__PURE__ */ jsx(asChild ? Slot : "header", {
1712
- className: joinClassNames(asChild ? "shrink-0" : "flex h-14 shrink-0 items-center justify-between border-b px-6", className),
1880
+ //#region src/SheetParts/sheet-header.tsx
1881
+ const SheetHeader = ({ asChild, className, style, children }) => {
1882
+ const isAsChild = asChild === true;
1883
+ return /* @__PURE__ */ jsx(isAsChild ? Slot : "header", {
1884
+ className: joinClassNames(isAsChild ? "shrink-0" : "flex h-14 shrink-0 items-center justify-between border-b px-6", className),
1713
1885
  style,
1714
1886
  children
1715
1887
  });
1716
- }
1888
+ };
1717
1889
  //#endregion
1718
- //#region src/SheetParts/SheetTitle.tsx
1719
- function SheetTitle({ asChild, className, style, children }) {
1890
+ //#region src/SheetParts/sheet-title.tsx
1891
+ const SheetTitle = ({ asChild, className, style, children }) => {
1720
1892
  const { panelId } = useSheetPanel();
1721
- return /* @__PURE__ */ jsx(asChild ? Slot : "h2", {
1722
- className: joinClassNames(asChild ? void 0 : "font-semibold text-sm", className),
1893
+ const isAsChild = asChild === true;
1894
+ return /* @__PURE__ */ jsx(isAsChild ? Slot : "h2", {
1895
+ className: joinClassNames(isAsChild ? void 0 : "font-semibold text-sm", className),
1723
1896
  id: `${panelId}-title`,
1724
1897
  style,
1725
1898
  children
1726
1899
  });
1727
- }
1900
+ };
1728
1901
  //#endregion
1729
- //#region src/SheetParts/SheetParts.ts
1902
+ //#region src/SheetParts/sheet-parts.ts
1730
1903
  /**
1731
1904
  * Composable Sheet Parts for building custom Sheet layouts.
1732
1905
  *