@patternmode/stacksheet 1.3.6 → 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.
- package/dist/Drag/drag-geometry.d.ts.map +1 -1
- package/dist/Drag/use-drag.d.ts.map +1 -1
- package/dist/SheetPanel/default-header.d.ts +1 -1
- package/dist/SheetPanel/default-header.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-content.d.ts +1 -1
- package/dist/SheetPanel/sheet-panel-content.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-focus.d.ts +1 -1
- package/dist/SheetPanel/sheet-panel-focus.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-handles.d.ts +2 -2
- package/dist/SheetPanel/sheet-panel-handles.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-layout.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel.d.ts +1 -1
- package/dist/SheetPanel/sheet-panel.d.ts.map +1 -1
- package/dist/SheetParts/sheet-back.d.ts +1 -1
- package/dist/SheetParts/sheet-back.d.ts.map +1 -1
- package/dist/SheetParts/sheet-body.d.ts +1 -1
- package/dist/SheetParts/sheet-body.d.ts.map +1 -1
- package/dist/SheetParts/sheet-close.d.ts +1 -1
- package/dist/SheetParts/sheet-close.d.ts.map +1 -1
- package/dist/SheetParts/sheet-description.d.ts +1 -1
- package/dist/SheetParts/sheet-description.d.ts.map +1 -1
- package/dist/SheetParts/sheet-footer.d.ts +1 -1
- package/dist/SheetParts/sheet-footer.d.ts.map +1 -1
- package/dist/SheetParts/sheet-handle.d.ts +1 -1
- package/dist/SheetParts/sheet-handle.d.ts.map +1 -1
- package/dist/SheetParts/sheet-header.d.ts +1 -1
- package/dist/SheetParts/sheet-header.d.ts.map +1 -1
- package/dist/SheetParts/sheet-parts.d.ts +8 -8
- package/dist/SheetParts/sheet-title.d.ts +1 -1
- package/dist/SheetParts/sheet-title.d.ts.map +1 -1
- package/dist/Store/create-sheet-store.d.ts.map +1 -1
- package/dist/Store/store-args.d.ts +11 -0
- package/dist/Store/store-args.d.ts.map +1 -1
- package/dist/create.d.ts.map +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/icons.d.ts.map +1 -1
- package/dist/index.mjs +598 -415
- package/dist/index.mjs.map +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/panel-context.d.ts.map +1 -1
- package/dist/renderer-effects.d.ts.map +1 -1
- package/dist/renderer-helpers.d.ts.map +1 -1
- package/dist/renderer.d.ts +2 -2
- package/dist/renderer.d.ts.map +1 -1
- package/dist/snap-points.d.ts.map +1 -1
- package/dist/springs.d.ts +11 -17
- package/dist/springs.d.ts.map +1 -1
- package/dist/stacking.d.ts +2 -2
- package/dist/stacking.d.ts.map +1 -1
- package/dist/types.d.ts +26 -32
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -12
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { Portal } from "@radix-ui/react-portal";
|
|
|
3
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,29 +12,28 @@ 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
|
-
snappy:
|
|
23
|
-
damping: 28,
|
|
24
|
-
mass: .8,
|
|
25
|
-
stiffness: 400
|
|
26
|
-
},
|
|
30
|
+
snappy: toSpringConfig(springs$1.snappy),
|
|
27
31
|
stiff: {
|
|
28
32
|
damping: 40,
|
|
29
33
|
mass: 1,
|
|
30
34
|
stiffness: 400
|
|
31
35
|
},
|
|
32
|
-
subtle:
|
|
33
|
-
damping: 30,
|
|
34
|
-
mass: 1,
|
|
35
|
-
stiffness: 300
|
|
36
|
-
}
|
|
36
|
+
subtle: toSpringConfig(springs$1.subtle)
|
|
37
37
|
};
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region src/config.ts
|
|
@@ -114,9 +114,13 @@ const useIsMobile = (breakpoint) => {
|
|
|
114
114
|
useEffect(() => {
|
|
115
115
|
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
116
116
|
setIsMobile(mql.matches);
|
|
117
|
-
const handler = (e) =>
|
|
117
|
+
const handler = (e) => {
|
|
118
|
+
setIsMobile(e.matches);
|
|
119
|
+
};
|
|
118
120
|
mql.addEventListener("change", handler);
|
|
119
|
-
return () =>
|
|
121
|
+
return () => {
|
|
122
|
+
mql.removeEventListener("change", handler);
|
|
123
|
+
};
|
|
120
124
|
}, [breakpoint]);
|
|
121
125
|
return isMobile;
|
|
122
126
|
};
|
|
@@ -130,13 +134,17 @@ const usePanelHeight = (panelRef, hasSnapPoints) => {
|
|
|
130
134
|
const [height, setHeight] = useState(0);
|
|
131
135
|
useEffect(() => {
|
|
132
136
|
const el = panelRef.current;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
};
|
|
140
148
|
}, [panelRef, hasSnapPoints]);
|
|
141
149
|
return height;
|
|
142
150
|
};
|
|
@@ -144,40 +152,56 @@ const getViewportHeight = () => typeof window === "undefined" ? 0 : window.visua
|
|
|
144
152
|
const useViewportHeight = (active) => {
|
|
145
153
|
const [height, setHeight] = useState(() => typeof window === "undefined" ? void 0 : getViewportHeight());
|
|
146
154
|
useEffect(() => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
window
|
|
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
|
+
}
|
|
151
163
|
return () => {
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
if (canListen) {
|
|
165
|
+
window.removeEventListener("resize", update);
|
|
166
|
+
window.visualViewport?.removeEventListener("resize", update);
|
|
167
|
+
}
|
|
154
168
|
};
|
|
155
169
|
}, []);
|
|
156
170
|
return active ? height ?? 0 : 0;
|
|
157
171
|
};
|
|
158
172
|
const useBodyScale = (config, isOpen, prefersReducedMotion) => {
|
|
159
173
|
useEffect(() => {
|
|
160
|
-
if (!config.shouldScaleBackground || prefersReducedMotion) return;
|
|
161
174
|
const wrapper = document.querySelector("[data-stacksheet-wrapper]");
|
|
162
|
-
|
|
163
|
-
|
|
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) {
|
|
164
186
|
const scale = config.scaleBackgroundAmount;
|
|
165
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)";
|
|
166
188
|
wrapper.style.transform = `scale(${scale})`;
|
|
167
189
|
wrapper.style.borderRadius = "8px";
|
|
168
190
|
wrapper.style.overflow = "hidden";
|
|
169
191
|
wrapper.style.transformOrigin = "center top";
|
|
170
|
-
return;
|
|
192
|
+
return resetWrapper;
|
|
171
193
|
}
|
|
172
|
-
wrapper.style.transform = "";
|
|
173
|
-
wrapper.style.borderRadius = "";
|
|
174
194
|
const handleEnd = () => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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);
|
|
178
204
|
};
|
|
179
|
-
wrapper.addEventListener("transitionend", handleEnd, { once: true });
|
|
180
|
-
return () => wrapper.removeEventListener("transitionend", handleEnd);
|
|
181
205
|
}, [
|
|
182
206
|
isOpen,
|
|
183
207
|
config.shouldScaleBackground,
|
|
@@ -187,7 +211,14 @@ const useBodyScale = (config, isOpen, prefersReducedMotion) => {
|
|
|
187
211
|
};
|
|
188
212
|
//#endregion
|
|
189
213
|
//#region src/snap-points.ts
|
|
190
|
-
const SNAP_POINT_RE = /^(
|
|
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
|
+
};
|
|
191
222
|
/**
|
|
192
223
|
* Resolve a snap point value to pixels given the viewport height.
|
|
193
224
|
* - number 0-1: fraction of viewport (0.5 → 50% of vh)
|
|
@@ -197,18 +228,11 @@ const SNAP_POINT_RE = /^(\d+(?:\.\d+)?)(px|rem|em|vh|%)$/u;
|
|
|
197
228
|
const resolveSnapPointPx = (point, viewportHeight) => {
|
|
198
229
|
if (typeof point === "number") return point <= 1 ? point * viewportHeight : point;
|
|
199
230
|
if (typeof point === "string") {
|
|
200
|
-
const match =
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
case "px": return value;
|
|
206
|
-
case "rem":
|
|
207
|
-
case "em": return value * (typeof document === "undefined" ? 16 : Number.parseFloat(getComputedStyle(document.documentElement).fontSize));
|
|
208
|
-
case "vh":
|
|
209
|
-
case "%": return value / 100 * viewportHeight;
|
|
210
|
-
default: return 0;
|
|
211
|
-
}
|
|
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);
|
|
212
236
|
}
|
|
213
237
|
return 0;
|
|
214
238
|
};
|
|
@@ -397,7 +421,7 @@ const RUBBER_BAND_FACTOR = .6;
|
|
|
397
421
|
//#region src/Drag/drag-geometry.ts
|
|
398
422
|
const isInteractiveElement = (el) => {
|
|
399
423
|
if (INTERACTIVE_TAGS.has(el.tagName)) return true;
|
|
400
|
-
if (el.isContentEditable) return true;
|
|
424
|
+
if (el instanceof HTMLElement && el.isContentEditable) return true;
|
|
401
425
|
if (el.closest("button, a, input, textarea, select, [contenteditable]")) return true;
|
|
402
426
|
if (el.closest("[data-stacksheet-no-drag]")) return true;
|
|
403
427
|
return false;
|
|
@@ -473,7 +497,7 @@ const classifyGesture = (dx, dy, axis, sign) => {
|
|
|
473
497
|
/** Decide whether a pointer gesture commits as a drag or should be ignored. */
|
|
474
498
|
const commitGesture = (dx, dy, axis, sign, scrollEl) => {
|
|
475
499
|
if (classifyGesture(dx, dy, axis, sign) === "none") return "none";
|
|
476
|
-
if (scrollEl && !isAtScrollEdge(scrollEl, axis, sign)) return "none";
|
|
500
|
+
if (scrollEl !== null && !isAtScrollEdge(scrollEl, axis, sign)) return "none";
|
|
477
501
|
return "drag";
|
|
478
502
|
};
|
|
479
503
|
const getPanelDimension = (panel, axis) => {
|
|
@@ -482,86 +506,64 @@ const getPanelDimension = (panel, axis) => {
|
|
|
482
506
|
};
|
|
483
507
|
//#endregion
|
|
484
508
|
//#region src/Drag/use-drag.ts
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
* rubber-band resistance (same physics as iOS over-scroll).
|
|
497
|
-
*
|
|
498
|
-
* When `snapHeights` is provided, release targeting uses
|
|
499
|
-
* `findSnapTarget()` instead of the simple threshold check.
|
|
500
|
-
*/
|
|
501
|
-
const useDrag = (panelRef, config, onDragUpdate) => {
|
|
502
|
-
const startRef = useRef(null);
|
|
503
|
-
const committedRef = useRef(null);
|
|
504
|
-
const offsetRef = useRef(0);
|
|
505
|
-
const scrollTargetRef = useRef(null);
|
|
506
|
-
const { axis, sign } = getDismissAxis(config.side);
|
|
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
|
+
};
|
|
507
520
|
const handlePointerDown = (e) => {
|
|
508
|
-
if (!config.enabled) return;
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
if (!target) return;
|
|
512
|
-
const isHandle = !!target.closest("[data-stacksheet-handle]");
|
|
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;
|
|
513
524
|
if (!isHandle && isInteractiveElement(target)) return;
|
|
514
|
-
scrollTargetRef.current = isHandle ? null : findScrollableAncestor(target, axis);
|
|
515
|
-
startRef.current = {
|
|
525
|
+
refs.scrollTargetRef.current = isHandle ? null : findScrollableAncestor(target, axis);
|
|
526
|
+
refs.startRef.current = {
|
|
516
527
|
time: Date.now(),
|
|
517
528
|
x: e.clientX,
|
|
518
529
|
y: e.clientY
|
|
519
530
|
};
|
|
520
|
-
committedRef.current = null;
|
|
521
|
-
offsetRef.current = 0;
|
|
522
|
-
e.currentTarget
|
|
531
|
+
refs.committedRef.current = null;
|
|
532
|
+
refs.offsetRef.current = 0;
|
|
533
|
+
if (e.currentTarget instanceof HTMLElement) e.currentTarget.setPointerCapture(e.pointerId);
|
|
523
534
|
};
|
|
524
535
|
const handlePointerMove = (e) => {
|
|
525
|
-
if (
|
|
526
|
-
const dx = e.clientX - startRef.current.x;
|
|
527
|
-
const dy = e.clientY - startRef.current.y;
|
|
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;
|
|
528
539
|
const dist = Math.hypot(dx, dy);
|
|
529
|
-
if (committedRef.current === null && dist < 10) return;
|
|
530
|
-
if (committedRef.current === null) {
|
|
531
|
-
committedRef.current = commitGesture(dx, dy, axis, sign, scrollTargetRef.current);
|
|
532
|
-
if (committedRef.current !== "drag") {
|
|
533
|
-
startRef.current = null;
|
|
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;
|
|
534
545
|
return;
|
|
535
546
|
}
|
|
536
547
|
}
|
|
537
|
-
if (committedRef.current !== "drag") return;
|
|
548
|
+
if (refs.committedRef.current !== "drag") return;
|
|
538
549
|
const directional = (axis === "x" ? dx : dy) * sign;
|
|
539
550
|
const clampedOffset = directional >= 0 ? directional : -Math.sqrt(Math.abs(directional)) * RUBBER_BAND_FACTOR;
|
|
540
|
-
offsetRef.current = clampedOffset;
|
|
551
|
+
refs.offsetRef.current = clampedOffset;
|
|
541
552
|
onDragUpdate({
|
|
542
553
|
isDragging: true,
|
|
543
554
|
offset: clampedOffset
|
|
544
555
|
});
|
|
545
556
|
e.preventDefault();
|
|
546
557
|
};
|
|
547
|
-
const
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
};
|
|
551
|
-
const handlePointerUp = (_e) => {
|
|
552
|
-
if (!startRef.current || committedRef.current !== "drag") {
|
|
553
|
-
startRef.current = null;
|
|
554
|
-
committedRef.current = null;
|
|
555
|
-
scrollTargetRef.current = null;
|
|
558
|
+
const handlePointerUp = () => {
|
|
559
|
+
if (refs.startRef.current === null || refs.committedRef.current !== "drag") {
|
|
560
|
+
resetDragRefs(refs);
|
|
556
561
|
return;
|
|
557
562
|
}
|
|
558
|
-
const offset = Math.max(0, offsetRef.current);
|
|
559
|
-
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;
|
|
560
565
|
const velocity = elapsed > 0 ? offset / elapsed : 0;
|
|
561
|
-
|
|
562
|
-
committedRef.current = null;
|
|
563
|
-
offsetRef.current = 0;
|
|
564
|
-
scrollTargetRef.current = null;
|
|
566
|
+
resetDragRefs(refs);
|
|
565
567
|
const panelSize = getPanelDimension(panelRef.current, axis);
|
|
566
568
|
if (config.snapHeights.length > 0) {
|
|
567
569
|
const targetIndex = findSnapTarget(offset, panelSize, config.snapHeights, velocity, config.activeSnapIndex, config.sequential);
|
|
@@ -584,15 +586,54 @@ const useDrag = (panelRef, config, onDragUpdate) => {
|
|
|
584
586
|
});
|
|
585
587
|
};
|
|
586
588
|
const handlePointerCancel = () => {
|
|
587
|
-
|
|
588
|
-
committedRef.current = null;
|
|
589
|
-
offsetRef.current = 0;
|
|
590
|
-
scrollTargetRef.current = null;
|
|
589
|
+
resetDragRefs(refs);
|
|
591
590
|
onDragUpdate({
|
|
592
591
|
isDragging: false,
|
|
593
592
|
offset: 0
|
|
594
593
|
});
|
|
595
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({
|
|
625
|
+
axis,
|
|
626
|
+
config,
|
|
627
|
+
onDragUpdate,
|
|
628
|
+
panelRef,
|
|
629
|
+
refs: {
|
|
630
|
+
committedRef,
|
|
631
|
+
offsetRef,
|
|
632
|
+
scrollTargetRef,
|
|
633
|
+
startRef
|
|
634
|
+
},
|
|
635
|
+
sign
|
|
636
|
+
});
|
|
596
637
|
const handlersRef = useRef({
|
|
597
638
|
handlePointerCancel,
|
|
598
639
|
handlePointerDown,
|
|
@@ -607,20 +648,32 @@ const useDrag = (panelRef, config, onDragUpdate) => {
|
|
|
607
648
|
};
|
|
608
649
|
useEffect(() => {
|
|
609
650
|
const el = panelRef.current;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
}
|
|
619
670
|
return () => {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
671
|
+
if (canListen) {
|
|
672
|
+
el.removeEventListener("pointerdown", onPointerDown);
|
|
673
|
+
el.removeEventListener("pointermove", onPointerMove);
|
|
674
|
+
el.removeEventListener("pointerup", onPointerUp);
|
|
675
|
+
el.removeEventListener("pointercancel", onPointerCancel);
|
|
676
|
+
}
|
|
624
677
|
};
|
|
625
678
|
}, [panelRef, config.enabled]);
|
|
626
679
|
};
|
|
@@ -795,11 +848,12 @@ const DefaultHeader = ({ isNested, onBack, onClose, className }) => /* @__PURE__
|
|
|
795
848
|
//#endregion
|
|
796
849
|
//#region src/SheetPanel/sheet-panel-content.tsx
|
|
797
850
|
const PanelInnerContent = ({ isComposable, shouldRender, Content, data, renderHeader, headerProps, headerClassName }) => {
|
|
798
|
-
if (isComposable) return shouldRender && Content ? /* @__PURE__ */ jsx(Content, { ...data }) : null;
|
|
799
|
-
|
|
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, {
|
|
800
854
|
...headerProps,
|
|
801
855
|
className: headerClassName
|
|
802
|
-
}), shouldRender && Content && /* @__PURE__ */ jsx("div", {
|
|
856
|
+
}) : customHeader(headerProps), shouldRender && Content !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
803
857
|
className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
|
|
804
858
|
"data-stacksheet-no-drag": "",
|
|
805
859
|
children: /* @__PURE__ */ jsx(Content, { ...data })
|
|
@@ -816,7 +870,9 @@ const LAYERED_MODAL_SELECTORS = [
|
|
|
816
870
|
].join(", ");
|
|
817
871
|
const subscribeToFocusTarget = (onStoreChange) => {
|
|
818
872
|
document.addEventListener("focusin", onStoreChange, true);
|
|
819
|
-
return () =>
|
|
873
|
+
return () => {
|
|
874
|
+
document.removeEventListener("focusin", onStoreChange, true);
|
|
875
|
+
};
|
|
820
876
|
};
|
|
821
877
|
const getServerLayeredModalFocused = () => false;
|
|
822
878
|
const getLayeredModalFocused = () => {
|
|
@@ -837,7 +893,7 @@ const ModalFocusTrap = ({ enabled, active, fallbackRef, children }) => {
|
|
|
837
893
|
allowOutsideClick: true,
|
|
838
894
|
escapeDeactivates: false,
|
|
839
895
|
fallbackFocus: () => {
|
|
840
|
-
if (fallbackRef.current) return fallbackRef.current;
|
|
896
|
+
if (fallbackRef.current !== null) return fallbackRef.current;
|
|
841
897
|
return document.body;
|
|
842
898
|
},
|
|
843
899
|
initialFocus: false,
|
|
@@ -890,10 +946,18 @@ const resolvePanelLayout = (layout, renderHeader) => {
|
|
|
890
946
|
//#region src/SheetPanel/sheet-panel.tsx
|
|
891
947
|
const getPanelAriaLabel = (item, fallbackLabel) => item.ariaLabel ?? (typeof item.data?.__ariaLabel === "string" ? item.data.__ariaLabel : void 0) ?? fallbackLabel;
|
|
892
948
|
const getPanelHoverProps = (enabled, setIsHovered) => enabled ? {
|
|
893
|
-
onBlur: () =>
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
949
|
+
onBlur: () => {
|
|
950
|
+
setIsHovered(false);
|
|
951
|
+
},
|
|
952
|
+
onFocus: () => {
|
|
953
|
+
setIsHovered(true);
|
|
954
|
+
},
|
|
955
|
+
onMouseEnter: () => {
|
|
956
|
+
setIsHovered(true);
|
|
957
|
+
},
|
|
958
|
+
onMouseLeave: () => {
|
|
959
|
+
setIsHovered(false);
|
|
960
|
+
}
|
|
897
961
|
} : {};
|
|
898
962
|
const getInactivePanelProps = (isTop) => isTop ? {} : {
|
|
899
963
|
"aria-hidden": "true",
|
|
@@ -927,25 +991,22 @@ const completeOpeningAnimation = (hasEnteredRef, isTop, onOpenCompleteRef) => {
|
|
|
927
991
|
onOpenCompleteRef.current?.();
|
|
928
992
|
}
|
|
929
993
|
};
|
|
930
|
-
const
|
|
931
|
-
const panelRef = useRef(null);
|
|
994
|
+
const useOpeningCompletion = (isTop, onOpenComplete) => {
|
|
932
995
|
const hasEnteredRef = useRef(false);
|
|
933
|
-
const onOpenCompleteRef = useRef(
|
|
934
|
-
const [dragState, setDragState] = useState({
|
|
935
|
-
isDragging: false,
|
|
936
|
-
offset: 0
|
|
937
|
-
});
|
|
938
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
939
|
-
const measuredHeight = usePanelHeight(panelRef, snapHeights.length > 0);
|
|
940
|
-
const transform = getStackTransform(depth, config.stacking);
|
|
941
|
-
const panelStyles = getPanelStyles(side, config, index);
|
|
996
|
+
const onOpenCompleteRef = useRef(onOpenComplete);
|
|
942
997
|
if (!isTop && hasEnteredRef.current) hasEnteredRef.current = false;
|
|
943
998
|
useEffect(() => {
|
|
944
|
-
onOpenCompleteRef.current =
|
|
945
|
-
}, [
|
|
946
|
-
|
|
999
|
+
onOpenCompleteRef.current = onOpenComplete;
|
|
1000
|
+
}, [onOpenComplete]);
|
|
1001
|
+
return () => {
|
|
947
1002
|
completeOpeningAnimation(hasEnteredRef, isTop, onOpenCompleteRef);
|
|
948
1003
|
};
|
|
1004
|
+
};
|
|
1005
|
+
const useSheetPanelDrag = ({ activeSnapIndex, config, isNested, isTop, onSnap, prefersReducedMotion, side, snapHeights, swipeClose, swipePop }, panelRef) => {
|
|
1006
|
+
const [dragState, setDragState] = useState({
|
|
1007
|
+
isDragging: false,
|
|
1008
|
+
offset: 0
|
|
1009
|
+
});
|
|
949
1010
|
useDrag(panelRef, {
|
|
950
1011
|
activeSnapIndex,
|
|
951
1012
|
closeThreshold: config.closeThreshold,
|
|
@@ -959,23 +1020,42 @@ const SheetPanel = ({ item, index, depth, isTop, isNested, side, config, classNa
|
|
|
959
1020
|
snapHeights,
|
|
960
1021
|
velocityThreshold: config.velocityThreshold
|
|
961
1022
|
}, setDragState);
|
|
962
|
-
|
|
963
|
-
|
|
1023
|
+
return dragState;
|
|
1024
|
+
};
|
|
1025
|
+
const useSheetPanelContext = ({ close, isNested, isTop, pop, side }, panelId) => {
|
|
964
1026
|
const [hasDescription, setHasDescription] = useState(false);
|
|
965
1027
|
const registerDescription = () => {
|
|
966
1028
|
setHasDescription(true);
|
|
967
|
-
return () =>
|
|
1029
|
+
return () => {
|
|
1030
|
+
setHasDescription(false);
|
|
1031
|
+
};
|
|
968
1032
|
};
|
|
969
|
-
|
|
970
|
-
close,
|
|
1033
|
+
return {
|
|
971
1034
|
hasDescription,
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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);
|
|
979
1059
|
const isComposable = resolvePanelLayout(layout, renderHeader) === "composable";
|
|
980
1060
|
const hasPanelClass = classNames.panel !== "";
|
|
981
1061
|
const dragOffset = getDragTransform(side, dragState.offset);
|
|
@@ -1002,9 +1082,26 @@ const SheetPanel = ({ item, index, depth, isTop, isNested, side, config, classNa
|
|
|
1002
1082
|
show: showSideHandle,
|
|
1003
1083
|
side
|
|
1004
1084
|
});
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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);
|
|
1008
1105
|
const panelContent = /* @__PURE__ */ jsxs(m.div, {
|
|
1009
1106
|
animate: animateTarget,
|
|
1010
1107
|
className: classNames.panel || void 0,
|
|
@@ -1079,21 +1176,27 @@ const getBackdropStyle = (config, hasBackdropClass) => ({
|
|
|
1079
1176
|
zIndex: config.zIndex,
|
|
1080
1177
|
...hasBackdropClass ? {} : { background: "var(--overlay, rgba(0, 0, 0, 0.15))" }
|
|
1081
1178
|
});
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classNamesProp, layout, renderHeader }) => {
|
|
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) => {
|
|
1090
1186
|
const isOpen = useStore(store, (s) => s.isOpen);
|
|
1091
1187
|
const stack = useStore(store, (s) => s.stack);
|
|
1092
|
-
const rawClose = useStore(store, (s) =>
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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) => {
|
|
1097
1200
|
const viewportHeight = useViewportHeight(isOpen && side === "bottom" && config.snapPoints.length > 0);
|
|
1098
1201
|
const snapHeights = getResolvedSnapHeights(side, config.snapPoints, viewportHeight);
|
|
1099
1202
|
const snapContext = isOpen ? stack.map((item) => item.id).join("\0") : "";
|
|
@@ -1118,6 +1221,13 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1118
1221
|
});
|
|
1119
1222
|
config.onSnapPointChange?.(index);
|
|
1120
1223
|
};
|
|
1224
|
+
return {
|
|
1225
|
+
activeSnapIndex,
|
|
1226
|
+
handleSnap,
|
|
1227
|
+
snapHeights
|
|
1228
|
+
};
|
|
1229
|
+
};
|
|
1230
|
+
const useCloseControls = (rawClose, rawPop) => {
|
|
1121
1231
|
const closeReasonRef = useRef("programmatic");
|
|
1122
1232
|
const closeWith = (reason) => {
|
|
1123
1233
|
closeReasonRef.current = reason;
|
|
@@ -1127,9 +1237,19 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1127
1237
|
closeReasonRef.current = reason;
|
|
1128
1238
|
rawPop();
|
|
1129
1239
|
};
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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) => {
|
|
1133
1253
|
const triggerRef = useRef(null);
|
|
1134
1254
|
const wasOpenRef = useRef(false);
|
|
1135
1255
|
useEffect(() => {
|
|
@@ -1141,56 +1261,96 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1141
1261
|
}
|
|
1142
1262
|
wasOpenRef.current = isOpen;
|
|
1143
1263
|
}, [isOpen]);
|
|
1144
|
-
|
|
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);
|
|
1145
1272
|
useEffect(() => {
|
|
1146
|
-
stackLengthRef.current =
|
|
1147
|
-
}, [
|
|
1273
|
+
stackLengthRef.current = stackLength;
|
|
1274
|
+
}, [stackLength]);
|
|
1148
1275
|
useEffect(() => {
|
|
1149
|
-
|
|
1276
|
+
const shouldListen = isOpen && config.closeOnEscape && config.dismissible;
|
|
1150
1277
|
const handleKeyDown = (e) => {
|
|
1151
1278
|
if (e.key === "Escape") {
|
|
1152
1279
|
e.preventDefault();
|
|
1153
|
-
|
|
1154
|
-
closeReasonRef
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1280
|
+
dismissFromEscape({
|
|
1281
|
+
closeReasonRef,
|
|
1282
|
+
rawClose,
|
|
1283
|
+
rawPop,
|
|
1284
|
+
stackLengthRef
|
|
1285
|
+
});
|
|
1160
1286
|
}
|
|
1161
1287
|
};
|
|
1162
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
1163
|
-
return () =>
|
|
1288
|
+
if (shouldListen) document.addEventListener("keydown", handleKeyDown);
|
|
1289
|
+
return () => {
|
|
1290
|
+
if (shouldListen) document.removeEventListener("keydown", handleKeyDown);
|
|
1291
|
+
};
|
|
1164
1292
|
}, [
|
|
1165
1293
|
isOpen,
|
|
1166
1294
|
config.closeOnEscape,
|
|
1167
1295
|
config.dismissible,
|
|
1168
1296
|
rawPop,
|
|
1169
|
-
rawClose
|
|
1297
|
+
rawClose,
|
|
1298
|
+
closeReasonRef
|
|
1170
1299
|
]);
|
|
1171
1300
|
useEffect(() => {
|
|
1172
|
-
|
|
1173
|
-
const
|
|
1301
|
+
const CloseWatcherConstructor = globalThis.CloseWatcher;
|
|
1302
|
+
const shouldListen = isOpen && config.dismissible && CloseWatcherConstructor !== void 0;
|
|
1303
|
+
let watcher;
|
|
1174
1304
|
const handleClose = () => {
|
|
1175
|
-
|
|
1176
|
-
closeReasonRef
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
}
|
|
1305
|
+
dismissFromEscape({
|
|
1306
|
+
closeReasonRef,
|
|
1307
|
+
rawClose,
|
|
1308
|
+
rawPop,
|
|
1309
|
+
stackLengthRef
|
|
1310
|
+
});
|
|
1182
1311
|
};
|
|
1183
|
-
|
|
1312
|
+
if (shouldListen) {
|
|
1313
|
+
watcher = new CloseWatcherConstructor();
|
|
1314
|
+
watcher.addEventListener("close", handleClose);
|
|
1315
|
+
}
|
|
1184
1316
|
return () => {
|
|
1185
|
-
watcher
|
|
1186
|
-
|
|
1317
|
+
if (watcher !== void 0) {
|
|
1318
|
+
watcher.removeEventListener("close", handleClose);
|
|
1319
|
+
watcher.destroy();
|
|
1320
|
+
}
|
|
1187
1321
|
};
|
|
1188
1322
|
}, [
|
|
1189
1323
|
isOpen,
|
|
1190
1324
|
config.dismissible,
|
|
1191
1325
|
rawPop,
|
|
1192
|
-
rawClose
|
|
1326
|
+
rawClose,
|
|
1327
|
+
closeReasonRef
|
|
1193
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
|
+
});
|
|
1194
1354
|
const slideFrom = getSlideFrom(side);
|
|
1195
1355
|
const slideTarget = getSlideTarget();
|
|
1196
1356
|
const spring = getMotionSpring(prefersReducedMotion, config);
|
|
@@ -1201,8 +1361,12 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1201
1361
|
const handleExitComplete = () => {
|
|
1202
1362
|
if (stack.length === 0) config.onCloseComplete?.(closeReasonRef.current);
|
|
1203
1363
|
};
|
|
1204
|
-
const swipeClose = () =>
|
|
1205
|
-
|
|
1364
|
+
const swipeClose = () => {
|
|
1365
|
+
closeWith("swipe");
|
|
1366
|
+
};
|
|
1367
|
+
const swipePop = () => {
|
|
1368
|
+
popWith("swipe");
|
|
1369
|
+
};
|
|
1206
1370
|
const shouldLockScroll = isOpen && isModal && config.lockScroll;
|
|
1207
1371
|
return /* @__PURE__ */ jsxs(LazyMotion, {
|
|
1208
1372
|
features: domMax,
|
|
@@ -1213,7 +1377,9 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1213
1377
|
className: `fixed inset-0 ${classNames.backdrop || ""}`,
|
|
1214
1378
|
exit: { opacity: 0 },
|
|
1215
1379
|
initial: { opacity: 0 },
|
|
1216
|
-
onClick: config.closeOnBackdrop && config.dismissible ? () =>
|
|
1380
|
+
onClick: config.closeOnBackdrop && config.dismissible ? () => {
|
|
1381
|
+
closeWith("backdrop");
|
|
1382
|
+
} : void 0,
|
|
1217
1383
|
style: backdropStyle,
|
|
1218
1384
|
transition: spring
|
|
1219
1385
|
}, "stacksheet-backdrop")
|
|
@@ -1232,7 +1398,7 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1232
1398
|
const shouldRender = depth <= config.stacking.renderThreshold;
|
|
1233
1399
|
return /* @__PURE__ */ jsx(SheetPanel, {
|
|
1234
1400
|
activeSnapIndex,
|
|
1235
|
-
Content: componentMap.get(item.type) ?? sheets
|
|
1401
|
+
Content: componentMap.get(item.type) ?? getStaticContent(sheets, item.type),
|
|
1236
1402
|
classNames,
|
|
1237
1403
|
close,
|
|
1238
1404
|
config,
|
|
@@ -1264,11 +1430,23 @@ const SheetRenderer = ({ store, config, sheets, componentMap, classNames: classN
|
|
|
1264
1430
|
};
|
|
1265
1431
|
//#endregion
|
|
1266
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
|
+
};
|
|
1267
1445
|
const resolvePresentationOptions = (value) => {
|
|
1268
|
-
if (!(value
|
|
1269
|
-
const
|
|
1270
|
-
if (
|
|
1271
|
-
return
|
|
1446
|
+
if (!isRecord(value)) return;
|
|
1447
|
+
const { ariaLabel } = value;
|
|
1448
|
+
if (ariaLabel !== void 0 && typeof ariaLabel !== "string") return;
|
|
1449
|
+
return ariaLabel === void 0 ? {} : { ariaLabel };
|
|
1272
1450
|
};
|
|
1273
1451
|
/**
|
|
1274
1452
|
* Dev-mode warning: detect likely inline arrow functions passed as ad-hoc components.
|
|
@@ -1276,52 +1454,202 @@ const resolvePresentationOptions = (value) => {
|
|
|
1276
1454
|
* it's almost always an inline arrow being re-created every render.
|
|
1277
1455
|
*/
|
|
1278
1456
|
const warnInlineComponent = (component, componentRegistry, warnedNames) => {
|
|
1279
|
-
if (
|
|
1280
|
-
const name = component
|
|
1281
|
-
if (
|
|
1457
|
+
if (getNodeEnv() === "production") return;
|
|
1458
|
+
const name = getComponentName(component);
|
|
1459
|
+
if (name === void 0) return;
|
|
1282
1460
|
if (warnedNames.has(name)) return;
|
|
1283
|
-
for (const [existing, key] of componentRegistry) if ((existing
|
|
1461
|
+
for (const [existing, key] of componentRegistry) if (getComponentName(existing) === name) {
|
|
1284
1462
|
warnedNames.add(name);
|
|
1285
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.`);
|
|
1286
1464
|
return;
|
|
1287
1465
|
}
|
|
1288
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
|
+
};
|
|
1289
1476
|
/**
|
|
1290
1477
|
* If `first` is a function (component), register it and return { type, id, data }.
|
|
1291
1478
|
* Otherwise, pass through the string-based (type, id, data) args unchanged.
|
|
1292
1479
|
*/
|
|
1293
1480
|
const resolveArgs = (componentRegistry, componentMap, getNextKey, warnedNames, first, second, third, fourth) => {
|
|
1294
|
-
if (
|
|
1295
|
-
const
|
|
1296
|
-
let typeKey = componentRegistry.get(component);
|
|
1297
|
-
if (!typeKey) {
|
|
1298
|
-
warnInlineComponent(component, componentRegistry, warnedNames);
|
|
1299
|
-
typeKey = getNextKey();
|
|
1300
|
-
componentRegistry.set(component, typeKey);
|
|
1301
|
-
componentMap.set(typeKey, component);
|
|
1302
|
-
}
|
|
1481
|
+
if (isComponent(first)) {
|
|
1482
|
+
const typeKey = registerComponent(first, componentRegistry, componentMap, getNextKey, warnedNames);
|
|
1303
1483
|
if (typeof second === "string") return {
|
|
1304
1484
|
ariaLabel: resolvePresentationOptions(fourth)?.ariaLabel,
|
|
1305
|
-
data: third
|
|
1485
|
+
data: toRecord(third),
|
|
1306
1486
|
id: second,
|
|
1307
1487
|
type: typeKey
|
|
1308
1488
|
};
|
|
1309
1489
|
return {
|
|
1310
1490
|
ariaLabel: resolvePresentationOptions(third)?.ariaLabel,
|
|
1311
|
-
data: second
|
|
1491
|
+
data: toRecord(second),
|
|
1312
1492
|
id: crypto.randomUUID(),
|
|
1313
1493
|
type: typeKey
|
|
1314
1494
|
};
|
|
1315
1495
|
}
|
|
1316
1496
|
return {
|
|
1317
1497
|
ariaLabel: resolvePresentationOptions(fourth)?.ariaLabel,
|
|
1318
|
-
data: third
|
|
1319
|
-
id: second,
|
|
1320
|
-
type: first
|
|
1498
|
+
data: toRecord(third),
|
|
1499
|
+
id: getStringArg(second, "sheet id"),
|
|
1500
|
+
type: getStringArg(first, "sheet type")
|
|
1321
1501
|
};
|
|
1322
1502
|
};
|
|
1323
1503
|
//#endregion
|
|
1324
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
|
+
};
|
|
1325
1653
|
/**
|
|
1326
1654
|
* Create an isolated Zustand store for a sheet stack instance.
|
|
1327
1655
|
*
|
|
@@ -1355,165 +1683,15 @@ const createSheetStore = (config) => {
|
|
|
1355
1683
|
return {
|
|
1356
1684
|
componentMap,
|
|
1357
1685
|
componentRegistry,
|
|
1358
|
-
store: createStore()((
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
}]
|
|
1368
|
-
});
|
|
1369
|
-
};
|
|
1370
|
-
const _pushResolved = ({ type, id, data, ariaLabel }) => {
|
|
1371
|
-
set((state) => {
|
|
1372
|
-
const item = {
|
|
1373
|
-
ariaLabel,
|
|
1374
|
-
data,
|
|
1375
|
-
id,
|
|
1376
|
-
type
|
|
1377
|
-
};
|
|
1378
|
-
if (Number.isFinite(config.maxDepth) && state.stack.length >= config.maxDepth) return {
|
|
1379
|
-
isOpen: true,
|
|
1380
|
-
stack: [...state.stack.slice(0, -1), item]
|
|
1381
|
-
};
|
|
1382
|
-
return {
|
|
1383
|
-
isOpen: true,
|
|
1384
|
-
stack: [...state.stack, item]
|
|
1385
|
-
};
|
|
1386
|
-
});
|
|
1387
|
-
};
|
|
1388
|
-
const _replaceResolved = ({ type, id, data, ariaLabel }) => {
|
|
1389
|
-
set((state) => {
|
|
1390
|
-
const item = {
|
|
1391
|
-
ariaLabel,
|
|
1392
|
-
data,
|
|
1393
|
-
id,
|
|
1394
|
-
type
|
|
1395
|
-
};
|
|
1396
|
-
if (state.stack.length === 0) return {
|
|
1397
|
-
isOpen: true,
|
|
1398
|
-
stack: [item]
|
|
1399
|
-
};
|
|
1400
|
-
return {
|
|
1401
|
-
isOpen: true,
|
|
1402
|
-
stack: [...state.stack.slice(0, -1), item]
|
|
1403
|
-
};
|
|
1404
|
-
});
|
|
1405
|
-
};
|
|
1406
|
-
return {
|
|
1407
|
-
close: () => {
|
|
1408
|
-
pruneRegistry([]);
|
|
1409
|
-
set({
|
|
1410
|
-
isOpen: false,
|
|
1411
|
-
stack: []
|
|
1412
|
-
});
|
|
1413
|
-
},
|
|
1414
|
-
isOpen: false,
|
|
1415
|
-
navigate: (first, second, third, fourth) => {
|
|
1416
|
-
const resolved = resolve(first, second, third, fourth);
|
|
1417
|
-
const { stack } = get();
|
|
1418
|
-
const top = stack.at(-1);
|
|
1419
|
-
if (stack.length === 0) {
|
|
1420
|
-
_openResolved(resolved);
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
let isSameType = top?.type === resolved.type;
|
|
1424
|
-
if (!isSameType && typeof first === "function") isSameType = componentMap.get(top?.type ?? "") === first;
|
|
1425
|
-
if (isSameType) {
|
|
1426
|
-
_replaceResolved(resolved);
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
_pushResolved(resolved);
|
|
1430
|
-
},
|
|
1431
|
-
open: (first, second, third, fourth) => {
|
|
1432
|
-
_openResolved(resolve(first, second, third, fourth));
|
|
1433
|
-
},
|
|
1434
|
-
pop: () => {
|
|
1435
|
-
set((state) => {
|
|
1436
|
-
if (state.stack.length <= 1) {
|
|
1437
|
-
pruneRegistry([]);
|
|
1438
|
-
return {
|
|
1439
|
-
isOpen: false,
|
|
1440
|
-
stack: []
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
const next = state.stack.slice(0, -1);
|
|
1444
|
-
pruneRegistry(next);
|
|
1445
|
-
return {
|
|
1446
|
-
isOpen: true,
|
|
1447
|
-
stack: next
|
|
1448
|
-
};
|
|
1449
|
-
});
|
|
1450
|
-
},
|
|
1451
|
-
push: (first, second, third, fourth) => {
|
|
1452
|
-
_pushResolved(resolve(first, second, third, fourth));
|
|
1453
|
-
},
|
|
1454
|
-
remove: (id) => {
|
|
1455
|
-
set((state) => {
|
|
1456
|
-
const next = state.stack.filter((item) => item.id !== id);
|
|
1457
|
-
if (next.length === state.stack.length) return state;
|
|
1458
|
-
pruneRegistry(next);
|
|
1459
|
-
if (next.length === 0) return {
|
|
1460
|
-
isOpen: false,
|
|
1461
|
-
stack: []
|
|
1462
|
-
};
|
|
1463
|
-
return { stack: next };
|
|
1464
|
-
});
|
|
1465
|
-
},
|
|
1466
|
-
replace: (first, second, third, fourth) => {
|
|
1467
|
-
_replaceResolved(resolve(first, second, third, fourth));
|
|
1468
|
-
},
|
|
1469
|
-
setData: (first, second, third) => {
|
|
1470
|
-
const { id, data } = resolve(first, second, third);
|
|
1471
|
-
set((state) => {
|
|
1472
|
-
const idx = state.stack.findIndex((item) => item.id === id);
|
|
1473
|
-
if (idx === -1) return state;
|
|
1474
|
-
const updated = [...state.stack];
|
|
1475
|
-
updated[idx] = {
|
|
1476
|
-
...updated[idx],
|
|
1477
|
-
data
|
|
1478
|
-
};
|
|
1479
|
-
return { stack: updated };
|
|
1480
|
-
});
|
|
1481
|
-
},
|
|
1482
|
-
stack: [],
|
|
1483
|
-
swap: (first, second, third) => {
|
|
1484
|
-
let type;
|
|
1485
|
-
let data;
|
|
1486
|
-
const ariaLabel = resolvePresentationOptions(third)?.ariaLabel;
|
|
1487
|
-
if (typeof first === "function") {
|
|
1488
|
-
const component = first;
|
|
1489
|
-
let typeKey = componentRegistry.get(component);
|
|
1490
|
-
if (!typeKey) {
|
|
1491
|
-
warnInlineComponent(component, componentRegistry, warnedNames);
|
|
1492
|
-
typeKey = getNextKey();
|
|
1493
|
-
componentRegistry.set(component, typeKey);
|
|
1494
|
-
componentMap.set(typeKey, component);
|
|
1495
|
-
}
|
|
1496
|
-
type = typeKey;
|
|
1497
|
-
data = second ?? {};
|
|
1498
|
-
} else {
|
|
1499
|
-
type = first;
|
|
1500
|
-
data = second ?? {};
|
|
1501
|
-
}
|
|
1502
|
-
set((state) => {
|
|
1503
|
-
const top = state.stack.at(-1);
|
|
1504
|
-
if (!top) return state;
|
|
1505
|
-
const newStack = [...state.stack];
|
|
1506
|
-
newStack[newStack.length - 1] = {
|
|
1507
|
-
ariaLabel: ariaLabel ?? top.ariaLabel,
|
|
1508
|
-
data,
|
|
1509
|
-
id: top.id,
|
|
1510
|
-
type
|
|
1511
|
-
};
|
|
1512
|
-
return { stack: newStack };
|
|
1513
|
-
});
|
|
1514
|
-
}
|
|
1515
|
-
};
|
|
1516
|
-
})
|
|
1686
|
+
store: createStore()(createStoreState({
|
|
1687
|
+
componentMap,
|
|
1688
|
+
componentRegistry,
|
|
1689
|
+
config,
|
|
1690
|
+
getNextKey,
|
|
1691
|
+
pruneRegistry,
|
|
1692
|
+
resolve,
|
|
1693
|
+
warnedNames
|
|
1694
|
+
}))
|
|
1517
1695
|
};
|
|
1518
1696
|
};
|
|
1519
1697
|
//#endregion
|
|
@@ -1557,12 +1735,11 @@ const createStacksheet = (config) => {
|
|
|
1557
1735
|
if (!ctx) throw new Error("useSheet/useStacksheetState must be used within <StacksheetProvider>");
|
|
1558
1736
|
return ctx;
|
|
1559
1737
|
};
|
|
1560
|
-
const EMPTY_SHEETS = {};
|
|
1561
1738
|
const providerValue = {
|
|
1562
1739
|
config: resolved,
|
|
1563
1740
|
store
|
|
1564
1741
|
};
|
|
1565
|
-
const StacksheetProvider = ({ sheets
|
|
1742
|
+
const StacksheetProvider = ({ sheets, children, classNames, layout, renderHeader }) => /* @__PURE__ */ jsxs(StoreContext.Provider, {
|
|
1566
1743
|
value: providerValue,
|
|
1567
1744
|
children: [children, /* @__PURE__ */ jsx(Portal, {
|
|
1568
1745
|
asChild: false,
|
|
@@ -1611,19 +1788,20 @@ const createStacksheet = (config) => {
|
|
|
1611
1788
|
const SheetBack = ({ asChild, className, style, children }) => {
|
|
1612
1789
|
const { back, isNested } = useSheetPanel();
|
|
1613
1790
|
if (!isNested) return null;
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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),
|
|
1617
1795
|
onClick: back,
|
|
1618
1796
|
style,
|
|
1619
|
-
type:
|
|
1797
|
+
type: isAsChild ? void 0 : "button",
|
|
1620
1798
|
children: children ?? /* @__PURE__ */ jsx(ArrowLeftIcon, {})
|
|
1621
1799
|
});
|
|
1622
1800
|
};
|
|
1623
1801
|
//#endregion
|
|
1624
1802
|
//#region src/SheetParts/sheet-body.tsx
|
|
1625
1803
|
const SheetBody = ({ asChild, className, style, children }) => {
|
|
1626
|
-
if (asChild) return /* @__PURE__ */ jsx(Slot, {
|
|
1804
|
+
if (asChild === true) return /* @__PURE__ */ jsx(Slot, {
|
|
1627
1805
|
className: joinClassNames("relative min-h-0 flex-1", className),
|
|
1628
1806
|
"data-stacksheet-no-drag": "",
|
|
1629
1807
|
style,
|
|
@@ -1647,12 +1825,13 @@ const SheetBody = ({ asChild, className, style, children }) => {
|
|
|
1647
1825
|
//#region src/SheetParts/sheet-close.tsx
|
|
1648
1826
|
const SheetClose = ({ asChild, className, style, children }) => {
|
|
1649
1827
|
const { close } = useSheetPanel();
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
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),
|
|
1653
1832
|
onClick: close,
|
|
1654
1833
|
style,
|
|
1655
|
-
type:
|
|
1834
|
+
type: isAsChild ? void 0 : "button",
|
|
1656
1835
|
children: children ?? /* @__PURE__ */ jsx(XIcon, {})
|
|
1657
1836
|
});
|
|
1658
1837
|
};
|
|
@@ -1661,7 +1840,7 @@ const SheetClose = ({ asChild, className, style, children }) => {
|
|
|
1661
1840
|
const SheetDescription = ({ asChild, className, style, children }) => {
|
|
1662
1841
|
const { panelId, registerDescription } = useSheetPanel();
|
|
1663
1842
|
useEffect(() => registerDescription(), [registerDescription]);
|
|
1664
|
-
return /* @__PURE__ */ jsx(asChild ? Slot : "p", {
|
|
1843
|
+
return /* @__PURE__ */ jsx(asChild === true ? Slot : "p", {
|
|
1665
1844
|
className,
|
|
1666
1845
|
id: `${panelId}-desc`,
|
|
1667
1846
|
style,
|
|
@@ -1671,8 +1850,9 @@ const SheetDescription = ({ asChild, className, style, children }) => {
|
|
|
1671
1850
|
//#endregion
|
|
1672
1851
|
//#region src/SheetParts/sheet-footer.tsx
|
|
1673
1852
|
const SheetFooter = ({ asChild, className, style, children }) => {
|
|
1674
|
-
|
|
1675
|
-
|
|
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),
|
|
1676
1856
|
style,
|
|
1677
1857
|
children
|
|
1678
1858
|
});
|
|
@@ -1682,13 +1862,14 @@ const SheetFooter = ({ asChild, className, style, children }) => {
|
|
|
1682
1862
|
const SheetHandle = ({ asChild, className, style, children }) => {
|
|
1683
1863
|
const { close, back, isNested } = useSheetPanel();
|
|
1684
1864
|
const dismiss = isNested ? back : close;
|
|
1685
|
-
|
|
1865
|
+
const isAsChild = asChild === true;
|
|
1866
|
+
return /* @__PURE__ */ jsx(isAsChild ? Slot : "button", {
|
|
1686
1867
|
"aria-label": "Dismiss",
|
|
1687
|
-
className: joinClassNames(
|
|
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),
|
|
1688
1869
|
"data-stacksheet-handle": "",
|
|
1689
1870
|
onClick: dismiss,
|
|
1690
1871
|
style,
|
|
1691
|
-
type:
|
|
1872
|
+
type: isAsChild ? void 0 : "button",
|
|
1692
1873
|
children: children ?? /* @__PURE__ */ jsx("div", {
|
|
1693
1874
|
"aria-hidden": "true",
|
|
1694
1875
|
className: "h-1 w-9 rounded-sm bg-current/25"
|
|
@@ -1698,8 +1879,9 @@ const SheetHandle = ({ asChild, className, style, children }) => {
|
|
|
1698
1879
|
//#endregion
|
|
1699
1880
|
//#region src/SheetParts/sheet-header.tsx
|
|
1700
1881
|
const SheetHeader = ({ asChild, className, style, children }) => {
|
|
1701
|
-
|
|
1702
|
-
|
|
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),
|
|
1703
1885
|
style,
|
|
1704
1886
|
children
|
|
1705
1887
|
});
|
|
@@ -1708,8 +1890,9 @@ const SheetHeader = ({ asChild, className, style, children }) => {
|
|
|
1708
1890
|
//#region src/SheetParts/sheet-title.tsx
|
|
1709
1891
|
const SheetTitle = ({ asChild, className, style, children }) => {
|
|
1710
1892
|
const { panelId } = useSheetPanel();
|
|
1711
|
-
|
|
1712
|
-
|
|
1893
|
+
const isAsChild = asChild === true;
|
|
1894
|
+
return /* @__PURE__ */ jsx(isAsChild ? Slot : "h2", {
|
|
1895
|
+
className: joinClassNames(isAsChild ? void 0 : "font-semibold text-sm", className),
|
|
1713
1896
|
id: `${panelId}-title`,
|
|
1714
1897
|
style,
|
|
1715
1898
|
children
|