@patternmode/stacksheet 1.3.7 → 1.3.8
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-velocity.d.ts +24 -0
- package/dist/Drag/drag-velocity.d.ts.map +1 -0
- package/dist/Drag/use-drag.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel-focus.d.ts.map +1 -1
- package/dist/SheetPanel/sheet-panel.d.ts.map +1 -1
- package/dist/SheetParts/sheet-title.d.ts.map +1 -1
- package/dist/Store/store-args.d.ts +6 -0
- package/dist/Store/store-args.d.ts.map +1 -1
- package/dist/index.mjs +146 -55
- package/dist/index.mjs.map +1 -1
- package/dist/panel-context.d.ts +4 -0
- package/dist/panel-context.d.ts.map +1 -1
- package/dist/renderer-effects.d.ts.map +1 -1
- package/dist/renderer-helpers.d.ts +9 -1
- package/dist/renderer-helpers.d.ts.map +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** A single pointer sample recorded during a drag gesture. */
|
|
2
|
+
export interface VelocitySample {
|
|
3
|
+
/** Drag offset in the dismiss direction (px) at sample time */
|
|
4
|
+
offset: number;
|
|
5
|
+
/** Timestamp in ms (Date.now()) */
|
|
6
|
+
time: number;
|
|
7
|
+
}
|
|
8
|
+
/** Sliding window (ms) of samples used to compute release velocity. */
|
|
9
|
+
export declare const VELOCITY_WINDOW_MS = 100;
|
|
10
|
+
/**
|
|
11
|
+
* Append a pointer sample, pruning entries that fall outside the sliding
|
|
12
|
+
* window (plus a hard cap as a memory guard). Mutates and returns `samples`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const appendVelocitySample: (samples: VelocitySample[], sample: VelocitySample) => VelocitySample[];
|
|
15
|
+
/**
|
|
16
|
+
* Compute release velocity (px/ms, positive = dismiss direction) from the
|
|
17
|
+
* samples recorded within the sliding window before `releaseTime`.
|
|
18
|
+
*
|
|
19
|
+
* Using only recent samples means a pause followed by a flick reports the
|
|
20
|
+
* flick's velocity — not the whole-gesture average, which would dilute it
|
|
21
|
+
* to near zero.
|
|
22
|
+
*/
|
|
23
|
+
export declare const getReleaseVelocity: (samples: VelocitySample[], releaseTime: number) => number;
|
|
24
|
+
//# sourceMappingURL=drag-velocity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-velocity.d.ts","sourceRoot":"","sources":["../../src/Drag/drag-velocity.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAKtC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,cAAc,EAAE,EACzB,QAAQ,cAAc,KACrB,cAAc,EAOhB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,cAAc,EAAE,EAAE,aAAa,MAAM,KAAG,MASnF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-drag.d.ts","sourceRoot":"","sources":["../../src/Drag/use-drag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUvC,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"use-drag.d.ts","sourceRoot":"","sources":["../../src/Drag/use-drag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUvC,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAqJ1D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,OAAO,GAClB,UAAU,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAC1C,QAAQ,UAAU,EAClB,cAAc,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,SAyDzC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet-panel-focus.d.ts","sourceRoot":"","sources":["../../src/SheetPanel/sheet-panel-focus.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAsClD,eAAO,MAAM,cAAc,GAAI,6CAK5B;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC3C,QAAQ,EAAE,SAAS,CAAC;CACrB,KAAG,
|
|
1
|
+
{"version":3,"file":"sheet-panel-focus.d.ts","sourceRoot":"","sources":["../../src/SheetPanel/sheet-panel-focus.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAsClD,eAAO,MAAM,cAAc,GAAI,6CAK5B;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC3C,QAAQ,EAAE,SAAS,CAAC;CACrB,KAAG,SA2BH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet-panel.d.ts","sourceRoot":"","sources":["../../src/SheetPanel/sheet-panel.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"sheet-panel.d.ts","sourceRoot":"","sources":["../../src/SheetPanel/sheet-panel.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAyT3D,eAAO,MAAM,UAAU,GAAI,OAAO,eAAe,gCAiFhD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet-title.d.ts","sourceRoot":"","sources":["../../src/SheetParts/sheet-title.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sheet-title.d.ts","sourceRoot":"","sources":["../../src/SheetParts/sheet-title.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,eAAO,MAAM,UAAU,GAAI,yCAAyC,cAAc,gCAWjF,CAAC"}
|
|
@@ -9,6 +9,12 @@ declare global {
|
|
|
9
9
|
}
|
|
10
10
|
export declare const toRecord: (value: unknown) => Record<string, unknown>;
|
|
11
11
|
export declare const isComponent: (value: unknown) => value is AnyComponent;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a unique sheet id. `crypto.randomUUID` is only available in
|
|
14
|
+
* secure contexts, so fall back to a timestamp + random suffix on
|
|
15
|
+
* non-secure origins (e.g. plain-HTTP LAN dev servers).
|
|
16
|
+
*/
|
|
17
|
+
export declare const generateSheetId: () => string;
|
|
12
18
|
export declare const getStringArg: (value: unknown, name: string) => string;
|
|
13
19
|
export declare const resolvePresentationOptions: (value: unknown) => SheetPresentationOptions | undefined;
|
|
14
20
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-args.d.ts","sourceRoot":"","sources":["../../src/Store/store-args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,OAAO,EACP,SAAS,GACT;QACE,GAAG,CAAC,EAAE;YACJ,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;CACP;AAKD,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAmC,CAAC;AAEpG,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,YAA2C,CAAC;AASlG,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,KAAG,MAK3D,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,OAAO,OAAO,KACb,wBAAwB,GAAG,SAS7B,CAAC;AACF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,WAAW,YAAY,EACvB,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAC5C,aAAa,GAAG,CAAC,MAAM,CAAC,KACvB,IAyBF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,YAAY,EACvB,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAC5C,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACvC,YAAY,MAAM,MAAM,EACxB,aAAa,GAAG,CAAC,MAAM,CAAC,KACvB,MAWF,CAAC;AACF;;;GAGG;AACH,eAAO,MAAM,WAAW,GACtB,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAC5C,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACvC,YAAY,MAAM,MAAM,EACxB,aAAa,GAAG,CAAC,MAAM,CAAC,EACxB,OAAO,OAAO,EACd,QAAQ,OAAO,EACf,OAAO,OAAO,EACd,SAAS,OAAO,KACf,YA8BF,CAAC"}
|
|
1
|
+
{"version":3,"file":"store-args.d.ts","sourceRoot":"","sources":["../../src/Store/store-args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,OAAO,EACP,SAAS,GACT;QACE,GAAG,CAAC,EAAE;YACJ,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;CACP;AAKD,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAmC,CAAC;AAEpG,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,YAA2C,CAAC;AASlG;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAO,MAKlC,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,KAAG,MAK3D,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,OAAO,OAAO,KACb,wBAAwB,GAAG,SAS7B,CAAC;AACF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,WAAW,YAAY,EACvB,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAC5C,aAAa,GAAG,CAAC,MAAM,CAAC,KACvB,IAyBF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,YAAY,EACvB,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAC5C,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACvC,YAAY,MAAM,MAAM,EACxB,aAAa,GAAG,CAAC,MAAM,CAAC,KACvB,MAWF,CAAC;AACF;;;GAGG;AACH,eAAO,MAAM,WAAW,GACtB,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAC5C,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACvC,YAAY,MAAM,MAAM,EACxB,aAAa,GAAG,CAAC,MAAM,CAAC,EACxB,OAAO,OAAO,EACd,QAAQ,OAAO,EACf,OAAO,OAAO,EACd,SAAS,OAAO,KACf,YA8BF,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Portal } from "@radix-ui/react-portal";
|
|
3
|
-
import { createContext, use, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { createContext, use, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { createStore, useStore } from "zustand";
|
|
5
5
|
import { useShallow } from "zustand/react/shallow";
|
|
6
6
|
import { springs as springs$1 } from "@howells/motion";
|
|
@@ -169,38 +169,50 @@ const useViewportHeight = (active) => {
|
|
|
169
169
|
}, []);
|
|
170
170
|
return active ? height ?? 0 : 0;
|
|
171
171
|
};
|
|
172
|
+
const BODY_SCALE_TRANSITION = "transform 500ms cubic-bezier(0.32, 0.72, 0, 1), border-radius 500ms cubic-bezier(0.32, 0.72, 0, 1)";
|
|
173
|
+
/** Fallback delay (transition duration + margin) if `transitionend` never fires. */
|
|
174
|
+
const BODY_SCALE_RESET_FALLBACK_MS = 600;
|
|
175
|
+
/** Cancels a pending un-scale reset when the sheet reopens mid-animation. */
|
|
176
|
+
let cancelPendingBodyScaleReset;
|
|
172
177
|
const useBodyScale = (config, isOpen, prefersReducedMotion) => {
|
|
173
178
|
useEffect(() => {
|
|
174
179
|
const wrapper = document.querySelector("[data-stacksheet-wrapper]");
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
};
|
|
184
|
-
const canScale = config.shouldScaleBackground && !prefersReducedMotion && wrapper instanceof HTMLElement;
|
|
185
|
-
if (canScale && isOpen) {
|
|
186
|
-
const scale = config.scaleBackgroundAmount;
|
|
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)";
|
|
188
|
-
wrapper.style.transform = `scale(${scale})`;
|
|
189
|
-
wrapper.style.borderRadius = "8px";
|
|
190
|
-
wrapper.style.overflow = "hidden";
|
|
191
|
-
wrapper.style.transformOrigin = "center top";
|
|
192
|
-
return resetWrapper;
|
|
193
|
-
}
|
|
194
|
-
const handleEnd = () => {
|
|
195
|
-
resetWrapper();
|
|
196
|
-
};
|
|
197
|
-
if (canScale) {
|
|
198
|
-
wrapper.style.transform = "";
|
|
199
|
-
wrapper.style.borderRadius = "";
|
|
200
|
-
wrapper.addEventListener("transitionend", handleEnd, { once: true });
|
|
180
|
+
const scalable = config.shouldScaleBackground && !prefersReducedMotion && wrapper instanceof HTMLElement && isOpen ? wrapper : null;
|
|
181
|
+
if (scalable !== null) {
|
|
182
|
+
cancelPendingBodyScaleReset?.();
|
|
183
|
+
scalable.style.transition = BODY_SCALE_TRANSITION;
|
|
184
|
+
scalable.style.transform = `scale(${config.scaleBackgroundAmount})`;
|
|
185
|
+
scalable.style.borderRadius = "8px";
|
|
186
|
+
scalable.style.overflow = "hidden";
|
|
187
|
+
scalable.style.transformOrigin = "center top";
|
|
201
188
|
}
|
|
202
189
|
return () => {
|
|
203
|
-
if (
|
|
190
|
+
if (scalable === null) return;
|
|
191
|
+
scalable.style.transition = BODY_SCALE_TRANSITION;
|
|
192
|
+
scalable.style.transform = "";
|
|
193
|
+
scalable.style.borderRadius = "";
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const { signal } = controller;
|
|
196
|
+
const finish = () => {
|
|
197
|
+
controller.abort();
|
|
198
|
+
cancelPendingBodyScaleReset = void 0;
|
|
199
|
+
scalable.style.transition = "";
|
|
200
|
+
scalable.style.overflow = "";
|
|
201
|
+
scalable.style.transformOrigin = "";
|
|
202
|
+
};
|
|
203
|
+
scalable.addEventListener("transitionend", (event) => {
|
|
204
|
+
if (event.target === scalable && event.propertyName === "transform") finish();
|
|
205
|
+
}, { signal });
|
|
206
|
+
const timeoutId = setTimeout(() => {
|
|
207
|
+
if (!signal.aborted) finish();
|
|
208
|
+
}, BODY_SCALE_RESET_FALLBACK_MS);
|
|
209
|
+
signal.addEventListener("abort", () => {
|
|
210
|
+
clearTimeout(timeoutId);
|
|
211
|
+
});
|
|
212
|
+
cancelPendingBodyScaleReset = () => {
|
|
213
|
+
controller.abort();
|
|
214
|
+
cancelPendingBodyScaleReset = void 0;
|
|
215
|
+
};
|
|
204
216
|
};
|
|
205
217
|
}, [
|
|
206
218
|
isOpen,
|
|
@@ -318,12 +330,13 @@ const resolveClassNames = (cn) => {
|
|
|
318
330
|
panel: cn.panel ?? ""
|
|
319
331
|
};
|
|
320
332
|
};
|
|
321
|
-
const buildAriaProps = (
|
|
333
|
+
const buildAriaProps = ({ ariaLabel, hasDescription, hasTitle, isComposable, isModal, isTop, panelId }) => {
|
|
322
334
|
if (!isTop) return {};
|
|
323
335
|
const props = { role: "dialog" };
|
|
324
336
|
if (isModal) props["aria-modal"] = "true";
|
|
325
337
|
if (isComposable) {
|
|
326
|
-
props["aria-labelledby"] = `${panelId}-title`;
|
|
338
|
+
if (hasTitle) props["aria-labelledby"] = `${panelId}-title`;
|
|
339
|
+
else props["aria-label"] = ariaLabel;
|
|
327
340
|
if (hasDescription) props["aria-describedby"] = `${panelId}-desc`;
|
|
328
341
|
} else props["aria-label"] = ariaLabel;
|
|
329
342
|
return props;
|
|
@@ -504,12 +517,41 @@ const getPanelDimension = (panel, axis) => {
|
|
|
504
517
|
if (!panel) return 300;
|
|
505
518
|
return axis === "x" ? panel.offsetWidth : panel.offsetHeight;
|
|
506
519
|
};
|
|
520
|
+
/** Upper bound on retained samples — plenty for a 100ms window at 120Hz. */
|
|
521
|
+
const MAX_SAMPLES = 20;
|
|
522
|
+
/**
|
|
523
|
+
* Append a pointer sample, pruning entries that fall outside the sliding
|
|
524
|
+
* window (plus a hard cap as a memory guard). Mutates and returns `samples`.
|
|
525
|
+
*/
|
|
526
|
+
const appendVelocitySample = (samples, sample) => {
|
|
527
|
+
samples.push(sample);
|
|
528
|
+
const cutoff = sample.time - 100;
|
|
529
|
+
while (samples.length > MAX_SAMPLES || samples.length > 2 && (samples[0]?.time ?? 0) < cutoff) samples.shift();
|
|
530
|
+
return samples;
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
* Compute release velocity (px/ms, positive = dismiss direction) from the
|
|
534
|
+
* samples recorded within the sliding window before `releaseTime`.
|
|
535
|
+
*
|
|
536
|
+
* Using only recent samples means a pause followed by a flick reports the
|
|
537
|
+
* flick's velocity — not the whole-gesture average, which would dilute it
|
|
538
|
+
* to near zero.
|
|
539
|
+
*/
|
|
540
|
+
const getReleaseVelocity = (samples, releaseTime) => {
|
|
541
|
+
const cutoff = releaseTime - 100;
|
|
542
|
+
const recent = samples.filter((sample) => sample.time >= cutoff);
|
|
543
|
+
const [first] = recent;
|
|
544
|
+
const last = recent.at(-1);
|
|
545
|
+
if (first === void 0 || last === void 0 || last.time <= first.time) return 0;
|
|
546
|
+
return (last.offset - first.offset) / (last.time - first.time);
|
|
547
|
+
};
|
|
507
548
|
//#endregion
|
|
508
549
|
//#region src/Drag/use-drag.ts
|
|
509
|
-
const resetDragRefs = ({ committedRef, offsetRef, scrollTargetRef, startRef }) => {
|
|
550
|
+
const resetDragRefs = ({ committedRef, offsetRef, samplesRef, scrollTargetRef, startRef }) => {
|
|
510
551
|
startRef.current = null;
|
|
511
552
|
committedRef.current = null;
|
|
512
553
|
offsetRef.current = 0;
|
|
554
|
+
samplesRef.current = [];
|
|
513
555
|
scrollTargetRef.current = null;
|
|
514
556
|
};
|
|
515
557
|
const createDragHandlers = ({ axis, config, onDragUpdate, panelRef, refs, sign }) => {
|
|
@@ -524,13 +566,15 @@ const createDragHandlers = ({ axis, config, onDragUpdate, panelRef, refs, sign }
|
|
|
524
566
|
if (!isHandle && isInteractiveElement(target)) return;
|
|
525
567
|
refs.scrollTargetRef.current = isHandle ? null : findScrollableAncestor(target, axis);
|
|
526
568
|
refs.startRef.current = {
|
|
527
|
-
time: Date.now(),
|
|
528
569
|
x: e.clientX,
|
|
529
570
|
y: e.clientY
|
|
530
571
|
};
|
|
531
572
|
refs.committedRef.current = null;
|
|
532
573
|
refs.offsetRef.current = 0;
|
|
533
|
-
|
|
574
|
+
refs.samplesRef.current = [{
|
|
575
|
+
offset: 0,
|
|
576
|
+
time: Date.now()
|
|
577
|
+
}];
|
|
534
578
|
};
|
|
535
579
|
const handlePointerMove = (e) => {
|
|
536
580
|
if (refs.startRef.current === null) return;
|
|
@@ -544,11 +588,16 @@ const createDragHandlers = ({ axis, config, onDragUpdate, panelRef, refs, sign }
|
|
|
544
588
|
refs.startRef.current = null;
|
|
545
589
|
return;
|
|
546
590
|
}
|
|
591
|
+
if (e.currentTarget instanceof HTMLElement) e.currentTarget.setPointerCapture(e.pointerId);
|
|
547
592
|
}
|
|
548
593
|
if (refs.committedRef.current !== "drag") return;
|
|
549
594
|
const directional = (axis === "x" ? dx : dy) * sign;
|
|
550
595
|
const clampedOffset = directional >= 0 ? directional : -Math.sqrt(Math.abs(directional)) * RUBBER_BAND_FACTOR;
|
|
551
596
|
refs.offsetRef.current = clampedOffset;
|
|
597
|
+
appendVelocitySample(refs.samplesRef.current, {
|
|
598
|
+
offset: clampedOffset,
|
|
599
|
+
time: Date.now()
|
|
600
|
+
});
|
|
552
601
|
onDragUpdate({
|
|
553
602
|
isDragging: true,
|
|
554
603
|
offset: clampedOffset
|
|
@@ -561,8 +610,7 @@ const createDragHandlers = ({ axis, config, onDragUpdate, panelRef, refs, sign }
|
|
|
561
610
|
return;
|
|
562
611
|
}
|
|
563
612
|
const offset = Math.max(0, refs.offsetRef.current);
|
|
564
|
-
const
|
|
565
|
-
const velocity = elapsed > 0 ? offset / elapsed : 0;
|
|
613
|
+
const velocity = getReleaseVelocity(refs.samplesRef.current, Date.now());
|
|
566
614
|
resetDragRefs(refs);
|
|
567
615
|
const panelSize = getPanelDimension(panelRef.current, axis);
|
|
568
616
|
if (config.snapHeights.length > 0) {
|
|
@@ -619,6 +667,7 @@ const useDrag = (panelRef, config, onDragUpdate) => {
|
|
|
619
667
|
const startRef = useRef(null);
|
|
620
668
|
const committedRef = useRef(null);
|
|
621
669
|
const offsetRef = useRef(0);
|
|
670
|
+
const samplesRef = useRef([]);
|
|
622
671
|
const scrollTargetRef = useRef(null);
|
|
623
672
|
const { axis, sign } = getDismissAxis(config.side);
|
|
624
673
|
const { handlePointerCancel, handlePointerDown, handlePointerMove, handlePointerUp } = createDragHandlers({
|
|
@@ -629,6 +678,7 @@ const useDrag = (panelRef, config, onDragUpdate) => {
|
|
|
629
678
|
refs: {
|
|
630
679
|
committedRef,
|
|
631
680
|
offsetRef,
|
|
681
|
+
samplesRef,
|
|
632
682
|
scrollTargetRef,
|
|
633
683
|
startRef
|
|
634
684
|
},
|
|
@@ -896,7 +946,7 @@ const ModalFocusTrap = ({ enabled, active, fallbackRef, children }) => {
|
|
|
896
946
|
if (fallbackRef.current !== null) return fallbackRef.current;
|
|
897
947
|
return document.body;
|
|
898
948
|
},
|
|
899
|
-
initialFocus:
|
|
949
|
+
initialFocus: () => fallbackRef.current ?? void 0,
|
|
900
950
|
returnFocusOnDeactivate: true
|
|
901
951
|
},
|
|
902
952
|
paused,
|
|
@@ -969,14 +1019,16 @@ const getHeaderProps = ({ close, isNested, pop, side }) => ({
|
|
|
969
1019
|
onClose: close,
|
|
970
1020
|
side
|
|
971
1021
|
});
|
|
972
|
-
const getPanelContext = ({ close, hasDescription, isNested, isTop, panelId, pop, registerDescription, side }) => ({
|
|
1022
|
+
const getPanelContext = ({ close, hasDescription, hasTitle, isNested, isTop, panelId, pop, registerDescription, registerTitle, side }) => ({
|
|
973
1023
|
back: pop,
|
|
974
1024
|
close,
|
|
975
1025
|
hasDescription,
|
|
1026
|
+
hasTitle,
|
|
976
1027
|
isNested,
|
|
977
1028
|
isTop,
|
|
978
1029
|
panelId,
|
|
979
1030
|
registerDescription,
|
|
1031
|
+
registerTitle,
|
|
980
1032
|
side
|
|
981
1033
|
});
|
|
982
1034
|
const getOptionalSideHandle = ({ isHovered, onDismiss, show, side }) => show ? /* @__PURE__ */ jsx(SideHandle, {
|
|
@@ -1024,24 +1076,45 @@ const useSheetPanelDrag = ({ activeSnapIndex, config, isNested, isTop, onSnap, p
|
|
|
1024
1076
|
};
|
|
1025
1077
|
const useSheetPanelContext = ({ close, isNested, isTop, pop, side }, panelId) => {
|
|
1026
1078
|
const [hasDescription, setHasDescription] = useState(false);
|
|
1027
|
-
const
|
|
1079
|
+
const [hasTitle, setHasTitle] = useState(false);
|
|
1080
|
+
const registerDescription = useCallback(() => {
|
|
1028
1081
|
setHasDescription(true);
|
|
1029
1082
|
return () => {
|
|
1030
1083
|
setHasDescription(false);
|
|
1031
1084
|
};
|
|
1032
|
-
};
|
|
1085
|
+
}, []);
|
|
1086
|
+
const registerTitle = useCallback(() => {
|
|
1087
|
+
setHasTitle(true);
|
|
1088
|
+
return () => {
|
|
1089
|
+
setHasTitle(false);
|
|
1090
|
+
};
|
|
1091
|
+
}, []);
|
|
1033
1092
|
return {
|
|
1034
1093
|
hasDescription,
|
|
1035
|
-
|
|
1094
|
+
hasTitle,
|
|
1095
|
+
panelContext: useMemo(() => getPanelContext({
|
|
1036
1096
|
close,
|
|
1037
1097
|
hasDescription,
|
|
1098
|
+
hasTitle,
|
|
1038
1099
|
isNested,
|
|
1039
1100
|
isTop,
|
|
1040
1101
|
panelId,
|
|
1041
1102
|
pop,
|
|
1042
1103
|
registerDescription,
|
|
1104
|
+
registerTitle,
|
|
1043
1105
|
side
|
|
1044
|
-
})
|
|
1106
|
+
}), [
|
|
1107
|
+
close,
|
|
1108
|
+
hasDescription,
|
|
1109
|
+
hasTitle,
|
|
1110
|
+
isNested,
|
|
1111
|
+
isTop,
|
|
1112
|
+
panelId,
|
|
1113
|
+
pop,
|
|
1114
|
+
registerDescription,
|
|
1115
|
+
registerTitle,
|
|
1116
|
+
side
|
|
1117
|
+
])
|
|
1045
1118
|
};
|
|
1046
1119
|
};
|
|
1047
1120
|
const useSheetPanelModel = (props) => {
|
|
@@ -1055,7 +1128,7 @@ const useSheetPanelModel = (props) => {
|
|
|
1055
1128
|
const dragState = useSheetPanelDrag(props, panelRef);
|
|
1056
1129
|
const ariaLabel = getPanelAriaLabel(item, config.ariaLabel);
|
|
1057
1130
|
const panelId = `stacksheet-${item.id}`;
|
|
1058
|
-
const { hasDescription, panelContext } = useSheetPanelContext(props, panelId);
|
|
1131
|
+
const { hasDescription, hasTitle, panelContext } = useSheetPanelContext(props, panelId);
|
|
1059
1132
|
const isComposable = resolvePanelLayout(layout, renderHeader) === "composable";
|
|
1060
1133
|
const hasPanelClass = classNames.panel !== "";
|
|
1061
1134
|
const dragOffset = getDragTransform(side, dragState.offset);
|
|
@@ -1066,7 +1139,15 @@ const useSheetPanelModel = (props) => {
|
|
|
1066
1139
|
pop,
|
|
1067
1140
|
side
|
|
1068
1141
|
});
|
|
1069
|
-
const ariaProps = buildAriaProps(
|
|
1142
|
+
const ariaProps = buildAriaProps({
|
|
1143
|
+
ariaLabel,
|
|
1144
|
+
hasDescription,
|
|
1145
|
+
hasTitle,
|
|
1146
|
+
isComposable,
|
|
1147
|
+
isModal: config.modal,
|
|
1148
|
+
isTop,
|
|
1149
|
+
panelId
|
|
1150
|
+
});
|
|
1070
1151
|
const transition = buildPanelTransition(dragState.isDragging, isTop, spring, stackSpring);
|
|
1071
1152
|
const animatedRadius = getAnimatedBorderRadius(side, depth, config.stacking);
|
|
1072
1153
|
const snapYOffset = computeSnapYOffset(side, snapHeights, activeSnapIndex, measuredHeight);
|
|
@@ -1275,15 +1356,14 @@ const useDismissalEffects = ({ closeReasonRef, config, isOpen, rawClose, rawPop,
|
|
|
1275
1356
|
useEffect(() => {
|
|
1276
1357
|
const shouldListen = isOpen && config.closeOnEscape && config.dismissible;
|
|
1277
1358
|
const handleKeyDown = (e) => {
|
|
1278
|
-
if (e.key
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1359
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
1360
|
+
e.preventDefault();
|
|
1361
|
+
dismissFromEscape({
|
|
1362
|
+
closeReasonRef,
|
|
1363
|
+
rawClose,
|
|
1364
|
+
rawPop,
|
|
1365
|
+
stackLengthRef
|
|
1366
|
+
});
|
|
1287
1367
|
};
|
|
1288
1368
|
if (shouldListen) document.addEventListener("keydown", handleKeyDown);
|
|
1289
1369
|
return () => {
|
|
@@ -1299,7 +1379,7 @@ const useDismissalEffects = ({ closeReasonRef, config, isOpen, rawClose, rawPop,
|
|
|
1299
1379
|
]);
|
|
1300
1380
|
useEffect(() => {
|
|
1301
1381
|
const CloseWatcherConstructor = globalThis.CloseWatcher;
|
|
1302
|
-
const shouldListen = isOpen && config.dismissible && CloseWatcherConstructor !== void 0;
|
|
1382
|
+
const shouldListen = isOpen && config.closeOnEscape && config.dismissible && CloseWatcherConstructor !== void 0;
|
|
1303
1383
|
let watcher;
|
|
1304
1384
|
const handleClose = () => {
|
|
1305
1385
|
dismissFromEscape({
|
|
@@ -1321,6 +1401,7 @@ const useDismissalEffects = ({ closeReasonRef, config, isOpen, rawClose, rawPop,
|
|
|
1321
1401
|
};
|
|
1322
1402
|
}, [
|
|
1323
1403
|
isOpen,
|
|
1404
|
+
config.closeOnEscape,
|
|
1324
1405
|
config.dismissible,
|
|
1325
1406
|
rawPop,
|
|
1326
1407
|
rawClose,
|
|
@@ -1438,6 +1519,15 @@ const getComponentName = (component) => {
|
|
|
1438
1519
|
return name === "" ? void 0 : name;
|
|
1439
1520
|
};
|
|
1440
1521
|
const getNodeEnv = () => globalThis.process?.env?.NODE_ENV;
|
|
1522
|
+
/**
|
|
1523
|
+
* Generate a unique sheet id. `crypto.randomUUID` is only available in
|
|
1524
|
+
* secure contexts, so fall back to a timestamp + random suffix on
|
|
1525
|
+
* non-secure origins (e.g. plain-HTTP LAN dev servers).
|
|
1526
|
+
*/
|
|
1527
|
+
const generateSheetId = () => {
|
|
1528
|
+
if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
|
|
1529
|
+
return `sheet-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1530
|
+
};
|
|
1441
1531
|
const getStringArg = (value, name) => {
|
|
1442
1532
|
if (typeof value !== "string") throw new TypeError(`Expected ${name} to be a string.`);
|
|
1443
1533
|
return value;
|
|
@@ -1489,7 +1579,7 @@ const resolveArgs = (componentRegistry, componentMap, getNextKey, warnedNames, f
|
|
|
1489
1579
|
return {
|
|
1490
1580
|
ariaLabel: resolvePresentationOptions(third)?.ariaLabel,
|
|
1491
1581
|
data: toRecord(second),
|
|
1492
|
-
id:
|
|
1582
|
+
id: generateSheetId(),
|
|
1493
1583
|
type: typeKey
|
|
1494
1584
|
};
|
|
1495
1585
|
}
|
|
@@ -1889,7 +1979,8 @@ const SheetHeader = ({ asChild, className, style, children }) => {
|
|
|
1889
1979
|
//#endregion
|
|
1890
1980
|
//#region src/SheetParts/sheet-title.tsx
|
|
1891
1981
|
const SheetTitle = ({ asChild, className, style, children }) => {
|
|
1892
|
-
const { panelId } = useSheetPanel();
|
|
1982
|
+
const { panelId, registerTitle } = useSheetPanel();
|
|
1983
|
+
useEffect(() => registerTitle(), [registerTitle]);
|
|
1893
1984
|
const isAsChild = asChild === true;
|
|
1894
1985
|
return /* @__PURE__ */ jsx(isAsChild ? Slot : "h2", {
|
|
1895
1986
|
className: joinClassNames(isAsChild ? void 0 : "font-semibold text-sm", className),
|