@surdeddd/bottom-sheet 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -21
- package/dist/BottomSheetEngine-CAymrH-P.d.cts +285 -0
- package/dist/BottomSheetEngine-DcqcuudN.d.ts +285 -0
- package/dist/element.cjs +775 -537
- package/dist/element.d.cts +9 -57
- package/dist/element.d.ts +9 -57
- package/dist/element.iife.global.js +2 -2
- package/dist/element.js +775 -537
- package/dist/index.cjs +760 -600
- package/dist/index.d.cts +18 -85
- package/dist/index.d.ts +18 -85
- package/dist/index.js +758 -601
- package/dist/integrations/formik.d.cts +2 -8
- package/dist/integrations/formik.d.ts +2 -8
- package/dist/integrations/react-hook-form.d.cts +2 -11
- package/dist/integrations/react-hook-form.d.ts +2 -11
- package/dist/overlay.cjs +168 -122
- package/dist/overlay.d.cts +14 -137
- package/dist/overlay.d.ts +14 -137
- package/dist/overlay.js +168 -122
- package/dist/preact.d.cts +2 -2
- package/dist/preact.d.ts +2 -2
- package/dist/preact.js +0 -1
- package/dist/qwik.cjs +667 -519
- package/dist/qwik.d.cts +3 -24
- package/dist/qwik.d.ts +3 -24
- package/dist/qwik.js +668 -520
- package/dist/react.cjs +873 -648
- package/dist/react.d.cts +34 -159
- package/dist/react.d.ts +34 -159
- package/dist/react.js +873 -648
- package/dist/solid.cjs +703 -560
- package/dist/solid.d.cts +264 -32
- package/dist/solid.d.ts +264 -32
- package/dist/solid.js +704 -561
- package/dist/solid.source.jsx +3215 -0
- package/dist/styles.css +37 -112
- package/dist/svelte-core.cjs +3149 -0
- package/dist/svelte-core.js +3147 -0
- package/dist/svelte-src/BottomSheet.svelte +210 -0
- package/dist/svelte-src/index.js +2 -0
- package/dist/svelte.cjs +718 -547
- package/dist/svelte.d.cts +100 -0
- package/dist/svelte.d.ts +15 -4
- package/dist/svelte.js +719 -548
- package/dist/themes/ios.css +0 -3
- package/dist/themes/material.css +0 -3
- package/dist/themes/vercel.css +0 -3
- package/dist/{transform-Brkf-wzo.d.cts → transform-jVfAikE9.d.cts} +0 -1
- package/dist/{transform-Brkf-wzo.d.ts → transform-jVfAikE9.d.ts} +0 -1
- package/dist/vue.cjs +677 -526
- package/dist/vue.d.cts +78 -0
- package/dist/vue.d.ts +12 -2
- package/dist/vue.js +678 -527
- package/package.json +105 -45
- package/dist/BottomSheetEngine-CvSVbo6c.d.cts +0 -704
- package/dist/BottomSheetEngine-DbO8rW_3.d.ts +0 -704
package/dist/qwik.cjs
CHANGED
|
@@ -25,6 +25,18 @@ var SheetStack = class {
|
|
|
25
25
|
this.entries.splice(idx, 1);
|
|
26
26
|
this.recompute();
|
|
27
27
|
}
|
|
28
|
+
promote(id) {
|
|
29
|
+
const idx = this.entries.findIndex((e) => e.id === id);
|
|
30
|
+
if (idx === -1) return;
|
|
31
|
+
if (idx !== this.entries.length - 1) {
|
|
32
|
+
const [entry] = this.entries.splice(idx, 1);
|
|
33
|
+
this.entries.push(entry);
|
|
34
|
+
}
|
|
35
|
+
this.recompute();
|
|
36
|
+
}
|
|
37
|
+
update() {
|
|
38
|
+
this.recompute();
|
|
39
|
+
}
|
|
28
40
|
size() {
|
|
29
41
|
return this.entries.length;
|
|
30
42
|
}
|
|
@@ -32,9 +44,24 @@ var SheetStack = class {
|
|
|
32
44
|
this.entries = [];
|
|
33
45
|
}
|
|
34
46
|
recompute() {
|
|
47
|
+
let top = null;
|
|
48
|
+
const open = [];
|
|
49
|
+
for (const entry of this.entries) {
|
|
50
|
+
if (!entry.isOpen || entry.isOpen()) {
|
|
51
|
+
top = entry;
|
|
52
|
+
open.push(entry);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!top && this.entries.length > 0) {
|
|
56
|
+
top = this.entries[this.entries.length - 1];
|
|
57
|
+
}
|
|
35
58
|
this.entries.forEach((entry, i) => {
|
|
36
59
|
entry.setZIndex(BASE_Z + i * STEP);
|
|
37
|
-
entry.setIsTop(
|
|
60
|
+
entry.setIsTop(entry === top);
|
|
61
|
+
if (entry.setDepth) {
|
|
62
|
+
const openIdx = open.indexOf(entry);
|
|
63
|
+
entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
|
|
64
|
+
}
|
|
38
65
|
});
|
|
39
66
|
}
|
|
40
67
|
};
|
|
@@ -233,11 +260,6 @@ var SnapResolver = class {
|
|
|
233
260
|
this.allowedIds = allowed ?? raw.map((p) => p.id);
|
|
234
261
|
this.recompute();
|
|
235
262
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Re-resolve raw → resolved against the current viewport / fit-measure,
|
|
238
|
-
* recompute maxAxisSize, invalidate range cache, notify engine. Called on
|
|
239
|
-
* resize, orientationchange, visualViewport changes, and snap-list swaps.
|
|
240
|
-
*/
|
|
241
263
|
recompute() {
|
|
242
264
|
this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
|
|
243
265
|
this.maxAxisSize = this.resolved.reduce(
|
|
@@ -247,15 +269,10 @@ var SnapResolver = class {
|
|
|
247
269
|
this.rangeCache = null;
|
|
248
270
|
this.onMaxAxisSizeChange?.(this.maxAxisSize);
|
|
249
271
|
}
|
|
250
|
-
/** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
|
|
251
272
|
setRaw(raw) {
|
|
252
273
|
this.raw = raw;
|
|
253
274
|
this.recompute();
|
|
254
275
|
}
|
|
255
|
-
/**
|
|
256
|
-
* Replace the allow-list. Range cache depends on `(resolvedSnaps ×
|
|
257
|
-
* allowedIds)` so flipping ids alone invalidates without a full recompute.
|
|
258
|
-
*/
|
|
259
276
|
setAllowedIds(ids) {
|
|
260
277
|
this.allowedIds = ids;
|
|
261
278
|
this.rangeCache = null;
|
|
@@ -263,11 +280,6 @@ var SnapResolver = class {
|
|
|
263
280
|
findById(id) {
|
|
264
281
|
return findById(id, this.resolved);
|
|
265
282
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Pure drag-settle target resolution. Engine passes the gesture-specific
|
|
268
|
-
* fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
|
|
269
|
-
* `size`/`activeId` since those are still engine-owned.
|
|
270
|
-
*/
|
|
271
283
|
findDragSettleTarget(args) {
|
|
272
284
|
return findDragSettleTarget({
|
|
273
285
|
...args,
|
|
@@ -276,18 +288,9 @@ var SnapResolver = class {
|
|
|
276
288
|
maxAxisSize: this.maxAxisSize
|
|
277
289
|
});
|
|
278
290
|
}
|
|
279
|
-
/**
|
|
280
|
-
* Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
|
|
281
|
-
* applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
|
|
282
|
-
* to one recompute per geometry/allow-list change.
|
|
283
|
-
*/
|
|
284
291
|
getAllowedRange() {
|
|
285
292
|
return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
|
|
286
293
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Read-only view of resolved snaps. Returned as `readonly` to discourage
|
|
289
|
-
* accidental mutation by feature install sites that close over this list.
|
|
290
|
-
*/
|
|
291
294
|
getResolvedSnaps() {
|
|
292
295
|
return this.resolved;
|
|
293
296
|
}
|
|
@@ -297,18 +300,6 @@ var SnapResolver = class {
|
|
|
297
300
|
getMaxAxisSize() {
|
|
298
301
|
return this.maxAxisSize;
|
|
299
302
|
}
|
|
300
|
-
/**
|
|
301
|
-
* External clamp override. The resize-observer feature compares
|
|
302
|
-
* `getMaxAxisSize()` against the live viewport and shrinks if the largest
|
|
303
|
-
* resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
|
|
304
|
-
* window resize). The next `recompute()` overwrites this clamp from the
|
|
305
|
-
* resolved-snap reduction.
|
|
306
|
-
*
|
|
307
|
-
* Does NOT invalidate the range cache — viewport clamping doesn't change
|
|
308
|
-
* `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
|
|
309
|
-
* so the resolver owns the layout-axis style write end-to-end (symmetric
|
|
310
|
-
* with `recompute()` — feature call sites no longer duplicate the write).
|
|
311
|
-
*/
|
|
312
303
|
setMaxAxisSize(n) {
|
|
313
304
|
this.maxAxisSize = n;
|
|
314
305
|
this.onMaxAxisSizeChange?.(n);
|
|
@@ -355,14 +346,17 @@ function installAutoCollapse(deps) {
|
|
|
355
346
|
const fire = () => {
|
|
356
347
|
if (deps.isDestroyed()) return;
|
|
357
348
|
if (deps.isDragging()) return;
|
|
358
|
-
const
|
|
359
|
-
|
|
349
|
+
const active = deps.resolveSnap(deps.getActiveId());
|
|
350
|
+
if (!active || active.size === 0) return;
|
|
351
|
+
let target = null;
|
|
352
|
+
for (const id of deps.getAllowedIds()) {
|
|
360
353
|
const snap = deps.resolveSnap(id);
|
|
361
|
-
|
|
362
|
-
|
|
354
|
+
if (!snap || snap.size <= 0) continue;
|
|
355
|
+
if (!target || snap.size < target.size) target = snap;
|
|
356
|
+
}
|
|
363
357
|
if (!target) return;
|
|
364
|
-
if (target ===
|
|
365
|
-
deps.snapTo(target);
|
|
358
|
+
if (target.id === active.id || target.size >= active.size) return;
|
|
359
|
+
deps.snapTo(target.id);
|
|
366
360
|
};
|
|
367
361
|
const reset = () => {
|
|
368
362
|
clearTimer();
|
|
@@ -373,6 +367,7 @@ function installAutoCollapse(deps) {
|
|
|
373
367
|
}, ms);
|
|
374
368
|
};
|
|
375
369
|
const unsubscribe = deps.on("snap", () => reset());
|
|
370
|
+
if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
|
|
376
371
|
return () => {
|
|
377
372
|
clearTimer();
|
|
378
373
|
unsubscribe();
|
|
@@ -385,6 +380,7 @@ function notifyLinkedSheets(sheets, self) {
|
|
|
385
380
|
for (const linked of sheets) {
|
|
386
381
|
if (linked === self) continue;
|
|
387
382
|
try {
|
|
383
|
+
if (linked.state.size === 0) continue;
|
|
388
384
|
const ids = linked.getAllowedIds();
|
|
389
385
|
const target = ids.find((id) => id !== "closed");
|
|
390
386
|
if (target && target !== linked.state.activeId) {
|
|
@@ -431,7 +427,7 @@ function installRoute(deps) {
|
|
|
431
427
|
const onPop = (event) => {
|
|
432
428
|
if (deps.isDestroyed()) return;
|
|
433
429
|
const state = event.state;
|
|
434
|
-
if (state
|
|
430
|
+
if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
|
|
435
431
|
routePushed = false;
|
|
436
432
|
void deps.close();
|
|
437
433
|
}
|
|
@@ -585,13 +581,17 @@ function installResizeObserver(deps) {
|
|
|
585
581
|
if (viewportSize2 > 0 && viewportSize2 < max) {
|
|
586
582
|
deps.setMaxAxisSize(viewportSize2);
|
|
587
583
|
}
|
|
584
|
+
const wasAnimating = deps.isAnimating?.() ?? false;
|
|
588
585
|
deps.cancelInFlight();
|
|
589
586
|
deps.newCycle();
|
|
590
587
|
const current = deps.resolveActiveSnap();
|
|
591
588
|
const newMax = deps.getMaxAxisSize();
|
|
592
589
|
const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
|
|
593
590
|
deps.setSize(targetSize);
|
|
594
|
-
if (current && !deps.isDragging())
|
|
591
|
+
if (current && !deps.isDragging()) {
|
|
592
|
+
deps.applySize(targetSize);
|
|
593
|
+
if (wasAnimating) deps.resyncAfterCancel?.();
|
|
594
|
+
}
|
|
595
595
|
};
|
|
596
596
|
let resizeObserver = null;
|
|
597
597
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -683,73 +683,130 @@ var WriteSentinel = class {
|
|
|
683
683
|
constructor() {
|
|
684
684
|
__publicField(this, "last", -1);
|
|
685
685
|
}
|
|
686
|
-
/**
|
|
687
|
-
* Returns `true` (and updates the cached value) if `next` differs from the
|
|
688
|
-
* cached `last` by more than `epsilon`. Returns `false` (and leaves the
|
|
689
|
-
* cache untouched) when the delta is within epsilon — caller skips the
|
|
690
|
-
* CSSOM write.
|
|
691
|
-
*/
|
|
692
686
|
shouldWrite(next, epsilon) {
|
|
693
687
|
if (Math.abs(next - this.last) <= epsilon) return false;
|
|
694
688
|
this.last = next;
|
|
695
689
|
return true;
|
|
696
690
|
}
|
|
697
|
-
/**
|
|
698
|
-
* Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
|
|
699
|
-
* non-negative `next` exceeds the epsilon comparison. Called from setters
|
|
700
|
-
* that mutate state under the cache (range/mode/enabled flips that change
|
|
701
|
-
* the progress→opacity mapping but don't move `progress` itself).
|
|
702
|
-
*/
|
|
703
691
|
invalidate() {
|
|
704
692
|
this.last = -1;
|
|
705
693
|
}
|
|
706
|
-
/**
|
|
707
|
-
* Record `value` as the last-written without performing a write. Used
|
|
708
|
-
* when a setter writes a literal value through a different path (e.g.
|
|
709
|
-
* `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
|
|
710
|
-
* `shouldWrite` calls to short-circuit against that known cached state.
|
|
711
|
-
*/
|
|
712
694
|
setLastWritten(value) {
|
|
713
695
|
this.last = value;
|
|
714
696
|
}
|
|
715
|
-
/** Read-only accessor for diagnostic / dedup-aware peek. */
|
|
716
697
|
get value() {
|
|
717
698
|
return this.last;
|
|
718
699
|
}
|
|
719
700
|
};
|
|
720
701
|
|
|
702
|
+
// src/core/primitives/devWarn.ts
|
|
703
|
+
function devWarn(...args) {
|
|
704
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
|
|
705
|
+
console.warn(...args);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/core/primitives/overlay-position.ts
|
|
710
|
+
function resolveSheetAnchoredStyle(mode, position, inset) {
|
|
711
|
+
const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
|
|
712
|
+
switch (mode) {
|
|
713
|
+
case "bottom":
|
|
714
|
+
if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
|
|
715
|
+
if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
|
|
716
|
+
return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
717
|
+
case "top":
|
|
718
|
+
if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
|
|
719
|
+
if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
|
|
720
|
+
return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
721
|
+
case "left":
|
|
722
|
+
if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
|
|
723
|
+
if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
|
|
724
|
+
return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
725
|
+
case "right":
|
|
726
|
+
if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
|
|
727
|
+
if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
|
|
728
|
+
return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function applyOverlayPosition(ws, mode, position, inset) {
|
|
732
|
+
switch (position) {
|
|
733
|
+
case "top-left":
|
|
734
|
+
ws.top = inset;
|
|
735
|
+
ws.left = inset;
|
|
736
|
+
break;
|
|
737
|
+
case "top-center":
|
|
738
|
+
ws.top = inset;
|
|
739
|
+
ws.left = "50%";
|
|
740
|
+
ws.transform = "translateX(-50%)";
|
|
741
|
+
break;
|
|
742
|
+
case "top-right":
|
|
743
|
+
ws.top = inset;
|
|
744
|
+
ws.right = inset;
|
|
745
|
+
break;
|
|
746
|
+
case "center-left":
|
|
747
|
+
ws.top = "50%";
|
|
748
|
+
ws.left = inset;
|
|
749
|
+
ws.transform = "translateY(-50%)";
|
|
750
|
+
break;
|
|
751
|
+
case "center":
|
|
752
|
+
ws.top = "50%";
|
|
753
|
+
ws.left = "50%";
|
|
754
|
+
ws.transform = "translate(-50%, -50%)";
|
|
755
|
+
break;
|
|
756
|
+
case "center-right":
|
|
757
|
+
ws.top = "50%";
|
|
758
|
+
ws.right = inset;
|
|
759
|
+
ws.transform = "translateY(-50%)";
|
|
760
|
+
break;
|
|
761
|
+
case "bottom-left":
|
|
762
|
+
ws.bottom = inset;
|
|
763
|
+
ws.left = inset;
|
|
764
|
+
break;
|
|
765
|
+
case "bottom-center":
|
|
766
|
+
ws.bottom = inset;
|
|
767
|
+
ws.left = "50%";
|
|
768
|
+
ws.transform = "translateX(-50%)";
|
|
769
|
+
break;
|
|
770
|
+
case "bottom-right":
|
|
771
|
+
ws.bottom = inset;
|
|
772
|
+
ws.right = inset;
|
|
773
|
+
break;
|
|
774
|
+
case "sheet-top-left":
|
|
775
|
+
case "sheet-top-center":
|
|
776
|
+
case "sheet-top-right": {
|
|
777
|
+
const style = resolveSheetAnchoredStyle(mode, position, inset);
|
|
778
|
+
if (style.top !== void 0) ws.top = style.top;
|
|
779
|
+
if (style.bottom !== void 0) ws.bottom = style.bottom;
|
|
780
|
+
if (style.left !== void 0) ws.left = style.left;
|
|
781
|
+
if (style.right !== void 0) ws.right = style.right;
|
|
782
|
+
if (style.transform !== void 0) ws.transform = style.transform;
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
721
788
|
// src/core/controllers/scrim-controller.ts
|
|
789
|
+
var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
|
|
722
790
|
var ScrimController = class {
|
|
723
791
|
constructor(deps, opts) {
|
|
724
|
-
// ── DOM refs (mirror engine's; cached for hot path) ─────────────────
|
|
725
792
|
__publicField(this, "screenComponent");
|
|
726
793
|
__publicField(this, "backdrop");
|
|
727
794
|
__publicField(this, "mode");
|
|
728
|
-
// ── Engine callbacks ────────────────────────────────────────────────
|
|
729
795
|
__publicField(this, "isDestroyed");
|
|
730
796
|
__publicField(this, "isTopSheet");
|
|
731
797
|
__publicField(this, "getAllowedIds");
|
|
732
798
|
__publicField(this, "getResolvedSnaps");
|
|
733
799
|
__publicField(this, "snapToFn");
|
|
734
|
-
|
|
800
|
+
__publicField(this, "closeFn");
|
|
801
|
+
__publicField(this, "scrimInteractive", false);
|
|
735
802
|
__publicField(this, "scrimMode");
|
|
736
803
|
__publicField(this, "backdropRange");
|
|
737
804
|
__publicField(this, "screenRange");
|
|
738
|
-
// setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
|
|
739
|
-
// ranges so re-enable can restore prior values without a remount. Public
|
|
740
|
-
// so tests / introspection can read it without an `as any` cast.
|
|
741
805
|
__publicField(this, "scrimEnabled", true);
|
|
742
806
|
__publicField(this, "savedScrimRanges", null);
|
|
743
|
-
// ── Tap-to-close ────────────────────────────────────────────────────
|
|
744
807
|
__publicField(this, "scrimTapToCloseEnabled", false);
|
|
745
808
|
__publicField(this, "detachScrimTap", null);
|
|
746
|
-
// ── Scrim overlay slot (positioned floating element) ────────────────
|
|
747
809
|
__publicField(this, "scrimOverlayTeardown", null);
|
|
748
|
-
// ── Hot-path opacity dedup caches (per-frame writes) ────────────────
|
|
749
|
-
// Numeric sentinels migrated to WriteSentinel primitive — single
|
|
750
|
-
// invalidation surface, shared with the engine's size/progress sentinels.
|
|
751
|
-
// The two string-typed sentinels (`Pointer`, `Display`) keep their inline
|
|
752
|
-
// pattern: equality on string union, no epsilon, different reset semantics.
|
|
753
810
|
__publicField(this, "backdropOpacitySentinel", new WriteSentinel());
|
|
754
811
|
__publicField(this, "screenOpacitySentinel", new WriteSentinel());
|
|
755
812
|
__publicField(this, "lastBackdropPointer", "");
|
|
@@ -762,6 +819,7 @@ var ScrimController = class {
|
|
|
762
819
|
this.getAllowedIds = deps.getAllowedIds;
|
|
763
820
|
this.getResolvedSnaps = deps.getResolvedSnaps;
|
|
764
821
|
this.snapToFn = deps.snapTo;
|
|
822
|
+
this.closeFn = deps.close;
|
|
765
823
|
this.scrimMode = opts.scrimMode ?? "full";
|
|
766
824
|
const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
|
|
767
825
|
this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
|
|
@@ -784,41 +842,15 @@ var ScrimController = class {
|
|
|
784
842
|
}
|
|
785
843
|
}
|
|
786
844
|
}
|
|
787
|
-
/**
|
|
788
|
-
* Invalidate the opacity dedup caches. Called from any setter that mutates
|
|
789
|
-
* range/mode/enabled — opacity is a pure function of (progress, range,
|
|
790
|
-
* enabled), so a config change makes the cached values stale even if
|
|
791
|
-
* `progress` itself didn't move. Next `applyOpacity(progress, false)` call
|
|
792
|
-
* sees the -1 sentinel and re-runs the math instead of short-circuiting.
|
|
793
|
-
*
|
|
794
|
-
* Public for the engine: `setSnapPoints` mutates resolved geometry which
|
|
795
|
-
* shifts `progress→opacity` mapping under the cache; engine calls this to
|
|
796
|
-
* keep the next render frame correct on a static sheet.
|
|
797
|
-
* @internal
|
|
798
|
-
*/
|
|
799
845
|
invalidateOpacityCache() {
|
|
800
846
|
this.backdropOpacitySentinel.invalidate();
|
|
801
847
|
this.screenOpacitySentinel.invalidate();
|
|
802
848
|
}
|
|
803
|
-
/** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
|
|
804
849
|
attach() {
|
|
805
850
|
if (this.scrimTapToCloseEnabled) {
|
|
806
851
|
this.setScrimTapToClose(true);
|
|
807
852
|
}
|
|
808
853
|
}
|
|
809
|
-
// ── Hot-path: opacity computation called per-frame from engine.applySize ──
|
|
810
|
-
/**
|
|
811
|
-
* Per-frame opacity writer. Computes backdrop + screen opacity from the
|
|
812
|
-
* sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
|
|
813
|
-
* caches so steady-state frames don't trigger style recalc.
|
|
814
|
-
*
|
|
815
|
-
* `progressChanged` is the engine's already-computed dedup gate — when
|
|
816
|
-
* `false`, opacity is by construction a pure function of `progress` so
|
|
817
|
-
* cached values are still valid; we short-circuit the Math.min/max +
|
|
818
|
-
* division work entirely. Setters that mutate ranges/mode/enabled
|
|
819
|
-
* invalidate the cache by writing -1 sentinels so the next call re-runs
|
|
820
|
-
* the math.
|
|
821
|
-
*/
|
|
822
854
|
applyOpacity(progress, progressChanged) {
|
|
823
855
|
if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
|
|
824
856
|
return;
|
|
@@ -864,80 +896,30 @@ var ScrimController = class {
|
|
|
864
896
|
}
|
|
865
897
|
}
|
|
866
898
|
}
|
|
867
|
-
// ── Public setters (engine forwards these 1:1) ─────────────────────
|
|
868
|
-
/**
|
|
869
|
-
* Replace the [start, end] progress window in which the backdrop fades 0→1.
|
|
870
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
871
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
872
|
-
* @example
|
|
873
|
-
* // Backdrop only starts darkening after the sheet crosses 30% open.
|
|
874
|
-
* engine.setBackdropRange([0.3, 1]);
|
|
875
|
-
*/
|
|
876
899
|
setBackdropRange(range, applySize) {
|
|
877
900
|
if (this.isDestroyed()) return;
|
|
878
901
|
this.backdropRange = range;
|
|
879
902
|
this.invalidateOpacityCache();
|
|
880
903
|
applySize();
|
|
881
904
|
}
|
|
882
|
-
/**
|
|
883
|
-
* Replace the [start, end] progress window in which the screen scrim fades 0→1.
|
|
884
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
885
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
886
|
-
* @example
|
|
887
|
-
* // Scrim is fully opaque only when the sheet is past 50% — softer feel.
|
|
888
|
-
* engine.setScreenRange([0, 0.5]);
|
|
889
|
-
*/
|
|
890
905
|
setScreenRange(range, applySize) {
|
|
891
906
|
if (this.isDestroyed()) return;
|
|
892
907
|
this.screenRange = range;
|
|
893
908
|
this.invalidateOpacityCache();
|
|
894
909
|
applySize();
|
|
895
910
|
}
|
|
896
|
-
/**
|
|
897
|
-
* Set the scrim background color. Pass `null`/`undefined` to clear the
|
|
898
|
-
* inline write and let consumer CSS take over.
|
|
899
|
-
* @param color - Any valid CSS color string, or `null`/`undefined` to clear.
|
|
900
|
-
* @example
|
|
901
|
-
* engine.setScrimColor("rgba(0, 0, 0, 0.6)");
|
|
902
|
-
* engine.setScrimColor(null); // hand back to consumer CSS
|
|
903
|
-
*/
|
|
904
911
|
setScrimColor(color) {
|
|
905
912
|
if (this.isDestroyed()) return;
|
|
906
913
|
this.applyScrimStyles({ color: color === void 0 ? null : color });
|
|
907
914
|
}
|
|
908
|
-
/**
|
|
909
|
-
* Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
|
|
910
|
-
* @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
|
|
911
|
-
* `null`/`undefined` to remove the filter.
|
|
912
|
-
* @example
|
|
913
|
-
* engine.setScrimBlur("12px"); // frosted-glass effect
|
|
914
|
-
* engine.setScrimBlur(null); // disable blur
|
|
915
|
-
*/
|
|
916
915
|
setScrimBlur(blur) {
|
|
917
916
|
if (this.isDestroyed()) return;
|
|
918
917
|
this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
|
|
919
918
|
}
|
|
920
|
-
/**
|
|
921
|
-
* Toggle whether the scrim layer captures pointer events.
|
|
922
|
-
* @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
|
|
923
|
-
* @example
|
|
924
|
-
* engine.setScrimInteractive(true); // scrim swallows clicks
|
|
925
|
-
* engine.setScrimInteractive(false); // clicks pass through to content below
|
|
926
|
-
*/
|
|
927
919
|
setScrimInteractive(interactive) {
|
|
928
920
|
if (this.isDestroyed()) return;
|
|
929
921
|
this.applyScrimStyles({ interactive });
|
|
930
922
|
}
|
|
931
|
-
/**
|
|
932
|
-
* Switch scrim positioning at runtime.
|
|
933
|
-
* @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
|
|
934
|
-
* so only the area NOT covered by the sheet is dimmed), or
|
|
935
|
-
* `"off"` (display:none + opacity:0 — no remount needed).
|
|
936
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
937
|
-
* @example
|
|
938
|
-
* engine.setScrimMode("above-sheet"); // dim only the area above the sheet
|
|
939
|
-
* engine.setScrimMode("off"); // disable scrim layer
|
|
940
|
-
*/
|
|
941
923
|
setScrimMode(mode, applySize) {
|
|
942
924
|
if (this.isDestroyed()) return;
|
|
943
925
|
if (mode === this.scrimMode) return;
|
|
@@ -967,15 +949,6 @@ var ScrimController = class {
|
|
|
967
949
|
}
|
|
968
950
|
applySize();
|
|
969
951
|
}
|
|
970
|
-
/**
|
|
971
|
-
* Enable or disable click-on-scrim → snap to first non-zero allowed snap.
|
|
972
|
-
* Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
|
|
973
|
-
* click listener actually fires.
|
|
974
|
-
* @param enabled - `true` installs the click listener; `false` removes it.
|
|
975
|
-
* @example
|
|
976
|
-
* engine.setScrimTapToClose(true); // tap outside sheet collapses it
|
|
977
|
-
* engine.setScrimTapToClose(false); // require explicit close button
|
|
978
|
-
*/
|
|
979
952
|
setScrimTapToClose(enabled) {
|
|
980
953
|
if (this.isDestroyed()) return;
|
|
981
954
|
if (!this.screenComponent) return;
|
|
@@ -988,11 +961,15 @@ var ScrimController = class {
|
|
|
988
961
|
}
|
|
989
962
|
const onClick = () => {
|
|
990
963
|
if (this.isDestroyed()) return;
|
|
964
|
+
if (this.closeFn) {
|
|
965
|
+
this.closeFn();
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
991
968
|
const allowed = this.getAllowedIds();
|
|
992
969
|
const snaps = this.getResolvedSnaps();
|
|
993
970
|
const fallback = allowed.find((id) => {
|
|
994
971
|
const snap = findById(id, snaps);
|
|
995
|
-
return snap !== null && snap.size
|
|
972
|
+
return snap !== null && snap.size === 0;
|
|
996
973
|
});
|
|
997
974
|
if (fallback) this.snapToFn(fallback);
|
|
998
975
|
};
|
|
@@ -1005,20 +982,11 @@ var ScrimController = class {
|
|
|
1005
982
|
this.scrimTapToCloseEnabled = false;
|
|
1006
983
|
}
|
|
1007
984
|
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Toggle the scrim layer without remounting. Disabling stashes current
|
|
1010
|
-
* `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
|
|
1011
|
-
* formula → 0 across the whole range); re-enabling restores the saved values.
|
|
1012
|
-
* @param enabled - `false` hides scrim, `true` restores prior ranges.
|
|
1013
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
1014
|
-
* @example
|
|
1015
|
-
* engine.setScrimEnabled(false); // hide scrim, ranges remembered
|
|
1016
|
-
* engine.setScrimEnabled(true); // restore prior ranges (no remount)
|
|
1017
|
-
*/
|
|
1018
985
|
setScrimEnabled(enabled, applySize) {
|
|
1019
986
|
if (this.isDestroyed()) return;
|
|
1020
987
|
if (enabled === this.scrimEnabled) return;
|
|
1021
988
|
this.scrimEnabled = enabled;
|
|
989
|
+
this.invalidateOpacityCache();
|
|
1022
990
|
if (!enabled) {
|
|
1023
991
|
this.savedScrimRanges = {
|
|
1024
992
|
screen: this.screenRange,
|
|
@@ -1033,20 +1001,6 @@ var ScrimController = class {
|
|
|
1033
1001
|
}
|
|
1034
1002
|
applySize();
|
|
1035
1003
|
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Batch-update multiple scrim fields with a single applySize() at the end.
|
|
1038
|
-
* If `preset` is set, it seeds color/blur/interactive/range first; explicit
|
|
1039
|
-
* fields override.
|
|
1040
|
-
* @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
|
|
1041
|
-
* color, blur, interactive, range.
|
|
1042
|
-
* @param applySize - Engine-supplied recompute hook; runs ONCE per call.
|
|
1043
|
-
* @example
|
|
1044
|
-
* engine.setScrim({
|
|
1045
|
-
* preset: "frosted",
|
|
1046
|
-
* mode: "above-sheet",
|
|
1047
|
-
* tapToClose: true,
|
|
1048
|
-
* });
|
|
1049
|
-
*/
|
|
1050
1004
|
setScrim(opts, applySize) {
|
|
1051
1005
|
if (this.isDestroyed()) return;
|
|
1052
1006
|
if (opts.preset) {
|
|
@@ -1056,7 +1010,12 @@ var ScrimController = class {
|
|
|
1056
1010
|
blur: cfg.blur ?? null,
|
|
1057
1011
|
interactive: cfg.interactive
|
|
1058
1012
|
});
|
|
1059
|
-
this.
|
|
1013
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
1014
|
+
this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
|
|
1015
|
+
} else {
|
|
1016
|
+
this.screenRange = cfg.range;
|
|
1017
|
+
this.backdropRange = cfg.range;
|
|
1018
|
+
}
|
|
1060
1019
|
}
|
|
1061
1020
|
const noop = () => {
|
|
1062
1021
|
};
|
|
@@ -1069,27 +1028,18 @@ var ScrimController = class {
|
|
|
1069
1028
|
interactive: opts.interactive
|
|
1070
1029
|
});
|
|
1071
1030
|
if (opts.range !== void 0) {
|
|
1072
|
-
this.
|
|
1031
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
1032
|
+
this.savedScrimRanges = {
|
|
1033
|
+
screen: opts.range,
|
|
1034
|
+
backdrop: this.savedScrimRanges.backdrop
|
|
1035
|
+
};
|
|
1036
|
+
} else {
|
|
1037
|
+
this.screenRange = opts.range;
|
|
1038
|
+
}
|
|
1073
1039
|
}
|
|
1074
1040
|
this.invalidateOpacityCache();
|
|
1075
1041
|
applySize();
|
|
1076
1042
|
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Inject a positioned floating element into the scrim layer (e.g. a close
|
|
1079
|
-
* button anchored top-right). Replaces any prior overlay so repeated calls
|
|
1080
|
-
* (reactive effects re-running) don't accumulate wrappers.
|
|
1081
|
-
* @param opts - Children element, optional `inset` (default `"16px"`),
|
|
1082
|
-
* `position` (9-way grid, default `"top-right"`), and
|
|
1083
|
-
* `interactive` flag (default `true`).
|
|
1084
|
-
* @returns Teardown function that removes the wrapper from the DOM.
|
|
1085
|
-
* @example
|
|
1086
|
-
* const teardown = engine.setScrimOverlay({
|
|
1087
|
-
* children: closeBtn,
|
|
1088
|
-
* position: "top-right",
|
|
1089
|
-
* inset: "20px",
|
|
1090
|
-
* });
|
|
1091
|
-
* // later: teardown(); // remove the floating button
|
|
1092
|
-
*/
|
|
1093
1043
|
setScrimOverlay(opts) {
|
|
1094
1044
|
if (this.isDestroyed()) return () => {
|
|
1095
1045
|
};
|
|
@@ -1097,61 +1047,38 @@ var ScrimController = class {
|
|
|
1097
1047
|
};
|
|
1098
1048
|
this.scrimOverlayTeardown?.();
|
|
1099
1049
|
const screen = this.screenComponent;
|
|
1100
|
-
const
|
|
1050
|
+
const insetRaw = opts.inset ?? "16px";
|
|
1051
|
+
const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
|
|
1052
|
+
if (inset !== insetRaw) {
|
|
1053
|
+
devWarn(
|
|
1054
|
+
`[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
|
|
1058
|
+
devWarn(
|
|
1059
|
+
"[bottom-sheet] setScrimOverlay: children belong to a different document than the scrim. Cross-document adoption can drop event listeners; construct children via the same document as the sheet root."
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1101
1062
|
const position = opts.position ?? "top-right";
|
|
1102
1063
|
const interactive = opts.interactive ?? true;
|
|
1103
1064
|
const wrapper = screen.ownerDocument.createElement("div");
|
|
1104
1065
|
wrapper.className = "bs-scrim-overlay";
|
|
1105
1066
|
const ws = wrapper.style;
|
|
1106
1067
|
ws.position = "absolute";
|
|
1107
|
-
|
|
1108
|
-
case "top-left":
|
|
1109
|
-
ws.top = inset;
|
|
1110
|
-
ws.left = inset;
|
|
1111
|
-
break;
|
|
1112
|
-
case "top-center":
|
|
1113
|
-
ws.top = inset;
|
|
1114
|
-
ws.left = "50%";
|
|
1115
|
-
ws.transform = "translateX(-50%)";
|
|
1116
|
-
break;
|
|
1117
|
-
case "top-right":
|
|
1118
|
-
ws.top = inset;
|
|
1119
|
-
ws.right = inset;
|
|
1120
|
-
break;
|
|
1121
|
-
case "center-left":
|
|
1122
|
-
ws.top = "50%";
|
|
1123
|
-
ws.left = inset;
|
|
1124
|
-
ws.transform = "translateY(-50%)";
|
|
1125
|
-
break;
|
|
1126
|
-
case "center":
|
|
1127
|
-
ws.top = "50%";
|
|
1128
|
-
ws.left = "50%";
|
|
1129
|
-
ws.transform = "translate(-50%, -50%)";
|
|
1130
|
-
break;
|
|
1131
|
-
case "center-right":
|
|
1132
|
-
ws.top = "50%";
|
|
1133
|
-
ws.right = inset;
|
|
1134
|
-
ws.transform = "translateY(-50%)";
|
|
1135
|
-
break;
|
|
1136
|
-
case "bottom-left":
|
|
1137
|
-
ws.bottom = inset;
|
|
1138
|
-
ws.left = inset;
|
|
1139
|
-
break;
|
|
1140
|
-
case "bottom-center":
|
|
1141
|
-
ws.bottom = inset;
|
|
1142
|
-
ws.left = "50%";
|
|
1143
|
-
ws.transform = "translateX(-50%)";
|
|
1144
|
-
break;
|
|
1145
|
-
case "bottom-right":
|
|
1146
|
-
ws.bottom = inset;
|
|
1147
|
-
ws.right = inset;
|
|
1148
|
-
break;
|
|
1149
|
-
}
|
|
1068
|
+
applyOverlayPosition(ws, this.mode, position, inset);
|
|
1150
1069
|
if (interactive) {
|
|
1151
1070
|
ws.pointerEvents = "auto";
|
|
1152
1071
|
}
|
|
1153
1072
|
wrapper.appendChild(opts.children);
|
|
1154
|
-
screen.
|
|
1073
|
+
const host = screen.parentElement;
|
|
1074
|
+
if (!host) {
|
|
1075
|
+
devWarn(
|
|
1076
|
+
"[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
|
|
1077
|
+
);
|
|
1078
|
+
return () => {
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
host.appendChild(wrapper);
|
|
1155
1082
|
const teardown = () => {
|
|
1156
1083
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
1157
1084
|
if (this.scrimOverlayTeardown === teardown) {
|
|
@@ -1161,7 +1088,6 @@ var ScrimController = class {
|
|
|
1161
1088
|
this.scrimOverlayTeardown = teardown;
|
|
1162
1089
|
return teardown;
|
|
1163
1090
|
}
|
|
1164
|
-
/** Tear down toggle-state listeners + remove any injected overlay wrapper. */
|
|
1165
1091
|
destroy() {
|
|
1166
1092
|
this.detachScrimTap?.();
|
|
1167
1093
|
this.detachScrimTap = null;
|
|
@@ -1180,8 +1106,6 @@ var ScrimController = class {
|
|
|
1180
1106
|
this.lastBackdropPointer = "";
|
|
1181
1107
|
this.lastScreenDisplay = "";
|
|
1182
1108
|
}
|
|
1183
|
-
// ── Private helpers ────────────────────────────────────────────────
|
|
1184
|
-
/** Centralized scrim style writer — atomic 3-property write across all setters. */
|
|
1185
1109
|
applyScrimStyles(opts) {
|
|
1186
1110
|
if (!this.screenComponent) return;
|
|
1187
1111
|
const s = this.screenComponent.style;
|
|
@@ -1200,10 +1124,10 @@ var ScrimController = class {
|
|
|
1200
1124
|
}
|
|
1201
1125
|
}
|
|
1202
1126
|
if (opts.interactive !== void 0) {
|
|
1127
|
+
this.scrimInteractive = opts.interactive;
|
|
1203
1128
|
s.pointerEvents = opts.interactive ? "auto" : "none";
|
|
1204
1129
|
}
|
|
1205
1130
|
}
|
|
1206
|
-
/** Pin the screen overlay to the area NOT occupied by the sheet. */
|
|
1207
1131
|
applyAboveSheetInset() {
|
|
1208
1132
|
if (!this.screenComponent) return;
|
|
1209
1133
|
const insetByMode = {
|
|
@@ -1215,7 +1139,7 @@ var ScrimController = class {
|
|
|
1215
1139
|
const s = this.screenComponent.style;
|
|
1216
1140
|
s.position = "fixed";
|
|
1217
1141
|
s.inset = insetByMode[this.mode];
|
|
1218
|
-
s.pointerEvents = "none";
|
|
1142
|
+
s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
|
|
1219
1143
|
}
|
|
1220
1144
|
};
|
|
1221
1145
|
|
|
@@ -1269,6 +1193,10 @@ var tween = ({
|
|
|
1269
1193
|
promise
|
|
1270
1194
|
};
|
|
1271
1195
|
};
|
|
1196
|
+
var prefersReducedMotion = () => {
|
|
1197
|
+
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
1198
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1199
|
+
};
|
|
1272
1200
|
|
|
1273
1201
|
// src/core/animation/spring.ts
|
|
1274
1202
|
var DEFAULT_SPRING = {
|
|
@@ -1299,14 +1227,20 @@ var runSpring = ({
|
|
|
1299
1227
|
if (cancelled) return;
|
|
1300
1228
|
const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
|
|
1301
1229
|
lastTime = now;
|
|
1302
|
-
const
|
|
1303
|
-
|
|
1230
|
+
const steps = Math.max(1, Math.ceil(dt * 240));
|
|
1231
|
+
const subDt = dt / steps;
|
|
1232
|
+
let stepsLeft = steps;
|
|
1304
1233
|
while (stepsLeft-- > 0) {
|
|
1305
1234
|
const force = -cfg.stiffness * (x - to) - cfg.damping * v;
|
|
1306
1235
|
const accel = force / cfg.mass;
|
|
1307
1236
|
v += accel * subDt;
|
|
1308
1237
|
x += v * subDt;
|
|
1309
1238
|
}
|
|
1239
|
+
if (!Number.isFinite(x) || !Number.isFinite(v)) {
|
|
1240
|
+
onUpdate(to, 0);
|
|
1241
|
+
resolve();
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1310
1244
|
onUpdate(x, v);
|
|
1311
1245
|
const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
|
|
1312
1246
|
if (isAtRest) {
|
|
@@ -1370,13 +1304,9 @@ var AnimationRunner = class {
|
|
|
1370
1304
|
__publicField(this, "duration");
|
|
1371
1305
|
__publicField(this, "easing");
|
|
1372
1306
|
__publicField(this, "respectReducedMotion");
|
|
1373
|
-
/** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
|
|
1374
1307
|
__publicField(this, "reducedMotion", false);
|
|
1375
|
-
/** Disposes the matchMedia change listener — drained from destroy(). */
|
|
1376
1308
|
__publicField(this, "detachReducedMotion", null);
|
|
1377
|
-
/** Computed once — both inputs (option + API presence) stable for life. */
|
|
1378
1309
|
__publicField(this, "viewTransitionsAvailable");
|
|
1379
|
-
/** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
|
|
1380
1310
|
__publicField(this, "currentTween", null);
|
|
1381
1311
|
__publicField(this, "currentSpring", null);
|
|
1382
1312
|
this.element = deps.element;
|
|
@@ -1407,26 +1337,13 @@ var AnimationRunner = class {
|
|
|
1407
1337
|
}
|
|
1408
1338
|
}
|
|
1409
1339
|
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Live derived flag — `true` whenever a spring/tween handle is in flight.
|
|
1412
|
-
* Reading the canonical handles (instead of mirroring into a separate
|
|
1413
|
-
* boolean) eliminates the reduced-motion early-return latent bug where
|
|
1414
|
-
* the flag stayed true forever, and removes the cross-controller
|
|
1415
|
-
* setIsAnimating callback.
|
|
1416
|
-
*/
|
|
1417
1340
|
get isAnimating() {
|
|
1418
1341
|
return this.currentTween !== null || this.currentSpring !== null;
|
|
1419
1342
|
}
|
|
1420
|
-
/** Cancel any in-flight tween/spring without touching engine state. */
|
|
1421
1343
|
cancel() {
|
|
1422
1344
|
this.currentTween?.cancel();
|
|
1423
1345
|
this.currentSpring?.cancel();
|
|
1424
1346
|
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Animate from current size to `target`. Resolves when the animation
|
|
1427
|
-
* completes; resolves early if cancelled mid-flight (caller checks an
|
|
1428
|
-
* abort signal post-await for cycle-invariant correctness).
|
|
1429
|
-
*/
|
|
1430
1347
|
async animateTo(target, velocityPxPerMs) {
|
|
1431
1348
|
if (target === this.getSize()) return;
|
|
1432
1349
|
this.currentTween?.cancel();
|
|
@@ -1467,7 +1384,6 @@ var AnimationRunner = class {
|
|
|
1467
1384
|
if (!this.isDragging()) this.element.style.willChange = "auto";
|
|
1468
1385
|
}
|
|
1469
1386
|
}
|
|
1470
|
-
/** Tear down the matchMedia listener. Engine calls from destroy(). */
|
|
1471
1387
|
destroy() {
|
|
1472
1388
|
this.detachReducedMotion?.();
|
|
1473
1389
|
this.detachReducedMotion = null;
|
|
@@ -1499,9 +1415,13 @@ var isVisible = (el) => {
|
|
|
1499
1415
|
var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
|
|
1500
1416
|
(el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
|
|
1501
1417
|
);
|
|
1418
|
+
var trapStack = [];
|
|
1502
1419
|
var installFocusTrap = (container, options = {}) => {
|
|
1503
1420
|
if (typeof document === "undefined") return () => {
|
|
1504
1421
|
};
|
|
1422
|
+
const token = {};
|
|
1423
|
+
trapStack.push(token);
|
|
1424
|
+
const isActive = () => trapStack[trapStack.length - 1] === token;
|
|
1505
1425
|
const previouslyFocused = document.activeElement;
|
|
1506
1426
|
const focusInitial = () => {
|
|
1507
1427
|
const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
|
|
@@ -1509,6 +1429,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1509
1429
|
};
|
|
1510
1430
|
focusInitial();
|
|
1511
1431
|
const handleKey = (e) => {
|
|
1432
|
+
if (!isActive()) return;
|
|
1512
1433
|
if (e.key === "Escape" && options.onEscape) {
|
|
1513
1434
|
options.onEscape();
|
|
1514
1435
|
return;
|
|
@@ -1531,6 +1452,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1531
1452
|
}
|
|
1532
1453
|
};
|
|
1533
1454
|
const handleFocusIn = (e) => {
|
|
1455
|
+
if (!isActive()) return;
|
|
1534
1456
|
const target = e.target;
|
|
1535
1457
|
if (target && !container.contains(target)) {
|
|
1536
1458
|
const list = focusables(container);
|
|
@@ -1543,6 +1465,8 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1543
1465
|
return () => {
|
|
1544
1466
|
if (released) return;
|
|
1545
1467
|
released = true;
|
|
1468
|
+
const idx = trapStack.indexOf(token);
|
|
1469
|
+
if (idx !== -1) trapStack.splice(idx, 1);
|
|
1546
1470
|
document.removeEventListener("keydown", handleKey, true);
|
|
1547
1471
|
document.removeEventListener("focusin", handleFocusIn, true);
|
|
1548
1472
|
if (previouslyFocused && document.contains(previouslyFocused)) {
|
|
@@ -1598,11 +1522,17 @@ function createInertSiblings(rootProvider) {
|
|
|
1598
1522
|
return {
|
|
1599
1523
|
apply() {
|
|
1600
1524
|
if (typeof document === "undefined") return;
|
|
1601
|
-
|
|
1602
|
-
|
|
1525
|
+
let top = rootProvider();
|
|
1526
|
+
while (top.parentElement && top.parentElement !== document.body) {
|
|
1527
|
+
top = top.parentElement;
|
|
1528
|
+
}
|
|
1529
|
+
const parent = top.parentElement ?? document.body;
|
|
1603
1530
|
for (const child of Array.from(parent.children)) {
|
|
1604
|
-
if (child ===
|
|
1531
|
+
if (child === top) continue;
|
|
1605
1532
|
if (child.hasAttribute("inert")) continue;
|
|
1533
|
+
if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1606
1536
|
child.setAttribute("inert", "");
|
|
1607
1537
|
tracked.push(child);
|
|
1608
1538
|
}
|
|
@@ -1618,7 +1548,6 @@ function createInertSiblings(rootProvider) {
|
|
|
1618
1548
|
var LifecycleController = class {
|
|
1619
1549
|
constructor(deps, opts) {
|
|
1620
1550
|
__publicField(this, "element");
|
|
1621
|
-
__publicField(this, "closeFn");
|
|
1622
1551
|
__publicField(this, "focusTrapEnabled");
|
|
1623
1552
|
__publicField(this, "initialFocus");
|
|
1624
1553
|
__publicField(this, "closeOnEscape");
|
|
@@ -1627,16 +1556,11 @@ var LifecycleController = class {
|
|
|
1627
1556
|
__publicField(this, "inertSiblings");
|
|
1628
1557
|
__publicField(this, "shouldApplyInertSiblings");
|
|
1629
1558
|
__publicField(this, "returnFocus");
|
|
1630
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
1631
1559
|
__publicField(this, "releaseFocusTrap", null);
|
|
1632
1560
|
__publicField(this, "releaseScrollLock", null);
|
|
1633
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
1634
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
1635
|
-
* released or never-installed instances. */
|
|
1636
1561
|
__publicField(this, "installed", false);
|
|
1637
1562
|
__publicField(this, "destroyed", false);
|
|
1638
1563
|
this.element = deps.element;
|
|
1639
|
-
this.closeFn = deps.close;
|
|
1640
1564
|
this.focusTrapEnabled = opts.focusTrap ?? false;
|
|
1641
1565
|
this.initialFocus = opts.initialFocus;
|
|
1642
1566
|
this.closeOnEscape = opts.closeOnEscape ?? true;
|
|
@@ -1646,14 +1570,9 @@ var LifecycleController = class {
|
|
|
1646
1570
|
this.returnFocus = opts.returnFocus;
|
|
1647
1571
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
1648
1572
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
1653
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
1654
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
1655
|
-
* No-op when destroyed.
|
|
1656
|
-
*/
|
|
1573
|
+
get isInstalled() {
|
|
1574
|
+
return this.installed;
|
|
1575
|
+
}
|
|
1657
1576
|
install() {
|
|
1658
1577
|
if (this.destroyed) return;
|
|
1659
1578
|
this.installed = true;
|
|
@@ -1662,30 +1581,17 @@ var LifecycleController = class {
|
|
|
1662
1581
|
}
|
|
1663
1582
|
if (this.focusTrapEnabled && !this.releaseFocusTrap) {
|
|
1664
1583
|
this.releaseFocusTrap = installFocusTrap(this.element, {
|
|
1665
|
-
initialFocus: this.initialFocus
|
|
1666
|
-
onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
|
|
1584
|
+
initialFocus: this.initialFocus
|
|
1667
1585
|
});
|
|
1668
1586
|
}
|
|
1669
1587
|
if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
|
|
1670
1588
|
this.inertSiblings.apply();
|
|
1671
1589
|
}
|
|
1672
1590
|
}
|
|
1673
|
-
/**
|
|
1674
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
1675
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
1676
|
-
* on the next `release()`. No-op when destroyed.
|
|
1677
|
-
*/
|
|
1678
1591
|
setReturnFocus(target) {
|
|
1679
1592
|
if (this.destroyed) return;
|
|
1680
1593
|
this.returnFocus = target;
|
|
1681
1594
|
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
1684
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
1685
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
1686
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
1687
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
1688
|
-
*/
|
|
1689
1595
|
release() {
|
|
1690
1596
|
if (!this.installed) return;
|
|
1691
1597
|
this.installed = false;
|
|
@@ -1699,12 +1605,6 @@ var LifecycleController = class {
|
|
|
1699
1605
|
target?.focus?.();
|
|
1700
1606
|
}
|
|
1701
1607
|
}
|
|
1702
|
-
/**
|
|
1703
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
1704
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
1705
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
1706
|
-
* restore.
|
|
1707
|
-
*/
|
|
1708
1608
|
destroy() {
|
|
1709
1609
|
this.release();
|
|
1710
1610
|
this.destroyed = true;
|
|
@@ -1714,12 +1614,10 @@ var LifecycleController = class {
|
|
|
1714
1614
|
// src/core/gestures.ts
|
|
1715
1615
|
var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
|
|
1716
1616
|
var SampleRing = class {
|
|
1717
|
-
// current count, ≤ capacity
|
|
1718
1617
|
constructor(capacity) {
|
|
1719
1618
|
__publicField(this, "slots");
|
|
1720
1619
|
__publicField(this, "capacity");
|
|
1721
1620
|
__publicField(this, "head", 0);
|
|
1722
|
-
// next write index
|
|
1723
1621
|
__publicField(this, "size", 0);
|
|
1724
1622
|
this.capacity = capacity;
|
|
1725
1623
|
this.slots = new Array(capacity);
|
|
@@ -1732,12 +1630,6 @@ var SampleRing = class {
|
|
|
1732
1630
|
this.head = (this.head + 1) % this.capacity;
|
|
1733
1631
|
if (this.size < this.capacity) this.size++;
|
|
1734
1632
|
}
|
|
1735
|
-
/**
|
|
1736
|
-
* Drop oldest entries whose timestamp is below `cutoff`, but never shrink
|
|
1737
|
-
* below `minKeep`. Mirrors the original
|
|
1738
|
-
* `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
|
|
1739
|
-
* but without copying — just advances the logical tail.
|
|
1740
|
-
*/
|
|
1741
1633
|
pruneBefore(cutoff, minKeep) {
|
|
1742
1634
|
while (this.size > minKeep) {
|
|
1743
1635
|
const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
|
|
@@ -1843,10 +1735,11 @@ var installGestures = (handle, mode, callbacks) => {
|
|
|
1843
1735
|
};
|
|
1844
1736
|
|
|
1845
1737
|
// src/core/primitives/rubber-band.ts
|
|
1846
|
-
var RUBBER_BAND_FACTOR = 0.55;
|
|
1847
1738
|
function rubberBand(overshoot, maxAxisSize) {
|
|
1848
1739
|
const cap = Math.min(maxAxisSize * 0.15, 80);
|
|
1849
|
-
|
|
1740
|
+
if (cap <= 0) return 0;
|
|
1741
|
+
const abs = Math.abs(overshoot);
|
|
1742
|
+
return Math.sign(overshoot) * (cap * abs / (abs + cap));
|
|
1850
1743
|
}
|
|
1851
1744
|
|
|
1852
1745
|
// src/core/features/soft-keyboard.ts
|
|
@@ -1873,28 +1766,14 @@ var GestureController = class {
|
|
|
1873
1766
|
__publicField(this, "detach", null);
|
|
1874
1767
|
__publicField(this, "isDragging_", false);
|
|
1875
1768
|
__publicField(this, "dragStartSize", 0);
|
|
1876
|
-
// Only "touch" gestures are eligible for soft-keyboard dismissal —
|
|
1877
|
-
// mouse/trackpad users don't have a keyboard to dismiss.
|
|
1878
1769
|
__publicField(this, "currentPointerKind", "touch");
|
|
1879
|
-
// One blur per drag — flipping focus every move event races the keyboard's
|
|
1880
|
-
// own animations on iOS and makes the page jitter.
|
|
1881
1770
|
__publicField(this, "keyboardDismissed", false);
|
|
1882
|
-
// Reused payload object for the "drag" event — mutated each onMove instead
|
|
1883
|
-
// of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
|
|
1884
|
-
// would otherwise churn tens of KB through the GC. Mirrors browser
|
|
1885
|
-
// PointerEvent semantics: consumers must NOT retain the reference across
|
|
1886
|
-
// frames (clone if needed). See SheetEventMap.drag JSDoc.
|
|
1887
1771
|
__publicField(this, "dragPayload", { size: 0, delta: 0 });
|
|
1888
1772
|
this.deps = deps;
|
|
1889
1773
|
}
|
|
1890
|
-
/** True between `dragstart` and `dragend`/`onCancel`. */
|
|
1891
1774
|
get isDragging() {
|
|
1892
1775
|
return this.isDragging_;
|
|
1893
1776
|
}
|
|
1894
|
-
/**
|
|
1895
|
-
* Bind pointer listeners on the handle and return a teardown. Engine pushes
|
|
1896
|
-
* the teardown into its TeardownStack so destroy() drains it once.
|
|
1897
|
-
*/
|
|
1898
1777
|
install() {
|
|
1899
1778
|
if (this.detach) return this.detach;
|
|
1900
1779
|
this.detach = installGestures(this.deps.handle, this.deps.mode, {
|
|
@@ -1945,17 +1824,16 @@ var GestureController = class {
|
|
|
1945
1824
|
this.isDragging_ = false;
|
|
1946
1825
|
this.keyboardDismissed = false;
|
|
1947
1826
|
this.deps.getRoot()?.removeAttribute("data-dragging");
|
|
1827
|
+
this.deps.emit("dragend", {
|
|
1828
|
+
size: this.deps.getDragContext().size,
|
|
1829
|
+
velocity: 0
|
|
1830
|
+
});
|
|
1948
1831
|
const restoreTo = this.dragStartSize;
|
|
1949
1832
|
void this.deps.animateTo(restoreTo, 0);
|
|
1950
1833
|
}
|
|
1951
1834
|
});
|
|
1952
1835
|
return this.detach;
|
|
1953
1836
|
}
|
|
1954
|
-
/**
|
|
1955
|
-
* Force-clear drag visual state. Engine calls from destroy() when destroyed
|
|
1956
|
-
* mid-drag — gesture detach won't synthesize an onCancel, so we have to
|
|
1957
|
-
* roll back the data-attribute + will-change ourselves.
|
|
1958
|
-
*/
|
|
1959
1837
|
forceClearDragState() {
|
|
1960
1838
|
if (!this.isDragging_) return;
|
|
1961
1839
|
this.isDragging_ = false;
|
|
@@ -1974,11 +1852,6 @@ var AriaSliderWriter = class {
|
|
|
1974
1852
|
mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
|
|
1975
1853
|
);
|
|
1976
1854
|
}
|
|
1977
|
-
/**
|
|
1978
|
-
* Update slider position. `allowedIds` length determines `aria-valuemax`;
|
|
1979
|
-
* `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
|
|
1980
|
-
* Empty list → strip every dynamic attr so AT skips the handle entirely.
|
|
1981
|
-
*/
|
|
1982
1855
|
setValue(allowedIds, activeId) {
|
|
1983
1856
|
if (allowedIds.length === 0) {
|
|
1984
1857
|
this.handle.removeAttribute("aria-valuemin");
|
|
@@ -2028,6 +1901,287 @@ function createScrollCache(deps) {
|
|
|
2028
1901
|
};
|
|
2029
1902
|
}
|
|
2030
1903
|
|
|
1904
|
+
// src/core/primitives/anchor-animations.ts
|
|
1905
|
+
var DEFAULT_DURATION3 = 200;
|
|
1906
|
+
var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1907
|
+
var PRESET_FRAMES = {
|
|
1908
|
+
fade: {
|
|
1909
|
+
enter: [{ opacity: 0 }, { opacity: 1 }],
|
|
1910
|
+
exit: [{ opacity: 1 }, { opacity: 0 }]
|
|
1911
|
+
},
|
|
1912
|
+
scale: {
|
|
1913
|
+
enter: [
|
|
1914
|
+
{ opacity: 0, transform: "scale(0.85)" },
|
|
1915
|
+
{ opacity: 1, transform: "scale(1)" }
|
|
1916
|
+
],
|
|
1917
|
+
exit: [
|
|
1918
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
1919
|
+
{ opacity: 0, transform: "scale(0.85)" }
|
|
1920
|
+
]
|
|
1921
|
+
},
|
|
1922
|
+
slide: {
|
|
1923
|
+
enter: [
|
|
1924
|
+
{ opacity: 0, transform: "translateY(10px)" },
|
|
1925
|
+
{ opacity: 1, transform: "translateY(0)" }
|
|
1926
|
+
],
|
|
1927
|
+
exit: [
|
|
1928
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
1929
|
+
{ opacity: 0, transform: "translateY(10px)" }
|
|
1930
|
+
]
|
|
1931
|
+
},
|
|
1932
|
+
pop: {
|
|
1933
|
+
enter: [
|
|
1934
|
+
{ opacity: 0, transform: "scale(0.6)", offset: 0 },
|
|
1935
|
+
{ opacity: 1, transform: "scale(1.06)", offset: 0.7 },
|
|
1936
|
+
{ opacity: 1, transform: "scale(1)", offset: 1 }
|
|
1937
|
+
],
|
|
1938
|
+
exit: [
|
|
1939
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
1940
|
+
{ opacity: 0, transform: "scale(0.6)" }
|
|
1941
|
+
]
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
var INSTANT = {
|
|
1945
|
+
finished: Promise.resolve(),
|
|
1946
|
+
cancel: () => {
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
function resolveTransitionFrames(spec, phase) {
|
|
1950
|
+
const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
|
|
1951
|
+
if (spec === "none") return fallback;
|
|
1952
|
+
if (spec === void 0 || typeof spec === "string") {
|
|
1953
|
+
const preset2 = PRESET_FRAMES[spec ?? "fade"];
|
|
1954
|
+
return {
|
|
1955
|
+
frames: preset2[phase],
|
|
1956
|
+
duration: DEFAULT_DURATION3,
|
|
1957
|
+
easing: DEFAULT_EASING
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
const custom = phase === "enter" ? spec.enter : spec.exit;
|
|
1961
|
+
if (custom) {
|
|
1962
|
+
return {
|
|
1963
|
+
frames: custom,
|
|
1964
|
+
duration: spec.duration ?? DEFAULT_DURATION3,
|
|
1965
|
+
easing: spec.easing ?? DEFAULT_EASING
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
if (spec.preset === "none") return fallback;
|
|
1969
|
+
const preset = PRESET_FRAMES[spec.preset ?? "fade"];
|
|
1970
|
+
return {
|
|
1971
|
+
frames: preset[phase],
|
|
1972
|
+
duration: spec.duration ?? DEFAULT_DURATION3,
|
|
1973
|
+
easing: spec.easing ?? DEFAULT_EASING
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function runAnchorTransition(el, spec, phase) {
|
|
1977
|
+
const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
|
|
1978
|
+
if (respectReduced && prefersReducedMotion()) return INSTANT;
|
|
1979
|
+
const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
|
|
1980
|
+
if (!frames || duration <= 0) return INSTANT;
|
|
1981
|
+
if (typeof el.animate !== "function") return INSTANT;
|
|
1982
|
+
const animation = el.animate(frames, {
|
|
1983
|
+
duration,
|
|
1984
|
+
easing,
|
|
1985
|
+
fill: "both"
|
|
1986
|
+
});
|
|
1987
|
+
const finished = animation.finished.then(() => void 0).catch(() => void 0);
|
|
1988
|
+
return {
|
|
1989
|
+
finished,
|
|
1990
|
+
cancel: () => {
|
|
1991
|
+
try {
|
|
1992
|
+
animation.cancel();
|
|
1993
|
+
} catch {
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// src/core/features/sheet-anchors.ts
|
|
2001
|
+
function attachAnchor(deps, opts) {
|
|
2002
|
+
const doc = deps.host.ownerDocument;
|
|
2003
|
+
const wrapper = doc.createElement("div");
|
|
2004
|
+
wrapper.className = "bs-anchor";
|
|
2005
|
+
const interactive = opts.interactive ?? true;
|
|
2006
|
+
const position = opts.position ?? "sheet-top-right";
|
|
2007
|
+
const isDock = position === "dock-bottom" || position === "dock-top";
|
|
2008
|
+
const ws = wrapper.style;
|
|
2009
|
+
ws.position = "fixed";
|
|
2010
|
+
if (isDock) {
|
|
2011
|
+
ws.left = "0";
|
|
2012
|
+
ws.right = "0";
|
|
2013
|
+
if (position === "dock-bottom") {
|
|
2014
|
+
ws.bottom = "0";
|
|
2015
|
+
} else {
|
|
2016
|
+
ws.top = "0";
|
|
2017
|
+
}
|
|
2018
|
+
} else {
|
|
2019
|
+
applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
|
|
2020
|
+
}
|
|
2021
|
+
if (opts.fadeRange) {
|
|
2022
|
+
const [r0, r1] = opts.fadeRange;
|
|
2023
|
+
const span = Math.max(r1 - r0, 1e-4);
|
|
2024
|
+
ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
|
|
2025
|
+
}
|
|
2026
|
+
wrapper.appendChild(opts.element);
|
|
2027
|
+
deps.host.appendChild(wrapper);
|
|
2028
|
+
let visible = null;
|
|
2029
|
+
let inFlight = null;
|
|
2030
|
+
const shouldShow = (state) => {
|
|
2031
|
+
if (typeof opts.showOn === "function") return opts.showOn(state);
|
|
2032
|
+
if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
|
|
2033
|
+
return isDock || state.size > 0;
|
|
2034
|
+
};
|
|
2035
|
+
const applyVisibility = (next, animate) => {
|
|
2036
|
+
if (next === visible) return;
|
|
2037
|
+
visible = next;
|
|
2038
|
+
inFlight?.cancel();
|
|
2039
|
+
inFlight = null;
|
|
2040
|
+
wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
|
|
2041
|
+
if (next) {
|
|
2042
|
+
wrapper.style.visibility = "";
|
|
2043
|
+
if (animate) {
|
|
2044
|
+
inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
|
|
2045
|
+
}
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if (!animate) {
|
|
2049
|
+
wrapper.style.visibility = "hidden";
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
const handle = runAnchorTransition(opts.element, opts.animation, "exit");
|
|
2053
|
+
inFlight = handle;
|
|
2054
|
+
void handle.finished.then(() => {
|
|
2055
|
+
if (inFlight === handle && visible === false) {
|
|
2056
|
+
wrapper.style.visibility = "hidden";
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
};
|
|
2060
|
+
const evaluate = (animate) => {
|
|
2061
|
+
if (deps.isDestroyed()) return;
|
|
2062
|
+
applyVisibility(shouldShow(deps.getState()), animate);
|
|
2063
|
+
};
|
|
2064
|
+
const offs = [
|
|
2065
|
+
deps.on("snap", () => evaluate(true)),
|
|
2066
|
+
deps.on("open", () => evaluate(true)),
|
|
2067
|
+
deps.on("close", () => evaluate(true))
|
|
2068
|
+
];
|
|
2069
|
+
evaluate(false);
|
|
2070
|
+
return {
|
|
2071
|
+
wrapper,
|
|
2072
|
+
detach: () => {
|
|
2073
|
+
offs.forEach((off) => off());
|
|
2074
|
+
inFlight?.cancel();
|
|
2075
|
+
inFlight = null;
|
|
2076
|
+
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
2077
|
+
},
|
|
2078
|
+
syncZ: (z) => {
|
|
2079
|
+
wrapper.style.zIndex = String(z);
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// src/core/features/scrim-stages.ts
|
|
2085
|
+
var matchesId = (def, activeId) => {
|
|
2086
|
+
if (def.for === void 0) return false;
|
|
2087
|
+
return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
|
|
2088
|
+
};
|
|
2089
|
+
var matchesRange = (def, progress) => {
|
|
2090
|
+
if (!def.forRange) return false;
|
|
2091
|
+
const [from, to] = def.forRange;
|
|
2092
|
+
return progress >= from && progress <= to;
|
|
2093
|
+
};
|
|
2094
|
+
function installScrimStages(deps, opts) {
|
|
2095
|
+
const doc = deps.host.ownerDocument;
|
|
2096
|
+
const entries = opts.stages.map((def) => {
|
|
2097
|
+
const wrapper = doc.createElement("div");
|
|
2098
|
+
wrapper.className = "bs-scrim-stage";
|
|
2099
|
+
const ws = wrapper.style;
|
|
2100
|
+
ws.position = "absolute";
|
|
2101
|
+
applyOverlayPosition(
|
|
2102
|
+
ws,
|
|
2103
|
+
deps.mode,
|
|
2104
|
+
def.position ?? opts.position ?? "center",
|
|
2105
|
+
def.inset ?? opts.inset ?? "16px"
|
|
2106
|
+
);
|
|
2107
|
+
ws.visibility = "hidden";
|
|
2108
|
+
ws.pointerEvents = "none";
|
|
2109
|
+
wrapper.appendChild(def.element);
|
|
2110
|
+
deps.host.appendChild(wrapper);
|
|
2111
|
+
return { def, wrapper, inFlight: null };
|
|
2112
|
+
});
|
|
2113
|
+
let active = null;
|
|
2114
|
+
const resolveActive = () => {
|
|
2115
|
+
const state = deps.getState();
|
|
2116
|
+
if (state.size === 0) {
|
|
2117
|
+
return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
|
|
2118
|
+
}
|
|
2119
|
+
return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
|
|
2120
|
+
};
|
|
2121
|
+
const hide = (entry, animate) => {
|
|
2122
|
+
entry.inFlight?.cancel();
|
|
2123
|
+
entry.wrapper.style.pointerEvents = "none";
|
|
2124
|
+
if (!animate) {
|
|
2125
|
+
entry.inFlight = null;
|
|
2126
|
+
entry.wrapper.style.visibility = "hidden";
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
const handle = runAnchorTransition(
|
|
2130
|
+
entry.def.element,
|
|
2131
|
+
entry.def.animation ?? opts.animation,
|
|
2132
|
+
"exit"
|
|
2133
|
+
);
|
|
2134
|
+
entry.inFlight = handle;
|
|
2135
|
+
void handle.finished.then(() => {
|
|
2136
|
+
if (entry.inFlight === handle && active !== entry) {
|
|
2137
|
+
entry.wrapper.style.visibility = "hidden";
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
};
|
|
2141
|
+
const show = (entry, animate) => {
|
|
2142
|
+
entry.inFlight?.cancel();
|
|
2143
|
+
entry.inFlight = null;
|
|
2144
|
+
entry.wrapper.style.visibility = "";
|
|
2145
|
+
entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
|
|
2146
|
+
if (animate) {
|
|
2147
|
+
entry.inFlight = runAnchorTransition(
|
|
2148
|
+
entry.def.element,
|
|
2149
|
+
entry.def.animation ?? opts.animation,
|
|
2150
|
+
"enter"
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
const update = (animate) => {
|
|
2155
|
+
if (deps.isDestroyed()) return;
|
|
2156
|
+
const next = resolveActive();
|
|
2157
|
+
if (next === active) return;
|
|
2158
|
+
const prev = active;
|
|
2159
|
+
active = next;
|
|
2160
|
+
if (prev) hide(prev, animate);
|
|
2161
|
+
if (next) show(next, animate);
|
|
2162
|
+
};
|
|
2163
|
+
const offs = [
|
|
2164
|
+
deps.on("snap", () => update(true)),
|
|
2165
|
+
deps.on("open", () => update(true)),
|
|
2166
|
+
deps.on("close", () => update(true))
|
|
2167
|
+
];
|
|
2168
|
+
if (entries.some((e) => e.def.forRange)) {
|
|
2169
|
+
offs.push(deps.on("progress", () => update(true)));
|
|
2170
|
+
}
|
|
2171
|
+
update(false);
|
|
2172
|
+
return () => {
|
|
2173
|
+
offs.forEach((off) => off());
|
|
2174
|
+
for (const entry of entries) {
|
|
2175
|
+
entry.inFlight?.cancel();
|
|
2176
|
+
entry.inFlight = null;
|
|
2177
|
+
if (entry.wrapper.parentNode) {
|
|
2178
|
+
entry.wrapper.parentNode.removeChild(entry.wrapper);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
active = null;
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2031
2185
|
// src/core/primitives/engine-options.ts
|
|
2032
2186
|
var DEFAULT_FLICK_VELOCITY = 0.65;
|
|
2033
2187
|
var DEFAULT_DRAG_THRESHOLD = 18;
|
|
@@ -2052,9 +2206,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2052
2206
|
scrimMode: opts.scrimMode,
|
|
2053
2207
|
scrimColor: opts.scrimColor,
|
|
2054
2208
|
scrimBlur: opts.scrimBlur,
|
|
2055
|
-
// Auto-promote pointer-events when tap-to-close is on so the click
|
|
2056
|
-
// listener actually receives events. Caller can still force `false`
|
|
2057
|
-
// explicitly to opt out — `??` only kicks in for `undefined`.
|
|
2058
2209
|
scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
|
|
2059
2210
|
scrimTapToClose: opts.scrimTapToClose,
|
|
2060
2211
|
scrimPreset: opts.scrimPreset,
|
|
@@ -2075,10 +2226,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2075
2226
|
closeOnEscape: opts.closeOnEscape,
|
|
2076
2227
|
lockBodyScroll: opts.lockBodyScroll,
|
|
2077
2228
|
inertSiblings: opts.inertSiblings,
|
|
2078
|
-
// Folded in from `extras` so the lifecycle bag is fully assembled at
|
|
2079
|
-
// the call site — engine no longer needs a spread+override at
|
|
2080
|
-
// controller construction. When `extras` is omitted, the field stays
|
|
2081
|
-
// undefined and the controller uses its own default predicate.
|
|
2082
2229
|
shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
|
|
2083
2230
|
}
|
|
2084
2231
|
};
|
|
@@ -2113,6 +2260,7 @@ var BottomSheetEngine = class {
|
|
|
2113
2260
|
__publicField(this, "scrollContainer");
|
|
2114
2261
|
__publicField(this, "backdrop");
|
|
2115
2262
|
__publicField(this, "screenComponent");
|
|
2263
|
+
__publicField(this, "scrimParent", null);
|
|
2116
2264
|
__publicField(this, "mode");
|
|
2117
2265
|
__publicField(this, "flickVelocity");
|
|
2118
2266
|
__publicField(this, "dragThreshold");
|
|
@@ -2123,40 +2271,22 @@ var BottomSheetEngine = class {
|
|
|
2123
2271
|
__publicField(this, "lifecycle");
|
|
2124
2272
|
__publicField(this, "closeOnBack");
|
|
2125
2273
|
__publicField(this, "routedTo");
|
|
2126
|
-
// Engine retains `snapPointsRaw` for the "find a closed-id" query in
|
|
2127
|
-
// close() (raw input, not resolved geometry). Resolver owns geometry.
|
|
2128
2274
|
__publicField(this, "snapPointsRaw");
|
|
2129
2275
|
__publicField(this, "snaps");
|
|
2130
2276
|
__publicField(this, "activeId");
|
|
2131
2277
|
__publicField(this, "size", 0);
|
|
2132
|
-
// Constructed in attach(); a plugin's install() could read `state` before
|
|
2133
|
-
// attach completes if the constructor throws partway through. State getter
|
|
2134
|
-
// null-guards via `this.gesture?.isDragging ?? false`.
|
|
2135
2278
|
__publicField(this, "gesture");
|
|
2136
2279
|
__publicField(this, "rootEl", null);
|
|
2137
2280
|
__publicField(this, "destroyed", false);
|
|
2138
|
-
// In-flight ViewTransition handle. A second snapTo while a VT is in flight
|
|
2139
|
-
// would otherwise let Chromium reject the second's promise and flicker —
|
|
2140
|
-
// newCycle() calls skipTransition() to abort cleanly.
|
|
2141
2281
|
__publicField(this, "currentViewTransition", null);
|
|
2142
|
-
// Each cycle captures the signal at entry; when a newer cycle aborts the
|
|
2143
|
-
// controller, the captured signal flips to `aborted=true` and post-await
|
|
2144
|
-
// guards short-circuit (prevents stale snap/open/close emits).
|
|
2145
2282
|
__publicField(this, "currentAbort", new AbortController());
|
|
2146
|
-
// Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
|
|
2147
|
-
// visually distinguishable; gating CSSOM writes drops 30-50% of writes
|
|
2148
|
-
// during the spring's settle tail.
|
|
2149
2283
|
__publicField(this, "sizeWriteSentinel", new WriteSentinel());
|
|
2150
2284
|
__publicField(this, "progressWriteSentinel", new WriteSentinel());
|
|
2151
2285
|
__publicField(this, "isTopSheet", true);
|
|
2152
|
-
// Pooled payload — identity documented in SheetEventMap.progress JSDoc;
|
|
2153
|
-
// consumers must not retain across frames.
|
|
2154
2286
|
__publicField(this, "progressPayload", {
|
|
2155
2287
|
value: 0,
|
|
2156
2288
|
size: 0
|
|
2157
2289
|
});
|
|
2158
|
-
// Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
|
|
2159
|
-
// Inner `range` object identity is also stable (mutated in place).
|
|
2160
2290
|
__publicField(this, "dragContextBuf", {
|
|
2161
2291
|
size: 0,
|
|
2162
2292
|
maxAxisSize: 0,
|
|
@@ -2167,13 +2297,13 @@ var BottomSheetEngine = class {
|
|
|
2167
2297
|
__publicField(this, "persistKey");
|
|
2168
2298
|
__publicField(this, "linkedSheets", []);
|
|
2169
2299
|
__publicField(this, "scrollCache");
|
|
2170
|
-
// Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
|
|
2171
|
-
// tear down first before the infrastructure they depend on. Toggle-semantic
|
|
2172
|
-
// listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
|
|
2173
|
-
// fields on their controllers.
|
|
2174
2300
|
__publicField(this, "teardowns", new TeardownStack());
|
|
2175
|
-
|
|
2176
|
-
|
|
2301
|
+
__publicField(this, "anchors", []);
|
|
2302
|
+
__publicField(this, "anchorHost", null);
|
|
2303
|
+
__publicField(this, "stackZ", 100);
|
|
2304
|
+
__publicField(this, "detachScrimStages", null);
|
|
2305
|
+
__publicField(this, "stackEffectEnabled", false);
|
|
2306
|
+
__publicField(this, "stackEffectPrimed", false);
|
|
2177
2307
|
__publicField(this, "transformTemplate");
|
|
2178
2308
|
const resolved = resolveEngineOptions(opts, {
|
|
2179
2309
|
shouldApplyInertSiblings: () => {
|
|
@@ -2188,7 +2318,8 @@ var BottomSheetEngine = class {
|
|
|
2188
2318
|
this.handle = opts.handle ?? opts.element;
|
|
2189
2319
|
this.scrollContainer = opts.scrollContainer;
|
|
2190
2320
|
this.backdrop = opts.backdrop;
|
|
2191
|
-
this.screenComponent = opts.screenComponent;
|
|
2321
|
+
this.screenComponent = opts.scrim ?? opts.screenComponent;
|
|
2322
|
+
this.scrimParent = this.screenComponent?.parentElement ?? null;
|
|
2192
2323
|
this.mode = resolved.mode;
|
|
2193
2324
|
this.transformTemplate = buildTransformTemplate(this.mode);
|
|
2194
2325
|
this.aria = new AriaSliderWriter(this.handle, this.mode);
|
|
@@ -2206,6 +2337,9 @@ var BottomSheetEngine = class {
|
|
|
2206
2337
|
getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
|
|
2207
2338
|
snapTo: (id) => {
|
|
2208
2339
|
void this.snapTo(id);
|
|
2340
|
+
},
|
|
2341
|
+
close: () => {
|
|
2342
|
+
void this.close();
|
|
2209
2343
|
}
|
|
2210
2344
|
},
|
|
2211
2345
|
resolved.scrim
|
|
@@ -2216,8 +2350,6 @@ var BottomSheetEngine = class {
|
|
|
2216
2350
|
getRootEl: () => this.rootEl,
|
|
2217
2351
|
applySize: (size) => this.applySize(size),
|
|
2218
2352
|
getSize: () => this.size,
|
|
2219
|
-
// Optional chain protects synthetic test paths that drive animateTo
|
|
2220
|
-
// before attach() (gesture is constructed there).
|
|
2221
2353
|
isDragging: () => this.gesture?.isDragging ?? false
|
|
2222
2354
|
},
|
|
2223
2355
|
resolved.animation
|
|
@@ -2231,6 +2363,7 @@ var BottomSheetEngine = class {
|
|
|
2231
2363
|
);
|
|
2232
2364
|
this.closeOnBack = resolved.closeOnBack;
|
|
2233
2365
|
this.routedTo = opts.routedTo;
|
|
2366
|
+
this.stackEffectEnabled = opts.stackEffect ?? false;
|
|
2234
2367
|
this.snapPointsRaw = opts.snapPoints;
|
|
2235
2368
|
this.persistKey = opts.persistKey;
|
|
2236
2369
|
if (opts.linkedSheets) {
|
|
@@ -2243,7 +2376,7 @@ var BottomSheetEngine = class {
|
|
|
2243
2376
|
this.snapPointsRaw,
|
|
2244
2377
|
resolved.initialAllowed,
|
|
2245
2378
|
this.mode,
|
|
2246
|
-
() => this.handle.offsetHeight,
|
|
2379
|
+
() => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
|
|
2247
2380
|
(maxAxisSize) => {
|
|
2248
2381
|
this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
|
|
2249
2382
|
}
|
|
@@ -2322,21 +2455,6 @@ var BottomSheetEngine = class {
|
|
|
2322
2455
|
on(event, fn) {
|
|
2323
2456
|
return this.bus.on(event, fn);
|
|
2324
2457
|
}
|
|
2325
|
-
/**
|
|
2326
|
-
* Register a plugin. `install` runs synchronously; if it returns a function,
|
|
2327
|
-
* it's captured for teardown during `destroy()`. No-op if already destroyed
|
|
2328
|
-
* — installing into a torn-down engine would leak listeners.
|
|
2329
|
-
*
|
|
2330
|
-
* Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
|
|
2331
|
-
* so a buggy plugin can't take the engine down with it: the engine stays
|
|
2332
|
-
* usable, sibling plugins still install, and the error surfaces in dev tools
|
|
2333
|
-
* on the next tick instead of being swallowed. Mirrors the per-fn isolation
|
|
2334
|
-
* model used for teardowns.
|
|
2335
|
-
*
|
|
2336
|
-
* Plugins that accept the second `scope` argument get transactional cleanup:
|
|
2337
|
-
* push partial-install cleanups via `scope.add(fn)` and the engine drains
|
|
2338
|
-
* them on throw, OR merges them into destroy-time teardowns on success.
|
|
2339
|
-
*/
|
|
2340
2458
|
use(plugin) {
|
|
2341
2459
|
if (this.destroyed) {
|
|
2342
2460
|
console.warn(
|
|
@@ -2346,9 +2464,6 @@ var BottomSheetEngine = class {
|
|
|
2346
2464
|
}
|
|
2347
2465
|
const scoped = [];
|
|
2348
2466
|
const scope = {
|
|
2349
|
-
// typeof check — not just truthiness — so a JS-only plugin pushing a
|
|
2350
|
-
// bogus value is silently rejected instead of queueing a confusing
|
|
2351
|
-
// TypeError on destroy() drain.
|
|
2352
2467
|
add: (fn) => {
|
|
2353
2468
|
if (typeof fn === "function") scoped.push(fn);
|
|
2354
2469
|
}
|
|
@@ -2377,36 +2492,13 @@ var BottomSheetEngine = class {
|
|
|
2377
2492
|
}
|
|
2378
2493
|
return this;
|
|
2379
2494
|
}
|
|
2380
|
-
/**
|
|
2381
|
-
* Replace the linked-sheets list at runtime. Use when peer engines aren't
|
|
2382
|
-
* available at construction time (mounted later in the tree). The wiring
|
|
2383
|
-
* to the `open` event is independent of this list — it reads `linkedSheets`
|
|
2384
|
-
* lazily on emit, so this setter is a simple field swap.
|
|
2385
|
-
*/
|
|
2386
2495
|
setLinkedSheets(sheets) {
|
|
2387
2496
|
if (this.destroyed) return;
|
|
2388
2497
|
this.linkedSheets = sheets;
|
|
2389
2498
|
}
|
|
2390
|
-
/** Read-only view of the current allow-list, for cross-sheet target negotiation. */
|
|
2391
2499
|
getAllowedIds() {
|
|
2392
2500
|
return this.snaps.getAllowedIds().slice();
|
|
2393
2501
|
}
|
|
2394
|
-
/**
|
|
2395
|
-
* Animate to a snap point by id. Resolves when the animation completes.
|
|
2396
|
-
*
|
|
2397
|
-
* Two call shapes:
|
|
2398
|
-
* ```ts
|
|
2399
|
-
* engine.snapTo("full"); // default velocity
|
|
2400
|
-
* engine.snapTo("full", 0.5); // px/ms initial velocity
|
|
2401
|
-
* engine.snapTo("full", { velocity: 0.5, signal }); // abortable
|
|
2402
|
-
* ```
|
|
2403
|
-
*
|
|
2404
|
-
* When `opts.signal` is provided, calling `controller.abort()` cancels the
|
|
2405
|
-
* in-flight tween/spring and the returned promise resolves without firing
|
|
2406
|
-
* `snap`. The captured `AbortSignal` is checked after each await so external
|
|
2407
|
-
* aborts ride the same cycle invariant as internal ones (resize, destroy,
|
|
2408
|
-
* concurrent snapTo).
|
|
2409
|
-
*/
|
|
2410
2502
|
async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
|
|
2411
2503
|
if (this.destroyed) return;
|
|
2412
2504
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2465,16 +2557,6 @@ var BottomSheetEngine = class {
|
|
|
2465
2557
|
this.handleClose();
|
|
2466
2558
|
}
|
|
2467
2559
|
}
|
|
2468
|
-
/**
|
|
2469
|
-
* Drive the sheet to an arbitrary px size, bypassing snap matching.
|
|
2470
|
-
* Useful for preview scrubs, animation chaining, or programmatic gestures.
|
|
2471
|
-
* Animates with the configured spring/tween. Resolves when the animation
|
|
2472
|
-
* completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
|
|
2473
|
-
*
|
|
2474
|
-
* Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
|
|
2475
|
-
* nor `snap` (the sheet isn't settling to a known snap). `progress` events
|
|
2476
|
-
* still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
|
|
2477
|
-
*/
|
|
2478
2560
|
async dragTo(targetSize, velocityOrOpts = 0) {
|
|
2479
2561
|
if (this.destroyed) return;
|
|
2480
2562
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2504,28 +2586,13 @@ var BottomSheetEngine = class {
|
|
|
2504
2586
|
}
|
|
2505
2587
|
setAllowed(ids, snap) {
|
|
2506
2588
|
if (this.destroyed) return;
|
|
2507
|
-
const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
|
|
2508
|
-
if (!willSnap) {
|
|
2509
|
-
this.animation.cancel();
|
|
2510
|
-
this.newCycle();
|
|
2511
|
-
}
|
|
2512
2589
|
this.snaps.setAllowedIds(ids);
|
|
2513
2590
|
this.updateAriaSlider();
|
|
2514
2591
|
if (snap && ids.includes(snap)) {
|
|
2515
|
-
void this.snapTo(
|
|
2516
|
-
snap,
|
|
2517
|
-
0,
|
|
2518
|
-
/* internal */
|
|
2519
|
-
true
|
|
2520
|
-
);
|
|
2592
|
+
void this.snapTo(snap, 0, true);
|
|
2521
2593
|
} else if (!ids.includes(this.activeId)) {
|
|
2522
2594
|
const fallback = ids[0];
|
|
2523
|
-
if (fallback) void this.snapTo(
|
|
2524
|
-
fallback,
|
|
2525
|
-
0,
|
|
2526
|
-
/* internal */
|
|
2527
|
-
true
|
|
2528
|
-
);
|
|
2595
|
+
if (fallback) void this.snapTo(fallback, 0, true);
|
|
2529
2596
|
}
|
|
2530
2597
|
}
|
|
2531
2598
|
setBackdropRange(range) {
|
|
@@ -2543,21 +2610,6 @@ var BottomSheetEngine = class {
|
|
|
2543
2610
|
setScrimInteractive(interactive) {
|
|
2544
2611
|
this.scrim.setScrimInteractive(interactive);
|
|
2545
2612
|
}
|
|
2546
|
-
/**
|
|
2547
|
-
* Batch-update multiple scrim fields in one call. Apply order is:
|
|
2548
|
-
* 1. `preset` — fills baseline color/blur/range/interactive.
|
|
2549
|
-
* 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
|
|
2550
|
-
* 3. `enabled` — flips the opacity contribution on/off.
|
|
2551
|
-
* 4. `tapToClose` — installs/tears the click listener.
|
|
2552
|
-
* 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
|
|
2553
|
-
* 6. ONE `applySize` so the per-frame opacity stays consistent.
|
|
2554
|
-
*
|
|
2555
|
-
* Performance: collecting changes here (vs calling individual setters)
|
|
2556
|
-
* avoids repeated `applySize` calls during a multi-field swap. Setters
|
|
2557
|
-
* called here suppress their own internal `applySize` either by passing
|
|
2558
|
-
* through fast-paths (mode/enabled no-op when value unchanged) or by
|
|
2559
|
-
* being visual-only (color/blur/interactive don't influence opacity).
|
|
2560
|
-
*/
|
|
2561
2613
|
setScrim(opts) {
|
|
2562
2614
|
this.scrim.setScrim(opts, () => this.applySize(this.size));
|
|
2563
2615
|
}
|
|
@@ -2573,11 +2625,79 @@ var BottomSheetEngine = class {
|
|
|
2573
2625
|
setScrimOverlay(opts) {
|
|
2574
2626
|
return this.scrim.setScrimOverlay(opts);
|
|
2575
2627
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2628
|
+
addAnchor(opts) {
|
|
2629
|
+
if (this.destroyed || typeof document === "undefined") return () => {
|
|
2630
|
+
};
|
|
2631
|
+
if (!this.anchorHost) {
|
|
2632
|
+
this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
|
|
2633
|
+
this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
|
|
2634
|
+
this.anchorHost.style.setProperty(
|
|
2635
|
+
"--bs-progress",
|
|
2636
|
+
String(this.computeProgress(this.size))
|
|
2637
|
+
);
|
|
2638
|
+
}
|
|
2639
|
+
const handle = attachAnchor(
|
|
2640
|
+
{
|
|
2641
|
+
mode: this.mode,
|
|
2642
|
+
host: this.anchorHost,
|
|
2643
|
+
getState: () => ({
|
|
2644
|
+
activeId: this.activeId,
|
|
2645
|
+
size: this.size,
|
|
2646
|
+
progress: this.computeProgress(this.size)
|
|
2647
|
+
}),
|
|
2648
|
+
on: (event, fn) => this.on(event, fn),
|
|
2649
|
+
isDestroyed: () => this.destroyed
|
|
2650
|
+
},
|
|
2651
|
+
opts
|
|
2652
|
+
);
|
|
2653
|
+
handle.syncZ(this.stackZ + 1);
|
|
2654
|
+
this.anchors.push(handle);
|
|
2655
|
+
const detach = () => {
|
|
2656
|
+
const idx = this.anchors.indexOf(handle);
|
|
2657
|
+
if (idx === -1) return;
|
|
2658
|
+
this.anchors.splice(idx, 1);
|
|
2659
|
+
handle.detach();
|
|
2660
|
+
};
|
|
2661
|
+
this.teardowns.add(detach);
|
|
2662
|
+
return detach;
|
|
2663
|
+
}
|
|
2664
|
+
setScrimStages(opts) {
|
|
2665
|
+
if (this.destroyed || typeof document === "undefined") return () => {
|
|
2666
|
+
};
|
|
2667
|
+
this.detachScrimStages?.();
|
|
2668
|
+
this.detachScrimStages = null;
|
|
2669
|
+
if (!opts) return () => {
|
|
2670
|
+
};
|
|
2671
|
+
const host = this.screenComponent?.parentElement;
|
|
2672
|
+
if (!host) {
|
|
2673
|
+
console.warn(
|
|
2674
|
+
"[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
|
|
2675
|
+
);
|
|
2676
|
+
return () => {
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
const detachInstalled = installScrimStages(
|
|
2680
|
+
{
|
|
2681
|
+
mode: this.mode,
|
|
2682
|
+
host,
|
|
2683
|
+
getState: () => ({
|
|
2684
|
+
activeId: this.activeId,
|
|
2685
|
+
size: this.size,
|
|
2686
|
+
progress: this.computeProgress(this.size)
|
|
2687
|
+
}),
|
|
2688
|
+
on: (event, fn) => this.on(event, fn),
|
|
2689
|
+
isDestroyed: () => this.destroyed
|
|
2690
|
+
},
|
|
2691
|
+
opts
|
|
2692
|
+
);
|
|
2693
|
+
const detach = () => {
|
|
2694
|
+
if (this.detachScrimStages === detach) this.detachScrimStages = null;
|
|
2695
|
+
detachInstalled();
|
|
2696
|
+
};
|
|
2697
|
+
this.detachScrimStages = detach;
|
|
2698
|
+
this.teardowns.add(() => this.detachScrimStages?.());
|
|
2699
|
+
return detach;
|
|
2700
|
+
}
|
|
2581
2701
|
getScrimState() {
|
|
2582
2702
|
return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
|
|
2583
2703
|
}
|
|
@@ -2623,24 +2743,11 @@ var BottomSheetEngine = class {
|
|
|
2623
2743
|
emit(event, payload) {
|
|
2624
2744
|
this.bus.emit(event, payload);
|
|
2625
2745
|
}
|
|
2626
|
-
/**
|
|
2627
|
-
* Abort the current cycle's signal and replace the controller. Returns the
|
|
2628
|
-
* fresh signal so the caller can capture it locally before any await — when
|
|
2629
|
-
* a future newCycle() / destroy aborts our controller, the captured signal
|
|
2630
|
-
* flips to aborted and post-await guards short-circuit.
|
|
2631
|
-
*/
|
|
2632
2746
|
newCycle() {
|
|
2633
2747
|
this.currentAbort.abort();
|
|
2634
2748
|
this.currentAbort = new AbortController();
|
|
2635
2749
|
return this.currentAbort.signal;
|
|
2636
2750
|
}
|
|
2637
|
-
/**
|
|
2638
|
-
* Fire `before-snap` and report whether a listener cancelled it. The
|
|
2639
|
-
* `cancel()` callback exposed to listeners is frozen after the synchronous
|
|
2640
|
-
* emit phase so async callers can't silently miss — they get a console
|
|
2641
|
-
* warning. Returns `true` when cancelled so callers can early-return /
|
|
2642
|
-
* spring-back without re-implementing the cancel/frozen pattern.
|
|
2643
|
-
*/
|
|
2644
2751
|
emitBeforeSnap(target, previousId) {
|
|
2645
2752
|
let cancelled = false;
|
|
2646
2753
|
let frozen = false;
|
|
@@ -2674,59 +2781,54 @@ var BottomSheetEngine = class {
|
|
|
2674
2781
|
this.snaps.setRaw(this.snapPointsRaw);
|
|
2675
2782
|
this.scrim.invalidateOpacityCache();
|
|
2676
2783
|
}
|
|
2677
|
-
/**
|
|
2678
|
-
* Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
|
|
2679
|
-
* Engine wrapper kept so existing call sites don't all need updating;
|
|
2680
|
-
* resolver-level memoization survives across method invocations.
|
|
2681
|
-
*/
|
|
2682
2784
|
getAllowedRange() {
|
|
2683
2785
|
return this.snaps.getAllowedRange();
|
|
2684
2786
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
* without it screen readers can't announce drag/snap progress to keyboard
|
|
2691
|
-
* + AT users (WCAG 4.1.2 Name, Role, Value).
|
|
2692
|
-
*
|
|
2693
|
-
* Engine does NOT set `role="slider"` itself — that's the adapter's or
|
|
2694
|
-
* consumer markup's responsibility. We only decorate with dynamic values.
|
|
2695
|
-
*
|
|
2696
|
-
* Empty allowed-list is malformed input (settable via `setAllowed([])`);
|
|
2697
|
-
* short-circuit to remove ARIA value attributes so screen readers see a
|
|
2698
|
-
* no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
|
|
2699
|
-
* requires `valuemax > valuemin` for `role="slider"`).
|
|
2700
|
-
*/
|
|
2787
|
+
allowedIdsBySize() {
|
|
2788
|
+
return this.snaps.getAllowedIds().slice().sort(
|
|
2789
|
+
(a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2701
2792
|
updateAriaSlider() {
|
|
2702
|
-
this.aria.setValue(this.
|
|
2793
|
+
this.aria.setValue(this.allowedIdsBySize(), this.activeId);
|
|
2703
2794
|
}
|
|
2704
2795
|
registerInStack() {
|
|
2705
2796
|
this.teardowns.add(sheetStack.push({
|
|
2706
2797
|
id: this.id,
|
|
2707
2798
|
setZIndex: (z) => {
|
|
2799
|
+
this.stackZ = z;
|
|
2708
2800
|
this.element.style.zIndex = String(z);
|
|
2709
2801
|
if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
|
|
2802
|
+
for (const anchor of this.anchors) anchor.syncZ(z + 1);
|
|
2710
2803
|
},
|
|
2711
2804
|
setIsTop: (isTop) => {
|
|
2712
2805
|
this.isTopSheet = isTop;
|
|
2713
2806
|
if (this.backdrop) {
|
|
2714
2807
|
this.backdrop.style.display = isTop ? "" : "none";
|
|
2715
2808
|
}
|
|
2716
|
-
}
|
|
2809
|
+
},
|
|
2810
|
+
isOpen: () => this.size > 0,
|
|
2811
|
+
setDepth: (depth) => this.applyStackDepth(depth)
|
|
2717
2812
|
}));
|
|
2718
2813
|
}
|
|
2814
|
+
applyStackDepth(depth) {
|
|
2815
|
+
if (!this.stackEffectEnabled) return;
|
|
2816
|
+
this.element.setAttribute("data-stack-depth", String(depth));
|
|
2817
|
+
if (!this.stackEffectPrimed) {
|
|
2818
|
+
this.stackEffectPrimed = true;
|
|
2819
|
+
const isVertical = this.mode === "bottom" || this.mode === "top";
|
|
2820
|
+
this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
|
|
2821
|
+
this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
|
|
2822
|
+
}
|
|
2823
|
+
const scale = Math.max(1 - depth * 0.04, 0.86);
|
|
2824
|
+
this.element.style.scale = scale === 1 ? "" : String(scale);
|
|
2825
|
+
}
|
|
2719
2826
|
attach() {
|
|
2720
2827
|
this.gesture = new GestureController({
|
|
2721
2828
|
handle: this.handle,
|
|
2722
2829
|
element: this.element,
|
|
2723
2830
|
mode: this.mode,
|
|
2724
2831
|
getRoot: () => this.rootEl,
|
|
2725
|
-
// Single coalesced reader — narrows the DI surface and removes 3 getter
|
|
2726
|
-
// dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
|
|
2727
|
-
// (this.dragContextBuf) — see field JSDoc for the retention contract.
|
|
2728
|
-
// Inner `range` object is mutated in place too so callers caching its
|
|
2729
|
-
// identity between frames stay valid.
|
|
2730
2832
|
getDragContext: () => {
|
|
2731
2833
|
const buf = this.dragContextBuf;
|
|
2732
2834
|
const r = this.getAllowedRange();
|
|
@@ -2780,7 +2882,7 @@ var BottomSheetEngine = class {
|
|
|
2780
2882
|
container: this.scrollContainer,
|
|
2781
2883
|
isDragging: () => this.gesture?.isDragging ?? false,
|
|
2782
2884
|
isAnimating: () => this.animation.isAnimating,
|
|
2783
|
-
getAllowedIds: () => this.
|
|
2885
|
+
getAllowedIds: () => this.allowedIdsBySize(),
|
|
2784
2886
|
getActiveId: () => this.activeId,
|
|
2785
2887
|
snapTo: (id) => {
|
|
2786
2888
|
void this.snapTo(id);
|
|
@@ -2809,11 +2911,13 @@ var BottomSheetEngine = class {
|
|
|
2809
2911
|
},
|
|
2810
2912
|
newCycle: () => {
|
|
2811
2913
|
this.newCycle();
|
|
2812
|
-
}
|
|
2914
|
+
},
|
|
2915
|
+
isAnimating: () => this.animation.isAnimating,
|
|
2916
|
+
resyncAfterCancel: () => this.resyncAfterResize()
|
|
2813
2917
|
}));
|
|
2814
2918
|
if (this.lifecycle.closeOnEscape) {
|
|
2815
2919
|
const onKey = (e) => {
|
|
2816
|
-
if (this.destroyed) return;
|
|
2920
|
+
if (this.destroyed || e.defaultPrevented) return;
|
|
2817
2921
|
if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
|
|
2818
2922
|
void this.close();
|
|
2819
2923
|
}
|
|
@@ -2845,7 +2949,7 @@ var BottomSheetEngine = class {
|
|
|
2845
2949
|
handle: this.handle,
|
|
2846
2950
|
mode: this.mode,
|
|
2847
2951
|
isDestroyed: () => this.destroyed,
|
|
2848
|
-
getAllowedIds: () => this.
|
|
2952
|
+
getAllowedIds: () => this.allowedIdsBySize(),
|
|
2849
2953
|
getActiveId: () => this.activeId,
|
|
2850
2954
|
snapTo: (id) => {
|
|
2851
2955
|
void this.snapTo(id);
|
|
@@ -2866,6 +2970,7 @@ var BottomSheetEngine = class {
|
|
|
2866
2970
|
});
|
|
2867
2971
|
if (!target) return;
|
|
2868
2972
|
const previousId = this.activeId;
|
|
2973
|
+
if (target.id === previousId && target.size === this.size) return;
|
|
2869
2974
|
if (this.emitBeforeSnap(target, previousId)) {
|
|
2870
2975
|
const restore = this.snaps.findById(previousId);
|
|
2871
2976
|
if (restore) {
|
|
@@ -2874,6 +2979,7 @@ var BottomSheetEngine = class {
|
|
|
2874
2979
|
return;
|
|
2875
2980
|
}
|
|
2876
2981
|
const previousSize = this.size;
|
|
2982
|
+
const previousSnapSize = this.snaps.findById(previousId)?.size;
|
|
2877
2983
|
this.scrollCache.cache(previousId, previousSize, target.size);
|
|
2878
2984
|
this.activeId = target.id;
|
|
2879
2985
|
void this.animation.animateTo(target.size, velocity).then(() => {
|
|
@@ -2882,9 +2988,10 @@ var BottomSheetEngine = class {
|
|
|
2882
2988
|
this.updateAriaSlider();
|
|
2883
2989
|
this.emit("snap", { id: target.id, size: target.size });
|
|
2884
2990
|
this.haptic();
|
|
2885
|
-
if (
|
|
2991
|
+
if (previousSnapSize === 0 && target.size > 0) {
|
|
2886
2992
|
this.emit("open", { id: target.id });
|
|
2887
2993
|
this.handleOpen();
|
|
2994
|
+
notifyLinkedSheets(this.linkedSheets, this);
|
|
2888
2995
|
}
|
|
2889
2996
|
if (target.size === 0) {
|
|
2890
2997
|
this.emit("close", void 0);
|
|
@@ -2892,7 +2999,6 @@ var BottomSheetEngine = class {
|
|
|
2892
2999
|
}
|
|
2893
3000
|
});
|
|
2894
3001
|
}
|
|
2895
|
-
/** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
|
|
2896
3002
|
haptic() {
|
|
2897
3003
|
if (typeof navigator !== "undefined" && "vibrate" in navigator) {
|
|
2898
3004
|
try {
|
|
@@ -2908,6 +3014,12 @@ var BottomSheetEngine = class {
|
|
|
2908
3014
|
style.transform = this.transformTemplate(offset);
|
|
2909
3015
|
if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
|
|
2910
3016
|
style.setProperty("--bs-size", `${size}px`);
|
|
3017
|
+
if (this.scrimParent) {
|
|
3018
|
+
this.scrimParent.style.setProperty("--bs-size", `${size}px`);
|
|
3019
|
+
}
|
|
3020
|
+
if (this.anchorHost && this.anchorHost !== this.scrimParent) {
|
|
3021
|
+
this.anchorHost.style.setProperty("--bs-size", `${size}px`);
|
|
3022
|
+
}
|
|
2911
3023
|
}
|
|
2912
3024
|
const progress = this.computeProgress(size);
|
|
2913
3025
|
const progressChanged = this.progressWriteSentinel.shouldWrite(
|
|
@@ -2916,6 +3028,9 @@ var BottomSheetEngine = class {
|
|
|
2916
3028
|
);
|
|
2917
3029
|
if (progressChanged) {
|
|
2918
3030
|
style.setProperty("--bs-progress", String(progress));
|
|
3031
|
+
if (this.anchorHost) {
|
|
3032
|
+
this.anchorHost.style.setProperty("--bs-progress", String(progress));
|
|
3033
|
+
}
|
|
2919
3034
|
}
|
|
2920
3035
|
this.scrim.applyOpacity(progress, progressChanged);
|
|
2921
3036
|
if (progressChanged && this.bus.listenerCount("progress") > 0) {
|
|
@@ -2930,6 +3045,7 @@ var BottomSheetEngine = class {
|
|
|
2930
3045
|
return Math.min(Math.max((size - min) / (max - min), 0), 1);
|
|
2931
3046
|
}
|
|
2932
3047
|
handleOpen() {
|
|
3048
|
+
sheetStack.promote(this.id);
|
|
2933
3049
|
try {
|
|
2934
3050
|
this.lifecycle.install();
|
|
2935
3051
|
} catch (err) {
|
|
@@ -2943,8 +3059,24 @@ var BottomSheetEngine = class {
|
|
|
2943
3059
|
});
|
|
2944
3060
|
}
|
|
2945
3061
|
}
|
|
3062
|
+
resyncAfterResize() {
|
|
3063
|
+
if (this.destroyed) return;
|
|
3064
|
+
const target = this.snaps.findById(this.activeId);
|
|
3065
|
+
if (!target) return;
|
|
3066
|
+
this.updateAriaSlider();
|
|
3067
|
+
this.emit("snap", { id: target.id, size: target.size });
|
|
3068
|
+
if (target.size > 0 && !this.lifecycle.isInstalled) {
|
|
3069
|
+
this.emit("open", { id: target.id });
|
|
3070
|
+
this.handleOpen();
|
|
3071
|
+
notifyLinkedSheets(this.linkedSheets, this);
|
|
3072
|
+
} else if (target.size === 0 && this.lifecycle.isInstalled) {
|
|
3073
|
+
this.emit("close", void 0);
|
|
3074
|
+
this.handleClose();
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
2946
3077
|
handleClose() {
|
|
2947
3078
|
this.lifecycle.release();
|
|
3079
|
+
sheetStack.update();
|
|
2948
3080
|
}
|
|
2949
3081
|
};
|
|
2950
3082
|
var BottomSheet = qwik.component$((props) => {
|
|
@@ -2952,6 +3084,7 @@ var BottomSheet = qwik.component$((props) => {
|
|
|
2952
3084
|
const handleRef = qwik.useSignal();
|
|
2953
3085
|
const contentRef = qwik.useSignal();
|
|
2954
3086
|
const backdropRef = qwik.useSignal();
|
|
3087
|
+
const scrimRef = qwik.useSignal();
|
|
2955
3088
|
const initialActive = props.initial ?? props.snapPoints[0]?.id ?? "default";
|
|
2956
3089
|
const state = qwik.useStore({
|
|
2957
3090
|
size: 0,
|
|
@@ -2960,6 +3093,7 @@ var BottomSheet = qwik.component$((props) => {
|
|
|
2960
3093
|
isAnimating: false,
|
|
2961
3094
|
progress: 0
|
|
2962
3095
|
});
|
|
3096
|
+
const engineStore = qwik.useStore({ engine: void 0 });
|
|
2963
3097
|
qwik.useVisibleTask$(({ cleanup }) => {
|
|
2964
3098
|
if (!sheetRef.value) return;
|
|
2965
3099
|
if (!handleRef.value || !contentRef.value) {
|
|
@@ -2972,9 +3106,8 @@ var BottomSheet = qwik.component$((props) => {
|
|
|
2972
3106
|
handle: handleRef.value,
|
|
2973
3107
|
scrollContainer: contentRef.value,
|
|
2974
3108
|
backdrop: props.backdrop !== false ? backdropRef.value : void 0,
|
|
3109
|
+
scrim: scrimRef.value,
|
|
2975
3110
|
mode: props.mode ?? "bottom",
|
|
2976
|
-
// Widen literal-id back to string at the engine boundary — the engine
|
|
2977
|
-
// is string-typed at runtime, the TId narrowing is a TS-only veneer.
|
|
2978
3111
|
snapPoints: props.snapPoints,
|
|
2979
3112
|
allowed: props.allowed,
|
|
2980
3113
|
initial: props.initial,
|
|
@@ -2988,6 +3121,7 @@ var BottomSheet = qwik.component$((props) => {
|
|
|
2988
3121
|
backdropRange: props.backdropRange
|
|
2989
3122
|
};
|
|
2990
3123
|
const engine = new BottomSheetEngine(engineOpts);
|
|
3124
|
+
engineStore.engine = qwik.noSerialize(engine);
|
|
2991
3125
|
const sync = () => {
|
|
2992
3126
|
const s = engine.state;
|
|
2993
3127
|
state.size = s.size;
|
|
@@ -3004,13 +3138,27 @@ var BottomSheet = qwik.component$((props) => {
|
|
|
3004
3138
|
});
|
|
3005
3139
|
engine.on("dragstart", sync);
|
|
3006
3140
|
engine.on("dragend", sync);
|
|
3007
|
-
cleanup(() =>
|
|
3141
|
+
cleanup(() => {
|
|
3142
|
+
engineStore.engine = void 0;
|
|
3143
|
+
engine.destroy();
|
|
3144
|
+
});
|
|
3145
|
+
});
|
|
3146
|
+
qwik.useTask$(({ track }) => {
|
|
3147
|
+
const points = track(() => props.snapPoints);
|
|
3148
|
+
const allowed = track(() => props.allowed);
|
|
3149
|
+
const engine = engineStore.engine;
|
|
3150
|
+
if (!engine) return;
|
|
3151
|
+
engine.setSnapPoints(
|
|
3152
|
+
points,
|
|
3153
|
+
allowed
|
|
3154
|
+
);
|
|
3008
3155
|
});
|
|
3009
3156
|
const showBackdrop = props.backdrop !== false;
|
|
3010
3157
|
const mode = props.mode ?? "bottom";
|
|
3011
3158
|
const ariaLabel = props.ariaLabel ?? "Bottom sheet";
|
|
3012
3159
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bs-root", children: [
|
|
3013
3160
|
showBackdrop ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bs-backdrop", ref: backdropRef }) : null,
|
|
3161
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "bs-screen", ref: scrimRef, children: /* @__PURE__ */ jsxRuntime.jsx(qwik.Slot, { name: "screen" }) }),
|
|
3014
3162
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3015
3163
|
"section",
|
|
3016
3164
|
{
|