@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/svelte.cjs
CHANGED
|
@@ -46,6 +46,18 @@ var SheetStack = class {
|
|
|
46
46
|
this.entries.splice(idx, 1);
|
|
47
47
|
this.recompute();
|
|
48
48
|
}
|
|
49
|
+
promote(id) {
|
|
50
|
+
const idx = this.entries.findIndex((e) => e.id === id);
|
|
51
|
+
if (idx === -1) return;
|
|
52
|
+
if (idx !== this.entries.length - 1) {
|
|
53
|
+
const [entry] = this.entries.splice(idx, 1);
|
|
54
|
+
this.entries.push(entry);
|
|
55
|
+
}
|
|
56
|
+
this.recompute();
|
|
57
|
+
}
|
|
58
|
+
update() {
|
|
59
|
+
this.recompute();
|
|
60
|
+
}
|
|
49
61
|
size() {
|
|
50
62
|
return this.entries.length;
|
|
51
63
|
}
|
|
@@ -53,9 +65,24 @@ var SheetStack = class {
|
|
|
53
65
|
this.entries = [];
|
|
54
66
|
}
|
|
55
67
|
recompute() {
|
|
68
|
+
let top = null;
|
|
69
|
+
const open = [];
|
|
70
|
+
for (const entry of this.entries) {
|
|
71
|
+
if (!entry.isOpen || entry.isOpen()) {
|
|
72
|
+
top = entry;
|
|
73
|
+
open.push(entry);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!top && this.entries.length > 0) {
|
|
77
|
+
top = this.entries[this.entries.length - 1];
|
|
78
|
+
}
|
|
56
79
|
this.entries.forEach((entry, i) => {
|
|
57
80
|
entry.setZIndex(BASE_Z + i * STEP);
|
|
58
|
-
entry.setIsTop(
|
|
81
|
+
entry.setIsTop(entry === top);
|
|
82
|
+
if (entry.setDepth) {
|
|
83
|
+
const openIdx = open.indexOf(entry);
|
|
84
|
+
entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
|
|
85
|
+
}
|
|
59
86
|
});
|
|
60
87
|
}
|
|
61
88
|
};
|
|
@@ -254,11 +281,6 @@ var SnapResolver = class {
|
|
|
254
281
|
this.allowedIds = allowed ?? raw.map((p) => p.id);
|
|
255
282
|
this.recompute();
|
|
256
283
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Re-resolve raw → resolved against the current viewport / fit-measure,
|
|
259
|
-
* recompute maxAxisSize, invalidate range cache, notify engine. Called on
|
|
260
|
-
* resize, orientationchange, visualViewport changes, and snap-list swaps.
|
|
261
|
-
*/
|
|
262
284
|
recompute() {
|
|
263
285
|
this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
|
|
264
286
|
this.maxAxisSize = this.resolved.reduce(
|
|
@@ -268,15 +290,10 @@ var SnapResolver = class {
|
|
|
268
290
|
this.rangeCache = null;
|
|
269
291
|
this.onMaxAxisSizeChange?.(this.maxAxisSize);
|
|
270
292
|
}
|
|
271
|
-
/** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
|
|
272
293
|
setRaw(raw) {
|
|
273
294
|
this.raw = raw;
|
|
274
295
|
this.recompute();
|
|
275
296
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Replace the allow-list. Range cache depends on `(resolvedSnaps ×
|
|
278
|
-
* allowedIds)` so flipping ids alone invalidates without a full recompute.
|
|
279
|
-
*/
|
|
280
297
|
setAllowedIds(ids) {
|
|
281
298
|
this.allowedIds = ids;
|
|
282
299
|
this.rangeCache = null;
|
|
@@ -284,11 +301,6 @@ var SnapResolver = class {
|
|
|
284
301
|
findById(id) {
|
|
285
302
|
return findById(id, this.resolved);
|
|
286
303
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Pure drag-settle target resolution. Engine passes the gesture-specific
|
|
289
|
-
* fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
|
|
290
|
-
* `size`/`activeId` since those are still engine-owned.
|
|
291
|
-
*/
|
|
292
304
|
findDragSettleTarget(args) {
|
|
293
305
|
return findDragSettleTarget({
|
|
294
306
|
...args,
|
|
@@ -297,18 +309,9 @@ var SnapResolver = class {
|
|
|
297
309
|
maxAxisSize: this.maxAxisSize
|
|
298
310
|
});
|
|
299
311
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
|
|
302
|
-
* applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
|
|
303
|
-
* to one recompute per geometry/allow-list change.
|
|
304
|
-
*/
|
|
305
312
|
getAllowedRange() {
|
|
306
313
|
return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
|
|
307
314
|
}
|
|
308
|
-
/**
|
|
309
|
-
* Read-only view of resolved snaps. Returned as `readonly` to discourage
|
|
310
|
-
* accidental mutation by feature install sites that close over this list.
|
|
311
|
-
*/
|
|
312
315
|
getResolvedSnaps() {
|
|
313
316
|
return this.resolved;
|
|
314
317
|
}
|
|
@@ -318,18 +321,6 @@ var SnapResolver = class {
|
|
|
318
321
|
getMaxAxisSize() {
|
|
319
322
|
return this.maxAxisSize;
|
|
320
323
|
}
|
|
321
|
-
/**
|
|
322
|
-
* External clamp override. The resize-observer feature compares
|
|
323
|
-
* `getMaxAxisSize()` against the live viewport and shrinks if the largest
|
|
324
|
-
* resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
|
|
325
|
-
* window resize). The next `recompute()` overwrites this clamp from the
|
|
326
|
-
* resolved-snap reduction.
|
|
327
|
-
*
|
|
328
|
-
* Does NOT invalidate the range cache — viewport clamping doesn't change
|
|
329
|
-
* `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
|
|
330
|
-
* so the resolver owns the layout-axis style write end-to-end (symmetric
|
|
331
|
-
* with `recompute()` — feature call sites no longer duplicate the write).
|
|
332
|
-
*/
|
|
333
324
|
setMaxAxisSize(n) {
|
|
334
325
|
this.maxAxisSize = n;
|
|
335
326
|
this.onMaxAxisSizeChange?.(n);
|
|
@@ -376,14 +367,17 @@ function installAutoCollapse(deps) {
|
|
|
376
367
|
const fire = () => {
|
|
377
368
|
if (deps.isDestroyed()) return;
|
|
378
369
|
if (deps.isDragging()) return;
|
|
379
|
-
const
|
|
380
|
-
|
|
370
|
+
const active = deps.resolveSnap(deps.getActiveId());
|
|
371
|
+
if (!active || active.size === 0) return;
|
|
372
|
+
let target = null;
|
|
373
|
+
for (const id of deps.getAllowedIds()) {
|
|
381
374
|
const snap = deps.resolveSnap(id);
|
|
382
|
-
|
|
383
|
-
|
|
375
|
+
if (!snap || snap.size <= 0) continue;
|
|
376
|
+
if (!target || snap.size < target.size) target = snap;
|
|
377
|
+
}
|
|
384
378
|
if (!target) return;
|
|
385
|
-
if (target ===
|
|
386
|
-
deps.snapTo(target);
|
|
379
|
+
if (target.id === active.id || target.size >= active.size) return;
|
|
380
|
+
deps.snapTo(target.id);
|
|
387
381
|
};
|
|
388
382
|
const reset2 = () => {
|
|
389
383
|
clearTimer();
|
|
@@ -394,6 +388,7 @@ function installAutoCollapse(deps) {
|
|
|
394
388
|
}, ms);
|
|
395
389
|
};
|
|
396
390
|
const unsubscribe = deps.on("snap", () => reset2());
|
|
391
|
+
if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset2();
|
|
397
392
|
return () => {
|
|
398
393
|
clearTimer();
|
|
399
394
|
unsubscribe();
|
|
@@ -406,6 +401,7 @@ function notifyLinkedSheets(sheets, self) {
|
|
|
406
401
|
for (const linked of sheets) {
|
|
407
402
|
if (linked === self) continue;
|
|
408
403
|
try {
|
|
404
|
+
if (linked.state.size === 0) continue;
|
|
409
405
|
const ids = linked.getAllowedIds();
|
|
410
406
|
const target = ids.find((id) => id !== "closed");
|
|
411
407
|
if (target && target !== linked.state.activeId) {
|
|
@@ -452,7 +448,7 @@ function installRoute(deps) {
|
|
|
452
448
|
const onPop = (event) => {
|
|
453
449
|
if (deps.isDestroyed()) return;
|
|
454
450
|
const state2 = event.state;
|
|
455
|
-
if (state2
|
|
451
|
+
if ((!state2 || state2.__bsRouted !== target) && deps.getSize() > 0) {
|
|
456
452
|
routePushed = false;
|
|
457
453
|
void deps.close();
|
|
458
454
|
}
|
|
@@ -606,13 +602,17 @@ function installResizeObserver(deps) {
|
|
|
606
602
|
if (viewportSize2 > 0 && viewportSize2 < max) {
|
|
607
603
|
deps.setMaxAxisSize(viewportSize2);
|
|
608
604
|
}
|
|
605
|
+
const wasAnimating = deps.isAnimating?.() ?? false;
|
|
609
606
|
deps.cancelInFlight();
|
|
610
607
|
deps.newCycle();
|
|
611
608
|
const current = deps.resolveActiveSnap();
|
|
612
609
|
const newMax = deps.getMaxAxisSize();
|
|
613
610
|
const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
|
|
614
611
|
deps.setSize(targetSize);
|
|
615
|
-
if (current && !deps.isDragging())
|
|
612
|
+
if (current && !deps.isDragging()) {
|
|
613
|
+
deps.applySize(targetSize);
|
|
614
|
+
if (wasAnimating) deps.resyncAfterCancel?.();
|
|
615
|
+
}
|
|
616
616
|
};
|
|
617
617
|
let resizeObserver = null;
|
|
618
618
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -704,73 +704,130 @@ var WriteSentinel = class {
|
|
|
704
704
|
constructor() {
|
|
705
705
|
__publicField(this, "last", -1);
|
|
706
706
|
}
|
|
707
|
-
/**
|
|
708
|
-
* Returns `true` (and updates the cached value) if `next` differs from the
|
|
709
|
-
* cached `last` by more than `epsilon`. Returns `false` (and leaves the
|
|
710
|
-
* cache untouched) when the delta is within epsilon — caller skips the
|
|
711
|
-
* CSSOM write.
|
|
712
|
-
*/
|
|
713
707
|
shouldWrite(next, epsilon) {
|
|
714
708
|
if (Math.abs(next - this.last) <= epsilon) return false;
|
|
715
709
|
this.last = next;
|
|
716
710
|
return true;
|
|
717
711
|
}
|
|
718
|
-
/**
|
|
719
|
-
* Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
|
|
720
|
-
* non-negative `next` exceeds the epsilon comparison. Called from setters
|
|
721
|
-
* that mutate state under the cache (range/mode/enabled flips that change
|
|
722
|
-
* the progress→opacity mapping but don't move `progress` itself).
|
|
723
|
-
*/
|
|
724
712
|
invalidate() {
|
|
725
713
|
this.last = -1;
|
|
726
714
|
}
|
|
727
|
-
/**
|
|
728
|
-
* Record `value` as the last-written without performing a write. Used
|
|
729
|
-
* when a setter writes a literal value through a different path (e.g.
|
|
730
|
-
* `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
|
|
731
|
-
* `shouldWrite` calls to short-circuit against that known cached state.
|
|
732
|
-
*/
|
|
733
715
|
setLastWritten(value) {
|
|
734
716
|
this.last = value;
|
|
735
717
|
}
|
|
736
|
-
/** Read-only accessor for diagnostic / dedup-aware peek. */
|
|
737
718
|
get value() {
|
|
738
719
|
return this.last;
|
|
739
720
|
}
|
|
740
721
|
};
|
|
741
722
|
|
|
723
|
+
// src/core/primitives/devWarn.ts
|
|
724
|
+
function devWarn(...args) {
|
|
725
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
|
|
726
|
+
console.warn(...args);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/core/primitives/overlay-position.ts
|
|
731
|
+
function resolveSheetAnchoredStyle(mode, position, inset) {
|
|
732
|
+
const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
|
|
733
|
+
switch (mode) {
|
|
734
|
+
case "bottom":
|
|
735
|
+
if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
|
|
736
|
+
if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
|
|
737
|
+
return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
738
|
+
case "top":
|
|
739
|
+
if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
|
|
740
|
+
if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
|
|
741
|
+
return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
742
|
+
case "left":
|
|
743
|
+
if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
|
|
744
|
+
if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
|
|
745
|
+
return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
746
|
+
case "right":
|
|
747
|
+
if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
|
|
748
|
+
if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
|
|
749
|
+
return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function applyOverlayPosition(ws, mode, position, inset) {
|
|
753
|
+
switch (position) {
|
|
754
|
+
case "top-left":
|
|
755
|
+
ws.top = inset;
|
|
756
|
+
ws.left = inset;
|
|
757
|
+
break;
|
|
758
|
+
case "top-center":
|
|
759
|
+
ws.top = inset;
|
|
760
|
+
ws.left = "50%";
|
|
761
|
+
ws.transform = "translateX(-50%)";
|
|
762
|
+
break;
|
|
763
|
+
case "top-right":
|
|
764
|
+
ws.top = inset;
|
|
765
|
+
ws.right = inset;
|
|
766
|
+
break;
|
|
767
|
+
case "center-left":
|
|
768
|
+
ws.top = "50%";
|
|
769
|
+
ws.left = inset;
|
|
770
|
+
ws.transform = "translateY(-50%)";
|
|
771
|
+
break;
|
|
772
|
+
case "center":
|
|
773
|
+
ws.top = "50%";
|
|
774
|
+
ws.left = "50%";
|
|
775
|
+
ws.transform = "translate(-50%, -50%)";
|
|
776
|
+
break;
|
|
777
|
+
case "center-right":
|
|
778
|
+
ws.top = "50%";
|
|
779
|
+
ws.right = inset;
|
|
780
|
+
ws.transform = "translateY(-50%)";
|
|
781
|
+
break;
|
|
782
|
+
case "bottom-left":
|
|
783
|
+
ws.bottom = inset;
|
|
784
|
+
ws.left = inset;
|
|
785
|
+
break;
|
|
786
|
+
case "bottom-center":
|
|
787
|
+
ws.bottom = inset;
|
|
788
|
+
ws.left = "50%";
|
|
789
|
+
ws.transform = "translateX(-50%)";
|
|
790
|
+
break;
|
|
791
|
+
case "bottom-right":
|
|
792
|
+
ws.bottom = inset;
|
|
793
|
+
ws.right = inset;
|
|
794
|
+
break;
|
|
795
|
+
case "sheet-top-left":
|
|
796
|
+
case "sheet-top-center":
|
|
797
|
+
case "sheet-top-right": {
|
|
798
|
+
const style = resolveSheetAnchoredStyle(mode, position, inset);
|
|
799
|
+
if (style.top !== void 0) ws.top = style.top;
|
|
800
|
+
if (style.bottom !== void 0) ws.bottom = style.bottom;
|
|
801
|
+
if (style.left !== void 0) ws.left = style.left;
|
|
802
|
+
if (style.right !== void 0) ws.right = style.right;
|
|
803
|
+
if (style.transform !== void 0) ws.transform = style.transform;
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
742
809
|
// src/core/controllers/scrim-controller.ts
|
|
810
|
+
var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
|
|
743
811
|
var ScrimController = class {
|
|
744
812
|
constructor(deps, opts) {
|
|
745
|
-
// ── DOM refs (mirror engine's; cached for hot path) ─────────────────
|
|
746
813
|
__publicField(this, "screenComponent");
|
|
747
814
|
__publicField(this, "backdrop");
|
|
748
815
|
__publicField(this, "mode");
|
|
749
|
-
// ── Engine callbacks ────────────────────────────────────────────────
|
|
750
816
|
__publicField(this, "isDestroyed");
|
|
751
817
|
__publicField(this, "isTopSheet");
|
|
752
818
|
__publicField(this, "getAllowedIds");
|
|
753
819
|
__publicField(this, "getResolvedSnaps");
|
|
754
820
|
__publicField(this, "snapToFn");
|
|
755
|
-
|
|
821
|
+
__publicField(this, "closeFn");
|
|
822
|
+
__publicField(this, "scrimInteractive", false);
|
|
756
823
|
__publicField(this, "scrimMode");
|
|
757
824
|
__publicField(this, "backdropRange");
|
|
758
825
|
__publicField(this, "screenRange");
|
|
759
|
-
// setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
|
|
760
|
-
// ranges so re-enable can restore prior values without a remount. Public
|
|
761
|
-
// so tests / introspection can read it without an `as any` cast.
|
|
762
826
|
__publicField(this, "scrimEnabled", true);
|
|
763
827
|
__publicField(this, "savedScrimRanges", null);
|
|
764
|
-
// ── Tap-to-close ────────────────────────────────────────────────────
|
|
765
828
|
__publicField(this, "scrimTapToCloseEnabled", false);
|
|
766
829
|
__publicField(this, "detachScrimTap", null);
|
|
767
|
-
// ── Scrim overlay slot (positioned floating element) ────────────────
|
|
768
830
|
__publicField(this, "scrimOverlayTeardown", null);
|
|
769
|
-
// ── Hot-path opacity dedup caches (per-frame writes) ────────────────
|
|
770
|
-
// Numeric sentinels migrated to WriteSentinel primitive — single
|
|
771
|
-
// invalidation surface, shared with the engine's size/progress sentinels.
|
|
772
|
-
// The two string-typed sentinels (`Pointer`, `Display`) keep their inline
|
|
773
|
-
// pattern: equality on string union, no epsilon, different reset semantics.
|
|
774
831
|
__publicField(this, "backdropOpacitySentinel", new WriteSentinel());
|
|
775
832
|
__publicField(this, "screenOpacitySentinel", new WriteSentinel());
|
|
776
833
|
__publicField(this, "lastBackdropPointer", "");
|
|
@@ -783,6 +840,7 @@ var ScrimController = class {
|
|
|
783
840
|
this.getAllowedIds = deps.getAllowedIds;
|
|
784
841
|
this.getResolvedSnaps = deps.getResolvedSnaps;
|
|
785
842
|
this.snapToFn = deps.snapTo;
|
|
843
|
+
this.closeFn = deps.close;
|
|
786
844
|
this.scrimMode = opts.scrimMode ?? "full";
|
|
787
845
|
const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
|
|
788
846
|
this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
|
|
@@ -805,41 +863,15 @@ var ScrimController = class {
|
|
|
805
863
|
}
|
|
806
864
|
}
|
|
807
865
|
}
|
|
808
|
-
/**
|
|
809
|
-
* Invalidate the opacity dedup caches. Called from any setter that mutates
|
|
810
|
-
* range/mode/enabled — opacity is a pure function of (progress, range,
|
|
811
|
-
* enabled), so a config change makes the cached values stale even if
|
|
812
|
-
* `progress` itself didn't move. Next `applyOpacity(progress, false)` call
|
|
813
|
-
* sees the -1 sentinel and re-runs the math instead of short-circuiting.
|
|
814
|
-
*
|
|
815
|
-
* Public for the engine: `setSnapPoints` mutates resolved geometry which
|
|
816
|
-
* shifts `progress→opacity` mapping under the cache; engine calls this to
|
|
817
|
-
* keep the next render frame correct on a static sheet.
|
|
818
|
-
* @internal
|
|
819
|
-
*/
|
|
820
866
|
invalidateOpacityCache() {
|
|
821
867
|
this.backdropOpacitySentinel.invalidate();
|
|
822
868
|
this.screenOpacitySentinel.invalidate();
|
|
823
869
|
}
|
|
824
|
-
/** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
|
|
825
870
|
attach() {
|
|
826
871
|
if (this.scrimTapToCloseEnabled) {
|
|
827
872
|
this.setScrimTapToClose(true);
|
|
828
873
|
}
|
|
829
874
|
}
|
|
830
|
-
// ── Hot-path: opacity computation called per-frame from engine.applySize ──
|
|
831
|
-
/**
|
|
832
|
-
* Per-frame opacity writer. Computes backdrop + screen opacity from the
|
|
833
|
-
* sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
|
|
834
|
-
* caches so steady-state frames don't trigger style recalc.
|
|
835
|
-
*
|
|
836
|
-
* `progressChanged` is the engine's already-computed dedup gate — when
|
|
837
|
-
* `false`, opacity is by construction a pure function of `progress` so
|
|
838
|
-
* cached values are still valid; we short-circuit the Math.min/max +
|
|
839
|
-
* division work entirely. Setters that mutate ranges/mode/enabled
|
|
840
|
-
* invalidate the cache by writing -1 sentinels so the next call re-runs
|
|
841
|
-
* the math.
|
|
842
|
-
*/
|
|
843
875
|
applyOpacity(progress, progressChanged) {
|
|
844
876
|
if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
|
|
845
877
|
return;
|
|
@@ -885,80 +917,30 @@ var ScrimController = class {
|
|
|
885
917
|
}
|
|
886
918
|
}
|
|
887
919
|
}
|
|
888
|
-
// ── Public setters (engine forwards these 1:1) ─────────────────────
|
|
889
|
-
/**
|
|
890
|
-
* Replace the [start, end] progress window in which the backdrop fades 0→1.
|
|
891
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
892
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
893
|
-
* @example
|
|
894
|
-
* // Backdrop only starts darkening after the sheet crosses 30% open.
|
|
895
|
-
* engine.setBackdropRange([0.3, 1]);
|
|
896
|
-
*/
|
|
897
920
|
setBackdropRange(range, applySize) {
|
|
898
921
|
if (this.isDestroyed()) return;
|
|
899
922
|
this.backdropRange = range;
|
|
900
923
|
this.invalidateOpacityCache();
|
|
901
924
|
applySize();
|
|
902
925
|
}
|
|
903
|
-
/**
|
|
904
|
-
* Replace the [start, end] progress window in which the screen scrim fades 0→1.
|
|
905
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
906
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
907
|
-
* @example
|
|
908
|
-
* // Scrim is fully opaque only when the sheet is past 50% — softer feel.
|
|
909
|
-
* engine.setScreenRange([0, 0.5]);
|
|
910
|
-
*/
|
|
911
926
|
setScreenRange(range, applySize) {
|
|
912
927
|
if (this.isDestroyed()) return;
|
|
913
928
|
this.screenRange = range;
|
|
914
929
|
this.invalidateOpacityCache();
|
|
915
930
|
applySize();
|
|
916
931
|
}
|
|
917
|
-
/**
|
|
918
|
-
* Set the scrim background color. Pass `null`/`undefined` to clear the
|
|
919
|
-
* inline write and let consumer CSS take over.
|
|
920
|
-
* @param color - Any valid CSS color string, or `null`/`undefined` to clear.
|
|
921
|
-
* @example
|
|
922
|
-
* engine.setScrimColor("rgba(0, 0, 0, 0.6)");
|
|
923
|
-
* engine.setScrimColor(null); // hand back to consumer CSS
|
|
924
|
-
*/
|
|
925
932
|
setScrimColor(color) {
|
|
926
933
|
if (this.isDestroyed()) return;
|
|
927
934
|
this.applyScrimStyles({ color: color === void 0 ? null : color });
|
|
928
935
|
}
|
|
929
|
-
/**
|
|
930
|
-
* Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
|
|
931
|
-
* @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
|
|
932
|
-
* `null`/`undefined` to remove the filter.
|
|
933
|
-
* @example
|
|
934
|
-
* engine.setScrimBlur("12px"); // frosted-glass effect
|
|
935
|
-
* engine.setScrimBlur(null); // disable blur
|
|
936
|
-
*/
|
|
937
936
|
setScrimBlur(blur) {
|
|
938
937
|
if (this.isDestroyed()) return;
|
|
939
938
|
this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
|
|
940
939
|
}
|
|
941
|
-
/**
|
|
942
|
-
* Toggle whether the scrim layer captures pointer events.
|
|
943
|
-
* @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
|
|
944
|
-
* @example
|
|
945
|
-
* engine.setScrimInteractive(true); // scrim swallows clicks
|
|
946
|
-
* engine.setScrimInteractive(false); // clicks pass through to content below
|
|
947
|
-
*/
|
|
948
940
|
setScrimInteractive(interactive) {
|
|
949
941
|
if (this.isDestroyed()) return;
|
|
950
942
|
this.applyScrimStyles({ interactive });
|
|
951
943
|
}
|
|
952
|
-
/**
|
|
953
|
-
* Switch scrim positioning at runtime.
|
|
954
|
-
* @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
|
|
955
|
-
* so only the area NOT covered by the sheet is dimmed), or
|
|
956
|
-
* `"off"` (display:none + opacity:0 — no remount needed).
|
|
957
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
958
|
-
* @example
|
|
959
|
-
* engine.setScrimMode("above-sheet"); // dim only the area above the sheet
|
|
960
|
-
* engine.setScrimMode("off"); // disable scrim layer
|
|
961
|
-
*/
|
|
962
944
|
setScrimMode(mode, applySize) {
|
|
963
945
|
if (this.isDestroyed()) return;
|
|
964
946
|
if (mode === this.scrimMode) return;
|
|
@@ -988,15 +970,6 @@ var ScrimController = class {
|
|
|
988
970
|
}
|
|
989
971
|
applySize();
|
|
990
972
|
}
|
|
991
|
-
/**
|
|
992
|
-
* Enable or disable click-on-scrim → snap to first non-zero allowed snap.
|
|
993
|
-
* Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
|
|
994
|
-
* click listener actually fires.
|
|
995
|
-
* @param enabled - `true` installs the click listener; `false` removes it.
|
|
996
|
-
* @example
|
|
997
|
-
* engine.setScrimTapToClose(true); // tap outside sheet collapses it
|
|
998
|
-
* engine.setScrimTapToClose(false); // require explicit close button
|
|
999
|
-
*/
|
|
1000
973
|
setScrimTapToClose(enabled) {
|
|
1001
974
|
if (this.isDestroyed()) return;
|
|
1002
975
|
if (!this.screenComponent) return;
|
|
@@ -1009,11 +982,15 @@ var ScrimController = class {
|
|
|
1009
982
|
}
|
|
1010
983
|
const onClick = () => {
|
|
1011
984
|
if (this.isDestroyed()) return;
|
|
985
|
+
if (this.closeFn) {
|
|
986
|
+
this.closeFn();
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
1012
989
|
const allowed = this.getAllowedIds();
|
|
1013
990
|
const snaps = this.getResolvedSnaps();
|
|
1014
991
|
const fallback = allowed.find((id) => {
|
|
1015
992
|
const snap = findById(id, snaps);
|
|
1016
|
-
return snap !== null && snap.size
|
|
993
|
+
return snap !== null && snap.size === 0;
|
|
1017
994
|
});
|
|
1018
995
|
if (fallback) this.snapToFn(fallback);
|
|
1019
996
|
};
|
|
@@ -1026,20 +1003,11 @@ var ScrimController = class {
|
|
|
1026
1003
|
this.scrimTapToCloseEnabled = false;
|
|
1027
1004
|
}
|
|
1028
1005
|
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Toggle the scrim layer without remounting. Disabling stashes current
|
|
1031
|
-
* `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
|
|
1032
|
-
* formula → 0 across the whole range); re-enabling restores the saved values.
|
|
1033
|
-
* @param enabled - `false` hides scrim, `true` restores prior ranges.
|
|
1034
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
1035
|
-
* @example
|
|
1036
|
-
* engine.setScrimEnabled(false); // hide scrim, ranges remembered
|
|
1037
|
-
* engine.setScrimEnabled(true); // restore prior ranges (no remount)
|
|
1038
|
-
*/
|
|
1039
1006
|
setScrimEnabled(enabled, applySize) {
|
|
1040
1007
|
if (this.isDestroyed()) return;
|
|
1041
1008
|
if (enabled === this.scrimEnabled) return;
|
|
1042
1009
|
this.scrimEnabled = enabled;
|
|
1010
|
+
this.invalidateOpacityCache();
|
|
1043
1011
|
if (!enabled) {
|
|
1044
1012
|
this.savedScrimRanges = {
|
|
1045
1013
|
screen: this.screenRange,
|
|
@@ -1054,20 +1022,6 @@ var ScrimController = class {
|
|
|
1054
1022
|
}
|
|
1055
1023
|
applySize();
|
|
1056
1024
|
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Batch-update multiple scrim fields with a single applySize() at the end.
|
|
1059
|
-
* If `preset` is set, it seeds color/blur/interactive/range first; explicit
|
|
1060
|
-
* fields override.
|
|
1061
|
-
* @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
|
|
1062
|
-
* color, blur, interactive, range.
|
|
1063
|
-
* @param applySize - Engine-supplied recompute hook; runs ONCE per call.
|
|
1064
|
-
* @example
|
|
1065
|
-
* engine.setScrim({
|
|
1066
|
-
* preset: "frosted",
|
|
1067
|
-
* mode: "above-sheet",
|
|
1068
|
-
* tapToClose: true,
|
|
1069
|
-
* });
|
|
1070
|
-
*/
|
|
1071
1025
|
setScrim(opts, applySize) {
|
|
1072
1026
|
if (this.isDestroyed()) return;
|
|
1073
1027
|
if (opts.preset) {
|
|
@@ -1077,7 +1031,12 @@ var ScrimController = class {
|
|
|
1077
1031
|
blur: cfg.blur ?? null,
|
|
1078
1032
|
interactive: cfg.interactive
|
|
1079
1033
|
});
|
|
1080
|
-
this.
|
|
1034
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
1035
|
+
this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
|
|
1036
|
+
} else {
|
|
1037
|
+
this.screenRange = cfg.range;
|
|
1038
|
+
this.backdropRange = cfg.range;
|
|
1039
|
+
}
|
|
1081
1040
|
}
|
|
1082
1041
|
const noop = () => {
|
|
1083
1042
|
};
|
|
@@ -1090,27 +1049,18 @@ var ScrimController = class {
|
|
|
1090
1049
|
interactive: opts.interactive
|
|
1091
1050
|
});
|
|
1092
1051
|
if (opts.range !== void 0) {
|
|
1093
|
-
this.
|
|
1052
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
1053
|
+
this.savedScrimRanges = {
|
|
1054
|
+
screen: opts.range,
|
|
1055
|
+
backdrop: this.savedScrimRanges.backdrop
|
|
1056
|
+
};
|
|
1057
|
+
} else {
|
|
1058
|
+
this.screenRange = opts.range;
|
|
1059
|
+
}
|
|
1094
1060
|
}
|
|
1095
1061
|
this.invalidateOpacityCache();
|
|
1096
1062
|
applySize();
|
|
1097
1063
|
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Inject a positioned floating element into the scrim layer (e.g. a close
|
|
1100
|
-
* button anchored top-right). Replaces any prior overlay so repeated calls
|
|
1101
|
-
* (reactive effects re-running) don't accumulate wrappers.
|
|
1102
|
-
* @param opts - Children element, optional `inset` (default `"16px"`),
|
|
1103
|
-
* `position` (9-way grid, default `"top-right"`), and
|
|
1104
|
-
* `interactive` flag (default `true`).
|
|
1105
|
-
* @returns Teardown function that removes the wrapper from the DOM.
|
|
1106
|
-
* @example
|
|
1107
|
-
* const teardown = engine.setScrimOverlay({
|
|
1108
|
-
* children: closeBtn,
|
|
1109
|
-
* position: "top-right",
|
|
1110
|
-
* inset: "20px",
|
|
1111
|
-
* });
|
|
1112
|
-
* // later: teardown(); // remove the floating button
|
|
1113
|
-
*/
|
|
1114
1064
|
setScrimOverlay(opts) {
|
|
1115
1065
|
if (this.isDestroyed()) return () => {
|
|
1116
1066
|
};
|
|
@@ -1118,61 +1068,38 @@ var ScrimController = class {
|
|
|
1118
1068
|
};
|
|
1119
1069
|
this.scrimOverlayTeardown?.();
|
|
1120
1070
|
const screen = this.screenComponent;
|
|
1121
|
-
const
|
|
1071
|
+
const insetRaw = opts.inset ?? "16px";
|
|
1072
|
+
const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
|
|
1073
|
+
if (inset !== insetRaw) {
|
|
1074
|
+
devWarn(
|
|
1075
|
+
`[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
|
|
1079
|
+
devWarn(
|
|
1080
|
+
"[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."
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1122
1083
|
const position = opts.position ?? "top-right";
|
|
1123
1084
|
const interactive = opts.interactive ?? true;
|
|
1124
1085
|
const wrapper = screen.ownerDocument.createElement("div");
|
|
1125
1086
|
wrapper.className = "bs-scrim-overlay";
|
|
1126
1087
|
const ws = wrapper.style;
|
|
1127
1088
|
ws.position = "absolute";
|
|
1128
|
-
|
|
1129
|
-
case "top-left":
|
|
1130
|
-
ws.top = inset;
|
|
1131
|
-
ws.left = inset;
|
|
1132
|
-
break;
|
|
1133
|
-
case "top-center":
|
|
1134
|
-
ws.top = inset;
|
|
1135
|
-
ws.left = "50%";
|
|
1136
|
-
ws.transform = "translateX(-50%)";
|
|
1137
|
-
break;
|
|
1138
|
-
case "top-right":
|
|
1139
|
-
ws.top = inset;
|
|
1140
|
-
ws.right = inset;
|
|
1141
|
-
break;
|
|
1142
|
-
case "center-left":
|
|
1143
|
-
ws.top = "50%";
|
|
1144
|
-
ws.left = inset;
|
|
1145
|
-
ws.transform = "translateY(-50%)";
|
|
1146
|
-
break;
|
|
1147
|
-
case "center":
|
|
1148
|
-
ws.top = "50%";
|
|
1149
|
-
ws.left = "50%";
|
|
1150
|
-
ws.transform = "translate(-50%, -50%)";
|
|
1151
|
-
break;
|
|
1152
|
-
case "center-right":
|
|
1153
|
-
ws.top = "50%";
|
|
1154
|
-
ws.right = inset;
|
|
1155
|
-
ws.transform = "translateY(-50%)";
|
|
1156
|
-
break;
|
|
1157
|
-
case "bottom-left":
|
|
1158
|
-
ws.bottom = inset;
|
|
1159
|
-
ws.left = inset;
|
|
1160
|
-
break;
|
|
1161
|
-
case "bottom-center":
|
|
1162
|
-
ws.bottom = inset;
|
|
1163
|
-
ws.left = "50%";
|
|
1164
|
-
ws.transform = "translateX(-50%)";
|
|
1165
|
-
break;
|
|
1166
|
-
case "bottom-right":
|
|
1167
|
-
ws.bottom = inset;
|
|
1168
|
-
ws.right = inset;
|
|
1169
|
-
break;
|
|
1170
|
-
}
|
|
1089
|
+
applyOverlayPosition(ws, this.mode, position, inset);
|
|
1171
1090
|
if (interactive) {
|
|
1172
1091
|
ws.pointerEvents = "auto";
|
|
1173
1092
|
}
|
|
1174
1093
|
wrapper.appendChild(opts.children);
|
|
1175
|
-
screen.
|
|
1094
|
+
const host = screen.parentElement;
|
|
1095
|
+
if (!host) {
|
|
1096
|
+
devWarn(
|
|
1097
|
+
"[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
|
|
1098
|
+
);
|
|
1099
|
+
return () => {
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
host.appendChild(wrapper);
|
|
1176
1103
|
const teardown = () => {
|
|
1177
1104
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
1178
1105
|
if (this.scrimOverlayTeardown === teardown) {
|
|
@@ -1182,7 +1109,6 @@ var ScrimController = class {
|
|
|
1182
1109
|
this.scrimOverlayTeardown = teardown;
|
|
1183
1110
|
return teardown;
|
|
1184
1111
|
}
|
|
1185
|
-
/** Tear down toggle-state listeners + remove any injected overlay wrapper. */
|
|
1186
1112
|
destroy() {
|
|
1187
1113
|
this.detachScrimTap?.();
|
|
1188
1114
|
this.detachScrimTap = null;
|
|
@@ -1201,8 +1127,6 @@ var ScrimController = class {
|
|
|
1201
1127
|
this.lastBackdropPointer = "";
|
|
1202
1128
|
this.lastScreenDisplay = "";
|
|
1203
1129
|
}
|
|
1204
|
-
// ── Private helpers ────────────────────────────────────────────────
|
|
1205
|
-
/** Centralized scrim style writer — atomic 3-property write across all setters. */
|
|
1206
1130
|
applyScrimStyles(opts) {
|
|
1207
1131
|
if (!this.screenComponent) return;
|
|
1208
1132
|
const s = this.screenComponent.style;
|
|
@@ -1221,10 +1145,10 @@ var ScrimController = class {
|
|
|
1221
1145
|
}
|
|
1222
1146
|
}
|
|
1223
1147
|
if (opts.interactive !== void 0) {
|
|
1148
|
+
this.scrimInteractive = opts.interactive;
|
|
1224
1149
|
s.pointerEvents = opts.interactive ? "auto" : "none";
|
|
1225
1150
|
}
|
|
1226
1151
|
}
|
|
1227
|
-
/** Pin the screen overlay to the area NOT occupied by the sheet. */
|
|
1228
1152
|
applyAboveSheetInset() {
|
|
1229
1153
|
if (!this.screenComponent) return;
|
|
1230
1154
|
const insetByMode = {
|
|
@@ -1236,7 +1160,7 @@ var ScrimController = class {
|
|
|
1236
1160
|
const s = this.screenComponent.style;
|
|
1237
1161
|
s.position = "fixed";
|
|
1238
1162
|
s.inset = insetByMode[this.mode];
|
|
1239
|
-
s.pointerEvents = "none";
|
|
1163
|
+
s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
|
|
1240
1164
|
}
|
|
1241
1165
|
};
|
|
1242
1166
|
|
|
@@ -1290,6 +1214,10 @@ var tween = ({
|
|
|
1290
1214
|
promise
|
|
1291
1215
|
};
|
|
1292
1216
|
};
|
|
1217
|
+
var prefersReducedMotion = () => {
|
|
1218
|
+
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
1219
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1220
|
+
};
|
|
1293
1221
|
|
|
1294
1222
|
// src/core/animation/spring.ts
|
|
1295
1223
|
var DEFAULT_SPRING = {
|
|
@@ -1320,14 +1248,20 @@ var runSpring = ({
|
|
|
1320
1248
|
if (cancelled) return;
|
|
1321
1249
|
const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
|
|
1322
1250
|
lastTime = now;
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1251
|
+
const steps = Math.max(1, Math.ceil(dt * 240));
|
|
1252
|
+
const subDt = dt / steps;
|
|
1253
|
+
let stepsLeft = steps;
|
|
1325
1254
|
while (stepsLeft-- > 0) {
|
|
1326
1255
|
const force = -cfg.stiffness * (x - to) - cfg.damping * v;
|
|
1327
1256
|
const accel = force / cfg.mass;
|
|
1328
1257
|
v += accel * subDt;
|
|
1329
1258
|
x += v * subDt;
|
|
1330
1259
|
}
|
|
1260
|
+
if (!Number.isFinite(x) || !Number.isFinite(v)) {
|
|
1261
|
+
onUpdate(to, 0);
|
|
1262
|
+
resolve();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1331
1265
|
onUpdate(x, v);
|
|
1332
1266
|
const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
|
|
1333
1267
|
if (isAtRest) {
|
|
@@ -1391,13 +1325,9 @@ var AnimationRunner = class {
|
|
|
1391
1325
|
__publicField(this, "duration");
|
|
1392
1326
|
__publicField(this, "easing");
|
|
1393
1327
|
__publicField(this, "respectReducedMotion");
|
|
1394
|
-
/** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
|
|
1395
1328
|
__publicField(this, "reducedMotion", false);
|
|
1396
|
-
/** Disposes the matchMedia change listener — drained from destroy(). */
|
|
1397
1329
|
__publicField(this, "detachReducedMotion", null);
|
|
1398
|
-
/** Computed once — both inputs (option + API presence) stable for life. */
|
|
1399
1330
|
__publicField(this, "viewTransitionsAvailable");
|
|
1400
|
-
/** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
|
|
1401
1331
|
__publicField(this, "currentTween", null);
|
|
1402
1332
|
__publicField(this, "currentSpring", null);
|
|
1403
1333
|
this.element = deps.element;
|
|
@@ -1428,26 +1358,13 @@ var AnimationRunner = class {
|
|
|
1428
1358
|
}
|
|
1429
1359
|
}
|
|
1430
1360
|
}
|
|
1431
|
-
/**
|
|
1432
|
-
* Live derived flag — `true` whenever a spring/tween handle is in flight.
|
|
1433
|
-
* Reading the canonical handles (instead of mirroring into a separate
|
|
1434
|
-
* boolean) eliminates the reduced-motion early-return latent bug where
|
|
1435
|
-
* the flag stayed true forever, and removes the cross-controller
|
|
1436
|
-
* setIsAnimating callback.
|
|
1437
|
-
*/
|
|
1438
1361
|
get isAnimating() {
|
|
1439
1362
|
return this.currentTween !== null || this.currentSpring !== null;
|
|
1440
1363
|
}
|
|
1441
|
-
/** Cancel any in-flight tween/spring without touching engine state. */
|
|
1442
1364
|
cancel() {
|
|
1443
1365
|
this.currentTween?.cancel();
|
|
1444
1366
|
this.currentSpring?.cancel();
|
|
1445
1367
|
}
|
|
1446
|
-
/**
|
|
1447
|
-
* Animate from current size to `target`. Resolves when the animation
|
|
1448
|
-
* completes; resolves early if cancelled mid-flight (caller checks an
|
|
1449
|
-
* abort signal post-await for cycle-invariant correctness).
|
|
1450
|
-
*/
|
|
1451
1368
|
async animateTo(target, velocityPxPerMs) {
|
|
1452
1369
|
if (target === this.getSize()) return;
|
|
1453
1370
|
this.currentTween?.cancel();
|
|
@@ -1488,7 +1405,6 @@ var AnimationRunner = class {
|
|
|
1488
1405
|
if (!this.isDragging()) this.element.style.willChange = "auto";
|
|
1489
1406
|
}
|
|
1490
1407
|
}
|
|
1491
|
-
/** Tear down the matchMedia listener. Engine calls from destroy(). */
|
|
1492
1408
|
destroy() {
|
|
1493
1409
|
this.detachReducedMotion?.();
|
|
1494
1410
|
this.detachReducedMotion = null;
|
|
@@ -1520,9 +1436,13 @@ var isVisible = (el) => {
|
|
|
1520
1436
|
var focusables = (root2) => Array.from(root2.querySelectorAll(FOCUSABLE)).filter(
|
|
1521
1437
|
(el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
|
|
1522
1438
|
);
|
|
1439
|
+
var trapStack = [];
|
|
1523
1440
|
var installFocusTrap = (container, options = {}) => {
|
|
1524
1441
|
if (typeof document === "undefined") return () => {
|
|
1525
1442
|
};
|
|
1443
|
+
const token = {};
|
|
1444
|
+
trapStack.push(token);
|
|
1445
|
+
const isActive = () => trapStack[trapStack.length - 1] === token;
|
|
1526
1446
|
const previouslyFocused = document.activeElement;
|
|
1527
1447
|
const focusInitial = () => {
|
|
1528
1448
|
const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
|
|
@@ -1530,6 +1450,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1530
1450
|
};
|
|
1531
1451
|
focusInitial();
|
|
1532
1452
|
const handleKey = (e) => {
|
|
1453
|
+
if (!isActive()) return;
|
|
1533
1454
|
if (e.key === "Escape" && options.onEscape) {
|
|
1534
1455
|
options.onEscape();
|
|
1535
1456
|
return;
|
|
@@ -1552,6 +1473,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1552
1473
|
}
|
|
1553
1474
|
};
|
|
1554
1475
|
const handleFocusIn = (e) => {
|
|
1476
|
+
if (!isActive()) return;
|
|
1555
1477
|
const target = e.target;
|
|
1556
1478
|
if (target && !container.contains(target)) {
|
|
1557
1479
|
const list = focusables(container);
|
|
@@ -1564,6 +1486,8 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1564
1486
|
return () => {
|
|
1565
1487
|
if (released) return;
|
|
1566
1488
|
released = true;
|
|
1489
|
+
const idx = trapStack.indexOf(token);
|
|
1490
|
+
if (idx !== -1) trapStack.splice(idx, 1);
|
|
1567
1491
|
document.removeEventListener("keydown", handleKey, true);
|
|
1568
1492
|
document.removeEventListener("focusin", handleFocusIn, true);
|
|
1569
1493
|
if (previouslyFocused && document.contains(previouslyFocused)) {
|
|
@@ -1619,11 +1543,17 @@ function createInertSiblings(rootProvider) {
|
|
|
1619
1543
|
return {
|
|
1620
1544
|
apply() {
|
|
1621
1545
|
if (typeof document === "undefined") return;
|
|
1622
|
-
|
|
1623
|
-
|
|
1546
|
+
let top = rootProvider();
|
|
1547
|
+
while (top.parentElement && top.parentElement !== document.body) {
|
|
1548
|
+
top = top.parentElement;
|
|
1549
|
+
}
|
|
1550
|
+
const parent = top.parentElement ?? document.body;
|
|
1624
1551
|
for (const child2 of Array.from(parent.children)) {
|
|
1625
|
-
if (child2 ===
|
|
1552
|
+
if (child2 === top) continue;
|
|
1626
1553
|
if (child2.hasAttribute("inert")) continue;
|
|
1554
|
+
if (child2.classList.contains("bs-backdrop") || child2.classList.contains("bs-screen") || child2.classList.contains("bs-root")) {
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1627
1557
|
child2.setAttribute("inert", "");
|
|
1628
1558
|
tracked.push(child2);
|
|
1629
1559
|
}
|
|
@@ -1639,7 +1569,6 @@ function createInertSiblings(rootProvider) {
|
|
|
1639
1569
|
var LifecycleController = class {
|
|
1640
1570
|
constructor(deps, opts) {
|
|
1641
1571
|
__publicField(this, "element");
|
|
1642
|
-
__publicField(this, "closeFn");
|
|
1643
1572
|
__publicField(this, "focusTrapEnabled");
|
|
1644
1573
|
__publicField(this, "initialFocus");
|
|
1645
1574
|
__publicField(this, "closeOnEscape");
|
|
@@ -1648,16 +1577,11 @@ var LifecycleController = class {
|
|
|
1648
1577
|
__publicField(this, "inertSiblings");
|
|
1649
1578
|
__publicField(this, "shouldApplyInertSiblings");
|
|
1650
1579
|
__publicField(this, "returnFocus");
|
|
1651
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
1652
1580
|
__publicField(this, "releaseFocusTrap", null);
|
|
1653
1581
|
__publicField(this, "releaseScrollLock", null);
|
|
1654
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
1655
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
1656
|
-
* released or never-installed instances. */
|
|
1657
1582
|
__publicField(this, "installed", false);
|
|
1658
1583
|
__publicField(this, "destroyed", false);
|
|
1659
1584
|
this.element = deps.element;
|
|
1660
|
-
this.closeFn = deps.close;
|
|
1661
1585
|
this.focusTrapEnabled = opts.focusTrap ?? false;
|
|
1662
1586
|
this.initialFocus = opts.initialFocus;
|
|
1663
1587
|
this.closeOnEscape = opts.closeOnEscape ?? true;
|
|
@@ -1667,14 +1591,9 @@ var LifecycleController = class {
|
|
|
1667
1591
|
this.returnFocus = opts.returnFocus;
|
|
1668
1592
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
1669
1593
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
1674
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
1675
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
1676
|
-
* No-op when destroyed.
|
|
1677
|
-
*/
|
|
1594
|
+
get isInstalled() {
|
|
1595
|
+
return this.installed;
|
|
1596
|
+
}
|
|
1678
1597
|
install() {
|
|
1679
1598
|
if (this.destroyed) return;
|
|
1680
1599
|
this.installed = true;
|
|
@@ -1683,30 +1602,17 @@ var LifecycleController = class {
|
|
|
1683
1602
|
}
|
|
1684
1603
|
if (this.focusTrapEnabled && !this.releaseFocusTrap) {
|
|
1685
1604
|
this.releaseFocusTrap = installFocusTrap(this.element, {
|
|
1686
|
-
initialFocus: this.initialFocus
|
|
1687
|
-
onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
|
|
1605
|
+
initialFocus: this.initialFocus
|
|
1688
1606
|
});
|
|
1689
1607
|
}
|
|
1690
1608
|
if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
|
|
1691
1609
|
this.inertSiblings.apply();
|
|
1692
1610
|
}
|
|
1693
1611
|
}
|
|
1694
|
-
/**
|
|
1695
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
1696
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
1697
|
-
* on the next `release()`. No-op when destroyed.
|
|
1698
|
-
*/
|
|
1699
1612
|
setReturnFocus(target) {
|
|
1700
1613
|
if (this.destroyed) return;
|
|
1701
1614
|
this.returnFocus = target;
|
|
1702
1615
|
}
|
|
1703
|
-
/**
|
|
1704
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
1705
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
1706
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
1707
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
1708
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
1709
|
-
*/
|
|
1710
1616
|
release() {
|
|
1711
1617
|
if (!this.installed) return;
|
|
1712
1618
|
this.installed = false;
|
|
@@ -1720,12 +1626,6 @@ var LifecycleController = class {
|
|
|
1720
1626
|
target?.focus?.();
|
|
1721
1627
|
}
|
|
1722
1628
|
}
|
|
1723
|
-
/**
|
|
1724
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
1725
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
1726
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
1727
|
-
* restore.
|
|
1728
|
-
*/
|
|
1729
1629
|
destroy() {
|
|
1730
1630
|
this.release();
|
|
1731
1631
|
this.destroyed = true;
|
|
@@ -1735,12 +1635,10 @@ var LifecycleController = class {
|
|
|
1735
1635
|
// src/core/gestures.ts
|
|
1736
1636
|
var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
|
|
1737
1637
|
var SampleRing = class {
|
|
1738
|
-
// current count, ≤ capacity
|
|
1739
1638
|
constructor(capacity) {
|
|
1740
1639
|
__publicField(this, "slots");
|
|
1741
1640
|
__publicField(this, "capacity");
|
|
1742
1641
|
__publicField(this, "head", 0);
|
|
1743
|
-
// next write index
|
|
1744
1642
|
__publicField(this, "size", 0);
|
|
1745
1643
|
this.capacity = capacity;
|
|
1746
1644
|
this.slots = new Array(capacity);
|
|
@@ -1753,12 +1651,6 @@ var SampleRing = class {
|
|
|
1753
1651
|
this.head = (this.head + 1) % this.capacity;
|
|
1754
1652
|
if (this.size < this.capacity) this.size++;
|
|
1755
1653
|
}
|
|
1756
|
-
/**
|
|
1757
|
-
* Drop oldest entries whose timestamp is below `cutoff`, but never shrink
|
|
1758
|
-
* below `minKeep`. Mirrors the original
|
|
1759
|
-
* `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
|
|
1760
|
-
* but without copying — just advances the logical tail.
|
|
1761
|
-
*/
|
|
1762
1654
|
pruneBefore(cutoff, minKeep) {
|
|
1763
1655
|
while (this.size > minKeep) {
|
|
1764
1656
|
const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
|
|
@@ -1864,10 +1756,11 @@ var installGestures = (handle, mode, callbacks) => {
|
|
|
1864
1756
|
};
|
|
1865
1757
|
|
|
1866
1758
|
// src/core/primitives/rubber-band.ts
|
|
1867
|
-
var RUBBER_BAND_FACTOR = 0.55;
|
|
1868
1759
|
function rubberBand(overshoot, maxAxisSize) {
|
|
1869
1760
|
const cap = Math.min(maxAxisSize * 0.15, 80);
|
|
1870
|
-
|
|
1761
|
+
if (cap <= 0) return 0;
|
|
1762
|
+
const abs = Math.abs(overshoot);
|
|
1763
|
+
return Math.sign(overshoot) * (cap * abs / (abs + cap));
|
|
1871
1764
|
}
|
|
1872
1765
|
|
|
1873
1766
|
// src/core/features/soft-keyboard.ts
|
|
@@ -1894,28 +1787,14 @@ var GestureController = class {
|
|
|
1894
1787
|
__publicField(this, "detach", null);
|
|
1895
1788
|
__publicField(this, "isDragging_", false);
|
|
1896
1789
|
__publicField(this, "dragStartSize", 0);
|
|
1897
|
-
// Only "touch" gestures are eligible for soft-keyboard dismissal —
|
|
1898
|
-
// mouse/trackpad users don't have a keyboard to dismiss.
|
|
1899
1790
|
__publicField(this, "currentPointerKind", "touch");
|
|
1900
|
-
// One blur per drag — flipping focus every move event races the keyboard's
|
|
1901
|
-
// own animations on iOS and makes the page jitter.
|
|
1902
1791
|
__publicField(this, "keyboardDismissed", false);
|
|
1903
|
-
// Reused payload object for the "drag" event — mutated each onMove instead
|
|
1904
|
-
// of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
|
|
1905
|
-
// would otherwise churn tens of KB through the GC. Mirrors browser
|
|
1906
|
-
// PointerEvent semantics: consumers must NOT retain the reference across
|
|
1907
|
-
// frames (clone if needed). See SheetEventMap.drag JSDoc.
|
|
1908
1792
|
__publicField(this, "dragPayload", { size: 0, delta: 0 });
|
|
1909
1793
|
this.deps = deps;
|
|
1910
1794
|
}
|
|
1911
|
-
/** True between `dragstart` and `dragend`/`onCancel`. */
|
|
1912
1795
|
get isDragging() {
|
|
1913
1796
|
return this.isDragging_;
|
|
1914
1797
|
}
|
|
1915
|
-
/**
|
|
1916
|
-
* Bind pointer listeners on the handle and return a teardown. Engine pushes
|
|
1917
|
-
* the teardown into its TeardownStack so destroy() drains it once.
|
|
1918
|
-
*/
|
|
1919
1798
|
install() {
|
|
1920
1799
|
if (this.detach) return this.detach;
|
|
1921
1800
|
this.detach = installGestures(this.deps.handle, this.deps.mode, {
|
|
@@ -1966,17 +1845,16 @@ var GestureController = class {
|
|
|
1966
1845
|
this.isDragging_ = false;
|
|
1967
1846
|
this.keyboardDismissed = false;
|
|
1968
1847
|
this.deps.getRoot()?.removeAttribute("data-dragging");
|
|
1848
|
+
this.deps.emit("dragend", {
|
|
1849
|
+
size: this.deps.getDragContext().size,
|
|
1850
|
+
velocity: 0
|
|
1851
|
+
});
|
|
1969
1852
|
const restoreTo = this.dragStartSize;
|
|
1970
1853
|
void this.deps.animateTo(restoreTo, 0);
|
|
1971
1854
|
}
|
|
1972
1855
|
});
|
|
1973
1856
|
return this.detach;
|
|
1974
1857
|
}
|
|
1975
|
-
/**
|
|
1976
|
-
* Force-clear drag visual state. Engine calls from destroy() when destroyed
|
|
1977
|
-
* mid-drag — gesture detach won't synthesize an onCancel, so we have to
|
|
1978
|
-
* roll back the data-attribute + will-change ourselves.
|
|
1979
|
-
*/
|
|
1980
1858
|
forceClearDragState() {
|
|
1981
1859
|
if (!this.isDragging_) return;
|
|
1982
1860
|
this.isDragging_ = false;
|
|
@@ -1995,11 +1873,6 @@ var AriaSliderWriter = class {
|
|
|
1995
1873
|
mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
|
|
1996
1874
|
);
|
|
1997
1875
|
}
|
|
1998
|
-
/**
|
|
1999
|
-
* Update slider position. `allowedIds` length determines `aria-valuemax`;
|
|
2000
|
-
* `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
|
|
2001
|
-
* Empty list → strip every dynamic attr so AT skips the handle entirely.
|
|
2002
|
-
*/
|
|
2003
1876
|
setValue(allowedIds, activeId) {
|
|
2004
1877
|
if (allowedIds.length === 0) {
|
|
2005
1878
|
this.handle.removeAttribute("aria-valuemin");
|
|
@@ -2049,6 +1922,287 @@ function createScrollCache(deps) {
|
|
|
2049
1922
|
};
|
|
2050
1923
|
}
|
|
2051
1924
|
|
|
1925
|
+
// src/core/primitives/anchor-animations.ts
|
|
1926
|
+
var DEFAULT_DURATION3 = 200;
|
|
1927
|
+
var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1928
|
+
var PRESET_FRAMES = {
|
|
1929
|
+
fade: {
|
|
1930
|
+
enter: [{ opacity: 0 }, { opacity: 1 }],
|
|
1931
|
+
exit: [{ opacity: 1 }, { opacity: 0 }]
|
|
1932
|
+
},
|
|
1933
|
+
scale: {
|
|
1934
|
+
enter: [
|
|
1935
|
+
{ opacity: 0, transform: "scale(0.85)" },
|
|
1936
|
+
{ opacity: 1, transform: "scale(1)" }
|
|
1937
|
+
],
|
|
1938
|
+
exit: [
|
|
1939
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
1940
|
+
{ opacity: 0, transform: "scale(0.85)" }
|
|
1941
|
+
]
|
|
1942
|
+
},
|
|
1943
|
+
slide: {
|
|
1944
|
+
enter: [
|
|
1945
|
+
{ opacity: 0, transform: "translateY(10px)" },
|
|
1946
|
+
{ opacity: 1, transform: "translateY(0)" }
|
|
1947
|
+
],
|
|
1948
|
+
exit: [
|
|
1949
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
1950
|
+
{ opacity: 0, transform: "translateY(10px)" }
|
|
1951
|
+
]
|
|
1952
|
+
},
|
|
1953
|
+
pop: {
|
|
1954
|
+
enter: [
|
|
1955
|
+
{ opacity: 0, transform: "scale(0.6)", offset: 0 },
|
|
1956
|
+
{ opacity: 1, transform: "scale(1.06)", offset: 0.7 },
|
|
1957
|
+
{ opacity: 1, transform: "scale(1)", offset: 1 }
|
|
1958
|
+
],
|
|
1959
|
+
exit: [
|
|
1960
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
1961
|
+
{ opacity: 0, transform: "scale(0.6)" }
|
|
1962
|
+
]
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
var INSTANT = {
|
|
1966
|
+
finished: Promise.resolve(),
|
|
1967
|
+
cancel: () => {
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
function resolveTransitionFrames(spec, phase) {
|
|
1971
|
+
const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
|
|
1972
|
+
if (spec === "none") return fallback;
|
|
1973
|
+
if (spec === void 0 || typeof spec === "string") {
|
|
1974
|
+
const preset2 = PRESET_FRAMES[spec ?? "fade"];
|
|
1975
|
+
return {
|
|
1976
|
+
frames: preset2[phase],
|
|
1977
|
+
duration: DEFAULT_DURATION3,
|
|
1978
|
+
easing: DEFAULT_EASING
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
const custom = phase === "enter" ? spec.enter : spec.exit;
|
|
1982
|
+
if (custom) {
|
|
1983
|
+
return {
|
|
1984
|
+
frames: custom,
|
|
1985
|
+
duration: spec.duration ?? DEFAULT_DURATION3,
|
|
1986
|
+
easing: spec.easing ?? DEFAULT_EASING
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
if (spec.preset === "none") return fallback;
|
|
1990
|
+
const preset = PRESET_FRAMES[spec.preset ?? "fade"];
|
|
1991
|
+
return {
|
|
1992
|
+
frames: preset[phase],
|
|
1993
|
+
duration: spec.duration ?? DEFAULT_DURATION3,
|
|
1994
|
+
easing: spec.easing ?? DEFAULT_EASING
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
function runAnchorTransition(el, spec, phase) {
|
|
1998
|
+
const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
|
|
1999
|
+
if (respectReduced && prefersReducedMotion()) return INSTANT;
|
|
2000
|
+
const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
|
|
2001
|
+
if (!frames || duration <= 0) return INSTANT;
|
|
2002
|
+
if (typeof el.animate !== "function") return INSTANT;
|
|
2003
|
+
const animation = el.animate(frames, {
|
|
2004
|
+
duration,
|
|
2005
|
+
easing,
|
|
2006
|
+
fill: "both"
|
|
2007
|
+
});
|
|
2008
|
+
const finished = animation.finished.then(() => void 0).catch(() => void 0);
|
|
2009
|
+
return {
|
|
2010
|
+
finished,
|
|
2011
|
+
cancel: () => {
|
|
2012
|
+
try {
|
|
2013
|
+
animation.cancel();
|
|
2014
|
+
} catch {
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/core/features/sheet-anchors.ts
|
|
2022
|
+
function attachAnchor(deps, opts) {
|
|
2023
|
+
const doc = deps.host.ownerDocument;
|
|
2024
|
+
const wrapper = doc.createElement("div");
|
|
2025
|
+
wrapper.className = "bs-anchor";
|
|
2026
|
+
const interactive = opts.interactive ?? true;
|
|
2027
|
+
const position = opts.position ?? "sheet-top-right";
|
|
2028
|
+
const isDock = position === "dock-bottom" || position === "dock-top";
|
|
2029
|
+
const ws = wrapper.style;
|
|
2030
|
+
ws.position = "fixed";
|
|
2031
|
+
if (isDock) {
|
|
2032
|
+
ws.left = "0";
|
|
2033
|
+
ws.right = "0";
|
|
2034
|
+
if (position === "dock-bottom") {
|
|
2035
|
+
ws.bottom = "0";
|
|
2036
|
+
} else {
|
|
2037
|
+
ws.top = "0";
|
|
2038
|
+
}
|
|
2039
|
+
} else {
|
|
2040
|
+
applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
|
|
2041
|
+
}
|
|
2042
|
+
if (opts.fadeRange) {
|
|
2043
|
+
const [r0, r1] = opts.fadeRange;
|
|
2044
|
+
const span = Math.max(r1 - r0, 1e-4);
|
|
2045
|
+
ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
|
|
2046
|
+
}
|
|
2047
|
+
wrapper.appendChild(opts.element);
|
|
2048
|
+
deps.host.appendChild(wrapper);
|
|
2049
|
+
let visible = null;
|
|
2050
|
+
let inFlight = null;
|
|
2051
|
+
const shouldShow = (state2) => {
|
|
2052
|
+
if (typeof opts.showOn === "function") return opts.showOn(state2);
|
|
2053
|
+
if (Array.isArray(opts.showOn)) return opts.showOn.includes(state2.activeId);
|
|
2054
|
+
return isDock || state2.size > 0;
|
|
2055
|
+
};
|
|
2056
|
+
const applyVisibility = (next, animate) => {
|
|
2057
|
+
if (next === visible) return;
|
|
2058
|
+
visible = next;
|
|
2059
|
+
inFlight?.cancel();
|
|
2060
|
+
inFlight = null;
|
|
2061
|
+
wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
|
|
2062
|
+
if (next) {
|
|
2063
|
+
wrapper.style.visibility = "";
|
|
2064
|
+
if (animate) {
|
|
2065
|
+
inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
|
|
2066
|
+
}
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
if (!animate) {
|
|
2070
|
+
wrapper.style.visibility = "hidden";
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
const handle = runAnchorTransition(opts.element, opts.animation, "exit");
|
|
2074
|
+
inFlight = handle;
|
|
2075
|
+
void handle.finished.then(() => {
|
|
2076
|
+
if (inFlight === handle && visible === false) {
|
|
2077
|
+
wrapper.style.visibility = "hidden";
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
};
|
|
2081
|
+
const evaluate = (animate) => {
|
|
2082
|
+
if (deps.isDestroyed()) return;
|
|
2083
|
+
applyVisibility(shouldShow(deps.getState()), animate);
|
|
2084
|
+
};
|
|
2085
|
+
const offs = [
|
|
2086
|
+
deps.on("snap", () => evaluate(true)),
|
|
2087
|
+
deps.on("open", () => evaluate(true)),
|
|
2088
|
+
deps.on("close", () => evaluate(true))
|
|
2089
|
+
];
|
|
2090
|
+
evaluate(false);
|
|
2091
|
+
return {
|
|
2092
|
+
wrapper,
|
|
2093
|
+
detach: () => {
|
|
2094
|
+
offs.forEach((off) => off());
|
|
2095
|
+
inFlight?.cancel();
|
|
2096
|
+
inFlight = null;
|
|
2097
|
+
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
2098
|
+
},
|
|
2099
|
+
syncZ: (z) => {
|
|
2100
|
+
wrapper.style.zIndex = String(z);
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// src/core/features/scrim-stages.ts
|
|
2106
|
+
var matchesId = (def, activeId) => {
|
|
2107
|
+
if (def.for === void 0) return false;
|
|
2108
|
+
return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
|
|
2109
|
+
};
|
|
2110
|
+
var matchesRange = (def, progress) => {
|
|
2111
|
+
if (!def.forRange) return false;
|
|
2112
|
+
const [from, to] = def.forRange;
|
|
2113
|
+
return progress >= from && progress <= to;
|
|
2114
|
+
};
|
|
2115
|
+
function installScrimStages(deps, opts) {
|
|
2116
|
+
const doc = deps.host.ownerDocument;
|
|
2117
|
+
const entries = opts.stages.map((def) => {
|
|
2118
|
+
const wrapper = doc.createElement("div");
|
|
2119
|
+
wrapper.className = "bs-scrim-stage";
|
|
2120
|
+
const ws = wrapper.style;
|
|
2121
|
+
ws.position = "absolute";
|
|
2122
|
+
applyOverlayPosition(
|
|
2123
|
+
ws,
|
|
2124
|
+
deps.mode,
|
|
2125
|
+
def.position ?? opts.position ?? "center",
|
|
2126
|
+
def.inset ?? opts.inset ?? "16px"
|
|
2127
|
+
);
|
|
2128
|
+
ws.visibility = "hidden";
|
|
2129
|
+
ws.pointerEvents = "none";
|
|
2130
|
+
wrapper.appendChild(def.element);
|
|
2131
|
+
deps.host.appendChild(wrapper);
|
|
2132
|
+
return { def, wrapper, inFlight: null };
|
|
2133
|
+
});
|
|
2134
|
+
let active = null;
|
|
2135
|
+
const resolveActive = () => {
|
|
2136
|
+
const state2 = deps.getState();
|
|
2137
|
+
if (state2.size === 0) {
|
|
2138
|
+
return entries.find((e) => matchesId(e.def, state2.activeId)) ?? null;
|
|
2139
|
+
}
|
|
2140
|
+
return entries.find((e) => matchesId(e.def, state2.activeId)) ?? entries.find((e) => matchesRange(e.def, state2.progress)) ?? null;
|
|
2141
|
+
};
|
|
2142
|
+
const hide = (entry, animate) => {
|
|
2143
|
+
entry.inFlight?.cancel();
|
|
2144
|
+
entry.wrapper.style.pointerEvents = "none";
|
|
2145
|
+
if (!animate) {
|
|
2146
|
+
entry.inFlight = null;
|
|
2147
|
+
entry.wrapper.style.visibility = "hidden";
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
const handle = runAnchorTransition(
|
|
2151
|
+
entry.def.element,
|
|
2152
|
+
entry.def.animation ?? opts.animation,
|
|
2153
|
+
"exit"
|
|
2154
|
+
);
|
|
2155
|
+
entry.inFlight = handle;
|
|
2156
|
+
void handle.finished.then(() => {
|
|
2157
|
+
if (entry.inFlight === handle && active !== entry) {
|
|
2158
|
+
entry.wrapper.style.visibility = "hidden";
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
};
|
|
2162
|
+
const show = (entry, animate) => {
|
|
2163
|
+
entry.inFlight?.cancel();
|
|
2164
|
+
entry.inFlight = null;
|
|
2165
|
+
entry.wrapper.style.visibility = "";
|
|
2166
|
+
entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
|
|
2167
|
+
if (animate) {
|
|
2168
|
+
entry.inFlight = runAnchorTransition(
|
|
2169
|
+
entry.def.element,
|
|
2170
|
+
entry.def.animation ?? opts.animation,
|
|
2171
|
+
"enter"
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
const update = (animate) => {
|
|
2176
|
+
if (deps.isDestroyed()) return;
|
|
2177
|
+
const next = resolveActive();
|
|
2178
|
+
if (next === active) return;
|
|
2179
|
+
const prev = active;
|
|
2180
|
+
active = next;
|
|
2181
|
+
if (prev) hide(prev, animate);
|
|
2182
|
+
if (next) show(next, animate);
|
|
2183
|
+
};
|
|
2184
|
+
const offs = [
|
|
2185
|
+
deps.on("snap", () => update(true)),
|
|
2186
|
+
deps.on("open", () => update(true)),
|
|
2187
|
+
deps.on("close", () => update(true))
|
|
2188
|
+
];
|
|
2189
|
+
if (entries.some((e) => e.def.forRange)) {
|
|
2190
|
+
offs.push(deps.on("progress", () => update(true)));
|
|
2191
|
+
}
|
|
2192
|
+
update(false);
|
|
2193
|
+
return () => {
|
|
2194
|
+
offs.forEach((off) => off());
|
|
2195
|
+
for (const entry of entries) {
|
|
2196
|
+
entry.inFlight?.cancel();
|
|
2197
|
+
entry.inFlight = null;
|
|
2198
|
+
if (entry.wrapper.parentNode) {
|
|
2199
|
+
entry.wrapper.parentNode.removeChild(entry.wrapper);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
active = null;
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2052
2206
|
// src/core/primitives/engine-options.ts
|
|
2053
2207
|
var DEFAULT_FLICK_VELOCITY = 0.65;
|
|
2054
2208
|
var DEFAULT_DRAG_THRESHOLD = 18;
|
|
@@ -2073,9 +2227,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2073
2227
|
scrimMode: opts.scrimMode,
|
|
2074
2228
|
scrimColor: opts.scrimColor,
|
|
2075
2229
|
scrimBlur: opts.scrimBlur,
|
|
2076
|
-
// Auto-promote pointer-events when tap-to-close is on so the click
|
|
2077
|
-
// listener actually receives events. Caller can still force `false`
|
|
2078
|
-
// explicitly to opt out — `??` only kicks in for `undefined`.
|
|
2079
2230
|
scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
|
|
2080
2231
|
scrimTapToClose: opts.scrimTapToClose,
|
|
2081
2232
|
scrimPreset: opts.scrimPreset,
|
|
@@ -2096,10 +2247,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2096
2247
|
closeOnEscape: opts.closeOnEscape,
|
|
2097
2248
|
lockBodyScroll: opts.lockBodyScroll,
|
|
2098
2249
|
inertSiblings: opts.inertSiblings,
|
|
2099
|
-
// Folded in from `extras` so the lifecycle bag is fully assembled at
|
|
2100
|
-
// the call site — engine no longer needs a spread+override at
|
|
2101
|
-
// controller construction. When `extras` is omitted, the field stays
|
|
2102
|
-
// undefined and the controller uses its own default predicate.
|
|
2103
2250
|
shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
|
|
2104
2251
|
}
|
|
2105
2252
|
};
|
|
@@ -2134,6 +2281,7 @@ var BottomSheetEngine = class {
|
|
|
2134
2281
|
__publicField(this, "scrollContainer");
|
|
2135
2282
|
__publicField(this, "backdrop");
|
|
2136
2283
|
__publicField(this, "screenComponent");
|
|
2284
|
+
__publicField(this, "scrimParent", null);
|
|
2137
2285
|
__publicField(this, "mode");
|
|
2138
2286
|
__publicField(this, "flickVelocity");
|
|
2139
2287
|
__publicField(this, "dragThreshold");
|
|
@@ -2144,40 +2292,22 @@ var BottomSheetEngine = class {
|
|
|
2144
2292
|
__publicField(this, "lifecycle");
|
|
2145
2293
|
__publicField(this, "closeOnBack");
|
|
2146
2294
|
__publicField(this, "routedTo");
|
|
2147
|
-
// Engine retains `snapPointsRaw` for the "find a closed-id" query in
|
|
2148
|
-
// close() (raw input, not resolved geometry). Resolver owns geometry.
|
|
2149
2295
|
__publicField(this, "snapPointsRaw");
|
|
2150
2296
|
__publicField(this, "snaps");
|
|
2151
2297
|
__publicField(this, "activeId");
|
|
2152
2298
|
__publicField(this, "size", 0);
|
|
2153
|
-
// Constructed in attach(); a plugin's install() could read `state` before
|
|
2154
|
-
// attach completes if the constructor throws partway through. State getter
|
|
2155
|
-
// null-guards via `this.gesture?.isDragging ?? false`.
|
|
2156
2299
|
__publicField(this, "gesture");
|
|
2157
2300
|
__publicField(this, "rootEl", null);
|
|
2158
2301
|
__publicField(this, "destroyed", false);
|
|
2159
|
-
// In-flight ViewTransition handle. A second snapTo while a VT is in flight
|
|
2160
|
-
// would otherwise let Chromium reject the second's promise and flicker —
|
|
2161
|
-
// newCycle() calls skipTransition() to abort cleanly.
|
|
2162
2302
|
__publicField(this, "currentViewTransition", null);
|
|
2163
|
-
// Each cycle captures the signal at entry; when a newer cycle aborts the
|
|
2164
|
-
// controller, the captured signal flips to `aborted=true` and post-await
|
|
2165
|
-
// guards short-circuit (prevents stale snap/open/close emits).
|
|
2166
2303
|
__publicField(this, "currentAbort", new AbortController());
|
|
2167
|
-
// Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
|
|
2168
|
-
// visually distinguishable; gating CSSOM writes drops 30-50% of writes
|
|
2169
|
-
// during the spring's settle tail.
|
|
2170
2304
|
__publicField(this, "sizeWriteSentinel", new WriteSentinel());
|
|
2171
2305
|
__publicField(this, "progressWriteSentinel", new WriteSentinel());
|
|
2172
2306
|
__publicField(this, "isTopSheet", true);
|
|
2173
|
-
// Pooled payload — identity documented in SheetEventMap.progress JSDoc;
|
|
2174
|
-
// consumers must not retain across frames.
|
|
2175
2307
|
__publicField(this, "progressPayload", {
|
|
2176
2308
|
value: 0,
|
|
2177
2309
|
size: 0
|
|
2178
2310
|
});
|
|
2179
|
-
// Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
|
|
2180
|
-
// Inner `range` object identity is also stable (mutated in place).
|
|
2181
2311
|
__publicField(this, "dragContextBuf", {
|
|
2182
2312
|
size: 0,
|
|
2183
2313
|
maxAxisSize: 0,
|
|
@@ -2188,13 +2318,13 @@ var BottomSheetEngine = class {
|
|
|
2188
2318
|
__publicField(this, "persistKey");
|
|
2189
2319
|
__publicField(this, "linkedSheets", []);
|
|
2190
2320
|
__publicField(this, "scrollCache");
|
|
2191
|
-
// Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
|
|
2192
|
-
// tear down first before the infrastructure they depend on. Toggle-semantic
|
|
2193
|
-
// listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
|
|
2194
|
-
// fields on their controllers.
|
|
2195
2321
|
__publicField(this, "teardowns", new TeardownStack());
|
|
2196
|
-
|
|
2197
|
-
|
|
2322
|
+
__publicField(this, "anchors", []);
|
|
2323
|
+
__publicField(this, "anchorHost", null);
|
|
2324
|
+
__publicField(this, "stackZ", 100);
|
|
2325
|
+
__publicField(this, "detachScrimStages", null);
|
|
2326
|
+
__publicField(this, "stackEffectEnabled", false);
|
|
2327
|
+
__publicField(this, "stackEffectPrimed", false);
|
|
2198
2328
|
__publicField(this, "transformTemplate");
|
|
2199
2329
|
const resolved = resolveEngineOptions(opts, {
|
|
2200
2330
|
shouldApplyInertSiblings: () => {
|
|
@@ -2209,7 +2339,8 @@ var BottomSheetEngine = class {
|
|
|
2209
2339
|
this.handle = opts.handle ?? opts.element;
|
|
2210
2340
|
this.scrollContainer = opts.scrollContainer;
|
|
2211
2341
|
this.backdrop = opts.backdrop;
|
|
2212
|
-
this.screenComponent = opts.screenComponent;
|
|
2342
|
+
this.screenComponent = opts.scrim ?? opts.screenComponent;
|
|
2343
|
+
this.scrimParent = this.screenComponent?.parentElement ?? null;
|
|
2213
2344
|
this.mode = resolved.mode;
|
|
2214
2345
|
this.transformTemplate = buildTransformTemplate(this.mode);
|
|
2215
2346
|
this.aria = new AriaSliderWriter(this.handle, this.mode);
|
|
@@ -2227,6 +2358,9 @@ var BottomSheetEngine = class {
|
|
|
2227
2358
|
getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
|
|
2228
2359
|
snapTo: (id) => {
|
|
2229
2360
|
void this.snapTo(id);
|
|
2361
|
+
},
|
|
2362
|
+
close: () => {
|
|
2363
|
+
void this.close();
|
|
2230
2364
|
}
|
|
2231
2365
|
},
|
|
2232
2366
|
resolved.scrim
|
|
@@ -2237,8 +2371,6 @@ var BottomSheetEngine = class {
|
|
|
2237
2371
|
getRootEl: () => this.rootEl,
|
|
2238
2372
|
applySize: (size) => this.applySize(size),
|
|
2239
2373
|
getSize: () => this.size,
|
|
2240
|
-
// Optional chain protects synthetic test paths that drive animateTo
|
|
2241
|
-
// before attach() (gesture is constructed there).
|
|
2242
2374
|
isDragging: () => this.gesture?.isDragging ?? false
|
|
2243
2375
|
},
|
|
2244
2376
|
resolved.animation
|
|
@@ -2252,6 +2384,7 @@ var BottomSheetEngine = class {
|
|
|
2252
2384
|
);
|
|
2253
2385
|
this.closeOnBack = resolved.closeOnBack;
|
|
2254
2386
|
this.routedTo = opts.routedTo;
|
|
2387
|
+
this.stackEffectEnabled = opts.stackEffect ?? false;
|
|
2255
2388
|
this.snapPointsRaw = opts.snapPoints;
|
|
2256
2389
|
this.persistKey = opts.persistKey;
|
|
2257
2390
|
if (opts.linkedSheets) {
|
|
@@ -2264,7 +2397,7 @@ var BottomSheetEngine = class {
|
|
|
2264
2397
|
this.snapPointsRaw,
|
|
2265
2398
|
resolved.initialAllowed,
|
|
2266
2399
|
this.mode,
|
|
2267
|
-
() => this.handle.offsetHeight,
|
|
2400
|
+
() => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
|
|
2268
2401
|
(maxAxisSize) => {
|
|
2269
2402
|
this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
|
|
2270
2403
|
}
|
|
@@ -2343,21 +2476,6 @@ var BottomSheetEngine = class {
|
|
|
2343
2476
|
on(event, fn) {
|
|
2344
2477
|
return this.bus.on(event, fn);
|
|
2345
2478
|
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Register a plugin. `install` runs synchronously; if it returns a function,
|
|
2348
|
-
* it's captured for teardown during `destroy()`. No-op if already destroyed
|
|
2349
|
-
* — installing into a torn-down engine would leak listeners.
|
|
2350
|
-
*
|
|
2351
|
-
* Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
|
|
2352
|
-
* so a buggy plugin can't take the engine down with it: the engine stays
|
|
2353
|
-
* usable, sibling plugins still install, and the error surfaces in dev tools
|
|
2354
|
-
* on the next tick instead of being swallowed. Mirrors the per-fn isolation
|
|
2355
|
-
* model used for teardowns.
|
|
2356
|
-
*
|
|
2357
|
-
* Plugins that accept the second `scope` argument get transactional cleanup:
|
|
2358
|
-
* push partial-install cleanups via `scope.add(fn)` and the engine drains
|
|
2359
|
-
* them on throw, OR merges them into destroy-time teardowns on success.
|
|
2360
|
-
*/
|
|
2361
2479
|
use(plugin) {
|
|
2362
2480
|
if (this.destroyed) {
|
|
2363
2481
|
console.warn(
|
|
@@ -2367,9 +2485,6 @@ var BottomSheetEngine = class {
|
|
|
2367
2485
|
}
|
|
2368
2486
|
const scoped = [];
|
|
2369
2487
|
const scope = {
|
|
2370
|
-
// typeof check — not just truthiness — so a JS-only plugin pushing a
|
|
2371
|
-
// bogus value is silently rejected instead of queueing a confusing
|
|
2372
|
-
// TypeError on destroy() drain.
|
|
2373
2488
|
add: (fn) => {
|
|
2374
2489
|
if (typeof fn === "function") scoped.push(fn);
|
|
2375
2490
|
}
|
|
@@ -2398,36 +2513,13 @@ var BottomSheetEngine = class {
|
|
|
2398
2513
|
}
|
|
2399
2514
|
return this;
|
|
2400
2515
|
}
|
|
2401
|
-
/**
|
|
2402
|
-
* Replace the linked-sheets list at runtime. Use when peer engines aren't
|
|
2403
|
-
* available at construction time (mounted later in the tree). The wiring
|
|
2404
|
-
* to the `open` event is independent of this list — it reads `linkedSheets`
|
|
2405
|
-
* lazily on emit, so this setter is a simple field swap.
|
|
2406
|
-
*/
|
|
2407
2516
|
setLinkedSheets(sheets) {
|
|
2408
2517
|
if (this.destroyed) return;
|
|
2409
2518
|
this.linkedSheets = sheets;
|
|
2410
2519
|
}
|
|
2411
|
-
/** Read-only view of the current allow-list, for cross-sheet target negotiation. */
|
|
2412
2520
|
getAllowedIds() {
|
|
2413
2521
|
return this.snaps.getAllowedIds().slice();
|
|
2414
2522
|
}
|
|
2415
|
-
/**
|
|
2416
|
-
* Animate to a snap point by id. Resolves when the animation completes.
|
|
2417
|
-
*
|
|
2418
|
-
* Two call shapes:
|
|
2419
|
-
* ```ts
|
|
2420
|
-
* engine.snapTo("full"); // default velocity
|
|
2421
|
-
* engine.snapTo("full", 0.5); // px/ms initial velocity
|
|
2422
|
-
* engine.snapTo("full", { velocity: 0.5, signal }); // abortable
|
|
2423
|
-
* ```
|
|
2424
|
-
*
|
|
2425
|
-
* When `opts.signal` is provided, calling `controller.abort()` cancels the
|
|
2426
|
-
* in-flight tween/spring and the returned promise resolves without firing
|
|
2427
|
-
* `snap`. The captured `AbortSignal` is checked after each await so external
|
|
2428
|
-
* aborts ride the same cycle invariant as internal ones (resize, destroy,
|
|
2429
|
-
* concurrent snapTo).
|
|
2430
|
-
*/
|
|
2431
2523
|
async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
|
|
2432
2524
|
if (this.destroyed) return;
|
|
2433
2525
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2486,16 +2578,6 @@ var BottomSheetEngine = class {
|
|
|
2486
2578
|
this.handleClose();
|
|
2487
2579
|
}
|
|
2488
2580
|
}
|
|
2489
|
-
/**
|
|
2490
|
-
* Drive the sheet to an arbitrary px size, bypassing snap matching.
|
|
2491
|
-
* Useful for preview scrubs, animation chaining, or programmatic gestures.
|
|
2492
|
-
* Animates with the configured spring/tween. Resolves when the animation
|
|
2493
|
-
* completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
|
|
2494
|
-
*
|
|
2495
|
-
* Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
|
|
2496
|
-
* nor `snap` (the sheet isn't settling to a known snap). `progress` events
|
|
2497
|
-
* still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
|
|
2498
|
-
*/
|
|
2499
2581
|
async dragTo(targetSize, velocityOrOpts = 0) {
|
|
2500
2582
|
if (this.destroyed) return;
|
|
2501
2583
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2525,28 +2607,13 @@ var BottomSheetEngine = class {
|
|
|
2525
2607
|
}
|
|
2526
2608
|
setAllowed(ids, snap) {
|
|
2527
2609
|
if (this.destroyed) return;
|
|
2528
|
-
const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
|
|
2529
|
-
if (!willSnap) {
|
|
2530
|
-
this.animation.cancel();
|
|
2531
|
-
this.newCycle();
|
|
2532
|
-
}
|
|
2533
2610
|
this.snaps.setAllowedIds(ids);
|
|
2534
2611
|
this.updateAriaSlider();
|
|
2535
2612
|
if (snap && ids.includes(snap)) {
|
|
2536
|
-
void this.snapTo(
|
|
2537
|
-
snap,
|
|
2538
|
-
0,
|
|
2539
|
-
/* internal */
|
|
2540
|
-
true
|
|
2541
|
-
);
|
|
2613
|
+
void this.snapTo(snap, 0, true);
|
|
2542
2614
|
} else if (!ids.includes(this.activeId)) {
|
|
2543
2615
|
const fallback = ids[0];
|
|
2544
|
-
if (fallback) void this.snapTo(
|
|
2545
|
-
fallback,
|
|
2546
|
-
0,
|
|
2547
|
-
/* internal */
|
|
2548
|
-
true
|
|
2549
|
-
);
|
|
2616
|
+
if (fallback) void this.snapTo(fallback, 0, true);
|
|
2550
2617
|
}
|
|
2551
2618
|
}
|
|
2552
2619
|
setBackdropRange(range) {
|
|
@@ -2564,21 +2631,6 @@ var BottomSheetEngine = class {
|
|
|
2564
2631
|
setScrimInteractive(interactive) {
|
|
2565
2632
|
this.scrim.setScrimInteractive(interactive);
|
|
2566
2633
|
}
|
|
2567
|
-
/**
|
|
2568
|
-
* Batch-update multiple scrim fields in one call. Apply order is:
|
|
2569
|
-
* 1. `preset` — fills baseline color/blur/range/interactive.
|
|
2570
|
-
* 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
|
|
2571
|
-
* 3. `enabled` — flips the opacity contribution on/off.
|
|
2572
|
-
* 4. `tapToClose` — installs/tears the click listener.
|
|
2573
|
-
* 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
|
|
2574
|
-
* 6. ONE `applySize` so the per-frame opacity stays consistent.
|
|
2575
|
-
*
|
|
2576
|
-
* Performance: collecting changes here (vs calling individual setters)
|
|
2577
|
-
* avoids repeated `applySize` calls during a multi-field swap. Setters
|
|
2578
|
-
* called here suppress their own internal `applySize` either by passing
|
|
2579
|
-
* through fast-paths (mode/enabled no-op when value unchanged) or by
|
|
2580
|
-
* being visual-only (color/blur/interactive don't influence opacity).
|
|
2581
|
-
*/
|
|
2582
2634
|
setScrim(opts) {
|
|
2583
2635
|
this.scrim.setScrim(opts, () => this.applySize(this.size));
|
|
2584
2636
|
}
|
|
@@ -2594,11 +2646,79 @@ var BottomSheetEngine = class {
|
|
|
2594
2646
|
setScrimOverlay(opts) {
|
|
2595
2647
|
return this.scrim.setScrimOverlay(opts);
|
|
2596
2648
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2649
|
+
addAnchor(opts) {
|
|
2650
|
+
if (this.destroyed || typeof document === "undefined") return () => {
|
|
2651
|
+
};
|
|
2652
|
+
if (!this.anchorHost) {
|
|
2653
|
+
this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
|
|
2654
|
+
this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
|
|
2655
|
+
this.anchorHost.style.setProperty(
|
|
2656
|
+
"--bs-progress",
|
|
2657
|
+
String(this.computeProgress(this.size))
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
const handle = attachAnchor(
|
|
2661
|
+
{
|
|
2662
|
+
mode: this.mode,
|
|
2663
|
+
host: this.anchorHost,
|
|
2664
|
+
getState: () => ({
|
|
2665
|
+
activeId: this.activeId,
|
|
2666
|
+
size: this.size,
|
|
2667
|
+
progress: this.computeProgress(this.size)
|
|
2668
|
+
}),
|
|
2669
|
+
on: (event, fn) => this.on(event, fn),
|
|
2670
|
+
isDestroyed: () => this.destroyed
|
|
2671
|
+
},
|
|
2672
|
+
opts
|
|
2673
|
+
);
|
|
2674
|
+
handle.syncZ(this.stackZ + 1);
|
|
2675
|
+
this.anchors.push(handle);
|
|
2676
|
+
const detach = () => {
|
|
2677
|
+
const idx = this.anchors.indexOf(handle);
|
|
2678
|
+
if (idx === -1) return;
|
|
2679
|
+
this.anchors.splice(idx, 1);
|
|
2680
|
+
handle.detach();
|
|
2681
|
+
};
|
|
2682
|
+
this.teardowns.add(detach);
|
|
2683
|
+
return detach;
|
|
2684
|
+
}
|
|
2685
|
+
setScrimStages(opts) {
|
|
2686
|
+
if (this.destroyed || typeof document === "undefined") return () => {
|
|
2687
|
+
};
|
|
2688
|
+
this.detachScrimStages?.();
|
|
2689
|
+
this.detachScrimStages = null;
|
|
2690
|
+
if (!opts) return () => {
|
|
2691
|
+
};
|
|
2692
|
+
const host = this.screenComponent?.parentElement;
|
|
2693
|
+
if (!host) {
|
|
2694
|
+
console.warn(
|
|
2695
|
+
"[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
|
|
2696
|
+
);
|
|
2697
|
+
return () => {
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
const detachInstalled = installScrimStages(
|
|
2701
|
+
{
|
|
2702
|
+
mode: this.mode,
|
|
2703
|
+
host,
|
|
2704
|
+
getState: () => ({
|
|
2705
|
+
activeId: this.activeId,
|
|
2706
|
+
size: this.size,
|
|
2707
|
+
progress: this.computeProgress(this.size)
|
|
2708
|
+
}),
|
|
2709
|
+
on: (event, fn) => this.on(event, fn),
|
|
2710
|
+
isDestroyed: () => this.destroyed
|
|
2711
|
+
},
|
|
2712
|
+
opts
|
|
2713
|
+
);
|
|
2714
|
+
const detach = () => {
|
|
2715
|
+
if (this.detachScrimStages === detach) this.detachScrimStages = null;
|
|
2716
|
+
detachInstalled();
|
|
2717
|
+
};
|
|
2718
|
+
this.detachScrimStages = detach;
|
|
2719
|
+
this.teardowns.add(() => this.detachScrimStages?.());
|
|
2720
|
+
return detach;
|
|
2721
|
+
}
|
|
2602
2722
|
getScrimState() {
|
|
2603
2723
|
return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
|
|
2604
2724
|
}
|
|
@@ -2644,24 +2764,11 @@ var BottomSheetEngine = class {
|
|
|
2644
2764
|
emit(event, payload) {
|
|
2645
2765
|
this.bus.emit(event, payload);
|
|
2646
2766
|
}
|
|
2647
|
-
/**
|
|
2648
|
-
* Abort the current cycle's signal and replace the controller. Returns the
|
|
2649
|
-
* fresh signal so the caller can capture it locally before any await — when
|
|
2650
|
-
* a future newCycle() / destroy aborts our controller, the captured signal
|
|
2651
|
-
* flips to aborted and post-await guards short-circuit.
|
|
2652
|
-
*/
|
|
2653
2767
|
newCycle() {
|
|
2654
2768
|
this.currentAbort.abort();
|
|
2655
2769
|
this.currentAbort = new AbortController();
|
|
2656
2770
|
return this.currentAbort.signal;
|
|
2657
2771
|
}
|
|
2658
|
-
/**
|
|
2659
|
-
* Fire `before-snap` and report whether a listener cancelled it. The
|
|
2660
|
-
* `cancel()` callback exposed to listeners is frozen after the synchronous
|
|
2661
|
-
* emit phase so async callers can't silently miss — they get a console
|
|
2662
|
-
* warning. Returns `true` when cancelled so callers can early-return /
|
|
2663
|
-
* spring-back without re-implementing the cancel/frozen pattern.
|
|
2664
|
-
*/
|
|
2665
2772
|
emitBeforeSnap(target, previousId) {
|
|
2666
2773
|
let cancelled = false;
|
|
2667
2774
|
let frozen = false;
|
|
@@ -2695,59 +2802,54 @@ var BottomSheetEngine = class {
|
|
|
2695
2802
|
this.snaps.setRaw(this.snapPointsRaw);
|
|
2696
2803
|
this.scrim.invalidateOpacityCache();
|
|
2697
2804
|
}
|
|
2698
|
-
/**
|
|
2699
|
-
* Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
|
|
2700
|
-
* Engine wrapper kept so existing call sites don't all need updating;
|
|
2701
|
-
* resolver-level memoization survives across method invocations.
|
|
2702
|
-
*/
|
|
2703
2805
|
getAllowedRange() {
|
|
2704
2806
|
return this.snaps.getAllowedRange();
|
|
2705
2807
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
* without it screen readers can't announce drag/snap progress to keyboard
|
|
2712
|
-
* + AT users (WCAG 4.1.2 Name, Role, Value).
|
|
2713
|
-
*
|
|
2714
|
-
* Engine does NOT set `role="slider"` itself — that's the adapter's or
|
|
2715
|
-
* consumer markup's responsibility. We only decorate with dynamic values.
|
|
2716
|
-
*
|
|
2717
|
-
* Empty allowed-list is malformed input (settable via `setAllowed([])`);
|
|
2718
|
-
* short-circuit to remove ARIA value attributes so screen readers see a
|
|
2719
|
-
* no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
|
|
2720
|
-
* requires `valuemax > valuemin` for `role="slider"`).
|
|
2721
|
-
*/
|
|
2808
|
+
allowedIdsBySize() {
|
|
2809
|
+
return this.snaps.getAllowedIds().slice().sort(
|
|
2810
|
+
(a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2722
2813
|
updateAriaSlider() {
|
|
2723
|
-
this.aria.setValue(this.
|
|
2814
|
+
this.aria.setValue(this.allowedIdsBySize(), this.activeId);
|
|
2724
2815
|
}
|
|
2725
2816
|
registerInStack() {
|
|
2726
2817
|
this.teardowns.add(sheetStack.push({
|
|
2727
2818
|
id: this.id,
|
|
2728
2819
|
setZIndex: (z) => {
|
|
2820
|
+
this.stackZ = z;
|
|
2729
2821
|
this.element.style.zIndex = String(z);
|
|
2730
2822
|
if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
|
|
2823
|
+
for (const anchor of this.anchors) anchor.syncZ(z + 1);
|
|
2731
2824
|
},
|
|
2732
2825
|
setIsTop: (isTop) => {
|
|
2733
2826
|
this.isTopSheet = isTop;
|
|
2734
2827
|
if (this.backdrop) {
|
|
2735
2828
|
this.backdrop.style.display = isTop ? "" : "none";
|
|
2736
2829
|
}
|
|
2737
|
-
}
|
|
2830
|
+
},
|
|
2831
|
+
isOpen: () => this.size > 0,
|
|
2832
|
+
setDepth: (depth) => this.applyStackDepth(depth)
|
|
2738
2833
|
}));
|
|
2739
2834
|
}
|
|
2835
|
+
applyStackDepth(depth) {
|
|
2836
|
+
if (!this.stackEffectEnabled) return;
|
|
2837
|
+
this.element.setAttribute("data-stack-depth", String(depth));
|
|
2838
|
+
if (!this.stackEffectPrimed) {
|
|
2839
|
+
this.stackEffectPrimed = true;
|
|
2840
|
+
const isVertical = this.mode === "bottom" || this.mode === "top";
|
|
2841
|
+
this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
|
|
2842
|
+
this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
|
|
2843
|
+
}
|
|
2844
|
+
const scale = Math.max(1 - depth * 0.04, 0.86);
|
|
2845
|
+
this.element.style.scale = scale === 1 ? "" : String(scale);
|
|
2846
|
+
}
|
|
2740
2847
|
attach() {
|
|
2741
2848
|
this.gesture = new GestureController({
|
|
2742
2849
|
handle: this.handle,
|
|
2743
2850
|
element: this.element,
|
|
2744
2851
|
mode: this.mode,
|
|
2745
2852
|
getRoot: () => this.rootEl,
|
|
2746
|
-
// Single coalesced reader — narrows the DI surface and removes 3 getter
|
|
2747
|
-
// dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
|
|
2748
|
-
// (this.dragContextBuf) — see field JSDoc for the retention contract.
|
|
2749
|
-
// Inner `range` object is mutated in place too so callers caching its
|
|
2750
|
-
// identity between frames stay valid.
|
|
2751
2853
|
getDragContext: () => {
|
|
2752
2854
|
const buf = this.dragContextBuf;
|
|
2753
2855
|
const r = this.getAllowedRange();
|
|
@@ -2801,7 +2903,7 @@ var BottomSheetEngine = class {
|
|
|
2801
2903
|
container: this.scrollContainer,
|
|
2802
2904
|
isDragging: () => this.gesture?.isDragging ?? false,
|
|
2803
2905
|
isAnimating: () => this.animation.isAnimating,
|
|
2804
|
-
getAllowedIds: () => this.
|
|
2906
|
+
getAllowedIds: () => this.allowedIdsBySize(),
|
|
2805
2907
|
getActiveId: () => this.activeId,
|
|
2806
2908
|
snapTo: (id) => {
|
|
2807
2909
|
void this.snapTo(id);
|
|
@@ -2830,11 +2932,13 @@ var BottomSheetEngine = class {
|
|
|
2830
2932
|
},
|
|
2831
2933
|
newCycle: () => {
|
|
2832
2934
|
this.newCycle();
|
|
2833
|
-
}
|
|
2935
|
+
},
|
|
2936
|
+
isAnimating: () => this.animation.isAnimating,
|
|
2937
|
+
resyncAfterCancel: () => this.resyncAfterResize()
|
|
2834
2938
|
}));
|
|
2835
2939
|
if (this.lifecycle.closeOnEscape) {
|
|
2836
2940
|
const onKey = (e) => {
|
|
2837
|
-
if (this.destroyed) return;
|
|
2941
|
+
if (this.destroyed || e.defaultPrevented) return;
|
|
2838
2942
|
if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
|
|
2839
2943
|
void this.close();
|
|
2840
2944
|
}
|
|
@@ -2866,7 +2970,7 @@ var BottomSheetEngine = class {
|
|
|
2866
2970
|
handle: this.handle,
|
|
2867
2971
|
mode: this.mode,
|
|
2868
2972
|
isDestroyed: () => this.destroyed,
|
|
2869
|
-
getAllowedIds: () => this.
|
|
2973
|
+
getAllowedIds: () => this.allowedIdsBySize(),
|
|
2870
2974
|
getActiveId: () => this.activeId,
|
|
2871
2975
|
snapTo: (id) => {
|
|
2872
2976
|
void this.snapTo(id);
|
|
@@ -2887,6 +2991,7 @@ var BottomSheetEngine = class {
|
|
|
2887
2991
|
});
|
|
2888
2992
|
if (!target) return;
|
|
2889
2993
|
const previousId = this.activeId;
|
|
2994
|
+
if (target.id === previousId && target.size === this.size) return;
|
|
2890
2995
|
if (this.emitBeforeSnap(target, previousId)) {
|
|
2891
2996
|
const restore = this.snaps.findById(previousId);
|
|
2892
2997
|
if (restore) {
|
|
@@ -2895,6 +3000,7 @@ var BottomSheetEngine = class {
|
|
|
2895
3000
|
return;
|
|
2896
3001
|
}
|
|
2897
3002
|
const previousSize = this.size;
|
|
3003
|
+
const previousSnapSize = this.snaps.findById(previousId)?.size;
|
|
2898
3004
|
this.scrollCache.cache(previousId, previousSize, target.size);
|
|
2899
3005
|
this.activeId = target.id;
|
|
2900
3006
|
void this.animation.animateTo(target.size, velocity).then(() => {
|
|
@@ -2903,9 +3009,10 @@ var BottomSheetEngine = class {
|
|
|
2903
3009
|
this.updateAriaSlider();
|
|
2904
3010
|
this.emit("snap", { id: target.id, size: target.size });
|
|
2905
3011
|
this.haptic();
|
|
2906
|
-
if (
|
|
3012
|
+
if (previousSnapSize === 0 && target.size > 0) {
|
|
2907
3013
|
this.emit("open", { id: target.id });
|
|
2908
3014
|
this.handleOpen();
|
|
3015
|
+
notifyLinkedSheets(this.linkedSheets, this);
|
|
2909
3016
|
}
|
|
2910
3017
|
if (target.size === 0) {
|
|
2911
3018
|
this.emit("close", void 0);
|
|
@@ -2913,7 +3020,6 @@ var BottomSheetEngine = class {
|
|
|
2913
3020
|
}
|
|
2914
3021
|
});
|
|
2915
3022
|
}
|
|
2916
|
-
/** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
|
|
2917
3023
|
haptic() {
|
|
2918
3024
|
if (typeof navigator !== "undefined" && "vibrate" in navigator) {
|
|
2919
3025
|
try {
|
|
@@ -2929,6 +3035,12 @@ var BottomSheetEngine = class {
|
|
|
2929
3035
|
style.transform = this.transformTemplate(offset);
|
|
2930
3036
|
if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
|
|
2931
3037
|
style.setProperty("--bs-size", `${size}px`);
|
|
3038
|
+
if (this.scrimParent) {
|
|
3039
|
+
this.scrimParent.style.setProperty("--bs-size", `${size}px`);
|
|
3040
|
+
}
|
|
3041
|
+
if (this.anchorHost && this.anchorHost !== this.scrimParent) {
|
|
3042
|
+
this.anchorHost.style.setProperty("--bs-size", `${size}px`);
|
|
3043
|
+
}
|
|
2932
3044
|
}
|
|
2933
3045
|
const progress = this.computeProgress(size);
|
|
2934
3046
|
const progressChanged = this.progressWriteSentinel.shouldWrite(
|
|
@@ -2937,6 +3049,9 @@ var BottomSheetEngine = class {
|
|
|
2937
3049
|
);
|
|
2938
3050
|
if (progressChanged) {
|
|
2939
3051
|
style.setProperty("--bs-progress", String(progress));
|
|
3052
|
+
if (this.anchorHost) {
|
|
3053
|
+
this.anchorHost.style.setProperty("--bs-progress", String(progress));
|
|
3054
|
+
}
|
|
2940
3055
|
}
|
|
2941
3056
|
this.scrim.applyOpacity(progress, progressChanged);
|
|
2942
3057
|
if (progressChanged && this.bus.listenerCount("progress") > 0) {
|
|
@@ -2951,6 +3066,7 @@ var BottomSheetEngine = class {
|
|
|
2951
3066
|
return Math.min(Math.max((size - min) / (max - min), 0), 1);
|
|
2952
3067
|
}
|
|
2953
3068
|
handleOpen() {
|
|
3069
|
+
sheetStack.promote(this.id);
|
|
2954
3070
|
try {
|
|
2955
3071
|
this.lifecycle.install();
|
|
2956
3072
|
} catch (err) {
|
|
@@ -2964,8 +3080,24 @@ var BottomSheetEngine = class {
|
|
|
2964
3080
|
});
|
|
2965
3081
|
}
|
|
2966
3082
|
}
|
|
3083
|
+
resyncAfterResize() {
|
|
3084
|
+
if (this.destroyed) return;
|
|
3085
|
+
const target = this.snaps.findById(this.activeId);
|
|
3086
|
+
if (!target) return;
|
|
3087
|
+
this.updateAriaSlider();
|
|
3088
|
+
this.emit("snap", { id: target.id, size: target.size });
|
|
3089
|
+
if (target.size > 0 && !this.lifecycle.isInstalled) {
|
|
3090
|
+
this.emit("open", { id: target.id });
|
|
3091
|
+
this.handleOpen();
|
|
3092
|
+
notifyLinkedSheets(this.linkedSheets, this);
|
|
3093
|
+
} else if (target.size === 0 && this.lifecycle.isInstalled) {
|
|
3094
|
+
this.emit("close", void 0);
|
|
3095
|
+
this.handleClose();
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
2967
3098
|
handleClose() {
|
|
2968
3099
|
this.lifecycle.release();
|
|
3100
|
+
sheetStack.update();
|
|
2969
3101
|
}
|
|
2970
3102
|
};
|
|
2971
3103
|
|
|
@@ -2983,17 +3115,18 @@ function createBottomSheet(opts) {
|
|
|
2983
3115
|
const attach = (refs) => {
|
|
2984
3116
|
if (engine) engine.destroy();
|
|
2985
3117
|
const { onSnap, ...engineOpts } = opts;
|
|
2986
|
-
|
|
3118
|
+
const created = new BottomSheetEngine({ ...engineOpts, ...refs });
|
|
3119
|
+
engine = created;
|
|
2987
3120
|
if (onSnap) {
|
|
2988
|
-
|
|
3121
|
+
created.on("snap", (payload) => onSnap(payload.id));
|
|
2989
3122
|
}
|
|
2990
3123
|
for (const entry of pending) {
|
|
2991
|
-
entry.engineUnsub =
|
|
3124
|
+
entry.engineUnsub = created.on(entry.event, entry.fn);
|
|
2992
3125
|
}
|
|
2993
3126
|
pending.length = 0;
|
|
2994
3127
|
return () => {
|
|
2995
|
-
|
|
2996
|
-
engine = null;
|
|
3128
|
+
created.destroy();
|
|
3129
|
+
if (engine === created) engine = null;
|
|
2997
3130
|
};
|
|
2998
3131
|
};
|
|
2999
3132
|
return {
|
|
@@ -3017,17 +3150,25 @@ function createBottomSheet(opts) {
|
|
|
3017
3150
|
open: (id) => engine?.open(id) ?? Promise.resolve(),
|
|
3018
3151
|
close: () => engine?.close() ?? Promise.resolve(),
|
|
3019
3152
|
setAllowed: (ids, snap) => engine?.setAllowed(ids, snap),
|
|
3153
|
+
setSnapPoints: (points, allowed) => engine?.setSnapPoints(points, allowed),
|
|
3154
|
+
setScrim: (scrimOpts) => engine?.setScrim(scrimOpts),
|
|
3155
|
+
setScrimOverlay: (overlayOpts) => engine?.setScrimOverlay(overlayOpts) ?? (() => {
|
|
3156
|
+
}),
|
|
3157
|
+
addAnchor: (anchorOpts) => engine?.addAnchor(anchorOpts) ?? (() => {
|
|
3158
|
+
}),
|
|
3159
|
+
setScrimStages: (stagesOpts) => engine?.setScrimStages(stagesOpts) ?? (() => {
|
|
3160
|
+
}),
|
|
3020
3161
|
destroy: () => {
|
|
3021
3162
|
engine?.destroy();
|
|
3022
3163
|
engine = null;
|
|
3023
|
-
}
|
|
3164
|
+
},
|
|
3165
|
+
getEngine: () => engine
|
|
3024
3166
|
};
|
|
3025
3167
|
}
|
|
3026
3168
|
var root_1 = $2__namespace.from_html(`<div class="bs-backdrop" aria-hidden="true"></div>`);
|
|
3027
|
-
var root_2 = $2__namespace.from_html(`<div class="bs-screen"><!></div>`);
|
|
3028
3169
|
var root_3 = $2__namespace.from_html(`<div class="bs-button-slot" data-side="left"><!></div>`);
|
|
3029
3170
|
var root_4 = $2__namespace.from_html(`<div class="bs-button-slot" data-side="right"><!></div>`);
|
|
3030
|
-
var root = $2__namespace.from_html(`<div class="bs-root"><!>
|
|
3171
|
+
var root = $2__namespace.from_html(`<div class="bs-root"><!> <div class="bs-screen"><!></div> <section class="bs-sheet" role="dialog"><!> <!> <div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet"><!></div> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section></div>`);
|
|
3031
3172
|
function BottomSheet($$anchor, $$props) {
|
|
3032
3173
|
$2__namespace.push($$props, true);
|
|
3033
3174
|
let mode = $2__namespace.prop($$props, "mode", 3, "bottom"), animation = $2__namespace.prop($$props, "animation", 3, "spring"), focusTrap = $2__namespace.prop($$props, "focusTrap", 3, false), closeOnEscape = $2__namespace.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2__namespace.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2__namespace.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2__namespace.prop($$props, "rubberBand", 3, true), backdrop = $2__namespace.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2__namespace.prop($$props, "closeOnBackdrop", 3, true), ariaLabel = $2__namespace.prop($$props, "ariaLabel", 3, "Bottom sheet");
|
|
@@ -3038,7 +3179,7 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3038
3179
|
let screenEl = $2__namespace.state(void 0);
|
|
3039
3180
|
let viewState = $2__namespace.state($2__namespace.proxy({
|
|
3040
3181
|
size: 0,
|
|
3041
|
-
activeId: $$props.initial ?? $$props.snapPoints[0]?.id ?? "default",
|
|
3182
|
+
activeId: svelte.untrack(() => $$props.initial ?? $$props.snapPoints[0]?.id ?? "default"),
|
|
3042
3183
|
isDragging: false,
|
|
3043
3184
|
isAnimating: false,
|
|
3044
3185
|
progress: 0
|
|
@@ -3051,7 +3192,7 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3051
3192
|
handle: $2__namespace.get(handleEl),
|
|
3052
3193
|
scrollContainer: $2__namespace.get(contentEl),
|
|
3053
3194
|
backdrop: $2__namespace.get(backdropEl),
|
|
3054
|
-
|
|
3195
|
+
scrim: $2__namespace.get(screenEl),
|
|
3055
3196
|
mode: mode(),
|
|
3056
3197
|
snapPoints: $$props.snapPoints,
|
|
3057
3198
|
allowed: $$props.allowed,
|
|
@@ -3088,8 +3229,26 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3088
3229
|
const open = (id) => engine?.open(id) ?? Promise.resolve();
|
|
3089
3230
|
const close = () => engine?.close() ?? Promise.resolve();
|
|
3090
3231
|
const setAllowed = (ids, snap) => engine?.setAllowed(ids, snap);
|
|
3232
|
+
const setSnapPoints = (points, nextAllowed) => engine?.setSnapPoints(points, nextAllowed);
|
|
3233
|
+
const setScrim = (opts) => engine?.setScrim(opts);
|
|
3234
|
+
const setScrimOverlay = (opts) => engine?.setScrimOverlay(opts) ?? (() => {
|
|
3235
|
+
});
|
|
3091
3236
|
const getState = () => engine?.state ?? $2__namespace.get(viewState);
|
|
3092
|
-
|
|
3237
|
+
const getEngine = () => engine;
|
|
3238
|
+
let allowedIds = $2__namespace.derived(() => $$props.allowed && $$props.allowed.length > 0 ? $$props.allowed : $$props.snapPoints.map((s) => s.id));
|
|
3239
|
+
let activeIdx = $2__namespace.derived(() => $2__namespace.get(allowedIds).indexOf($2__namespace.get(viewState).activeId));
|
|
3240
|
+
let isVerticalAxis = $2__namespace.derived(() => mode() === "bottom" || mode() === "top");
|
|
3241
|
+
var $$exports = {
|
|
3242
|
+
snapTo,
|
|
3243
|
+
open,
|
|
3244
|
+
close,
|
|
3245
|
+
setAllowed,
|
|
3246
|
+
setSnapPoints,
|
|
3247
|
+
setScrim,
|
|
3248
|
+
setScrimOverlay,
|
|
3249
|
+
getState,
|
|
3250
|
+
getEngine
|
|
3251
|
+
};
|
|
3093
3252
|
var div = root();
|
|
3094
3253
|
var node = $2__namespace.child(div);
|
|
3095
3254
|
{
|
|
@@ -3105,21 +3264,22 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3105
3264
|
if (backdrop()) $$render(consequent);
|
|
3106
3265
|
});
|
|
3107
3266
|
}
|
|
3108
|
-
var
|
|
3267
|
+
var div_2 = $2__namespace.sibling(node, 2);
|
|
3268
|
+
var node_1 = $2__namespace.child(div_2);
|
|
3109
3269
|
{
|
|
3110
3270
|
var consequent_1 = ($$anchor2) => {
|
|
3111
|
-
var
|
|
3112
|
-
var node_2 = $2__namespace.
|
|
3271
|
+
var fragment = $2__namespace.comment();
|
|
3272
|
+
var node_2 = $2__namespace.first_child(fragment);
|
|
3113
3273
|
$2__namespace.snippet(node_2, () => $$props.screen);
|
|
3114
|
-
$2__namespace.
|
|
3115
|
-
$2__namespace.bind_this(div_2, ($$value) => $2__namespace.set(screenEl, $$value), () => $2__namespace.get(screenEl));
|
|
3116
|
-
$2__namespace.append($$anchor2, div_2);
|
|
3274
|
+
$2__namespace.append($$anchor2, fragment);
|
|
3117
3275
|
};
|
|
3118
3276
|
$2__namespace.if(node_1, ($$render) => {
|
|
3119
3277
|
if ($$props.screen) $$render(consequent_1);
|
|
3120
3278
|
});
|
|
3121
3279
|
}
|
|
3122
|
-
|
|
3280
|
+
$2__namespace.reset(div_2);
|
|
3281
|
+
$2__namespace.bind_this(div_2, ($$value) => $2__namespace.set(screenEl, $$value), () => $2__namespace.get(screenEl));
|
|
3282
|
+
var section = $2__namespace.sibling(div_2, 2);
|
|
3123
3283
|
var node_3 = $2__namespace.child(section);
|
|
3124
3284
|
{
|
|
3125
3285
|
var consequent_2 = ($$anchor2) => {
|
|
@@ -3147,13 +3307,14 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3147
3307
|
});
|
|
3148
3308
|
}
|
|
3149
3309
|
var div_5 = $2__namespace.sibling(node_5, 2);
|
|
3310
|
+
$2__namespace.set_attribute(div_5, "aria-valuemin", 0);
|
|
3150
3311
|
var node_7 = $2__namespace.child(div_5);
|
|
3151
3312
|
{
|
|
3152
3313
|
var consequent_4 = ($$anchor2) => {
|
|
3153
|
-
var
|
|
3154
|
-
var node_8 = $2__namespace.first_child(
|
|
3314
|
+
var fragment_1 = $2__namespace.comment();
|
|
3315
|
+
var node_8 = $2__namespace.first_child(fragment_1);
|
|
3155
3316
|
$2__namespace.snippet(node_8, () => $$props.header, () => $2__namespace.get(viewState));
|
|
3156
|
-
$2__namespace.append($$anchor2,
|
|
3317
|
+
$2__namespace.append($$anchor2, fragment_1);
|
|
3157
3318
|
};
|
|
3158
3319
|
$2__namespace.if(node_7, ($$render) => {
|
|
3159
3320
|
if ($$props.header) $$render(consequent_4);
|
|
@@ -3165,10 +3326,10 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3165
3326
|
var node_9 = $2__namespace.child(div_6);
|
|
3166
3327
|
{
|
|
3167
3328
|
var consequent_5 = ($$anchor2) => {
|
|
3168
|
-
var
|
|
3169
|
-
var node_10 = $2__namespace.first_child(
|
|
3329
|
+
var fragment_2 = $2__namespace.comment();
|
|
3330
|
+
var node_10 = $2__namespace.first_child(fragment_2);
|
|
3170
3331
|
$2__namespace.snippet(node_10, () => $$props.children);
|
|
3171
|
-
$2__namespace.append($$anchor2,
|
|
3332
|
+
$2__namespace.append($$anchor2, fragment_2);
|
|
3172
3333
|
};
|
|
3173
3334
|
$2__namespace.if(node_9, ($$render) => {
|
|
3174
3335
|
if ($$props.children) $$render(consequent_5);
|
|
@@ -3182,13 +3343,23 @@ function BottomSheet($$anchor, $$props) {
|
|
|
3182
3343
|
$2__namespace.reset(section);
|
|
3183
3344
|
$2__namespace.bind_this(section, ($$value) => $2__namespace.set(sheetEl, $$value), () => $2__namespace.get(sheetEl));
|
|
3184
3345
|
$2__namespace.reset(div);
|
|
3185
|
-
$2__namespace.template_effect(
|
|
3186
|
-
$
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3346
|
+
$2__namespace.template_effect(
|
|
3347
|
+
($0, $1) => {
|
|
3348
|
+
$2__namespace.set_attribute(section, "data-mode", mode());
|
|
3349
|
+
$2__namespace.set_attribute(section, "data-active", $2__namespace.get(viewState).activeId);
|
|
3350
|
+
$2__namespace.set_attribute(section, "aria-modal", focusTrap() ? "true" : void 0);
|
|
3351
|
+
$2__namespace.set_attribute(section, "aria-label", ariaLabel());
|
|
3352
|
+
$2__namespace.set_attribute(div_5, "aria-orientation", $2__namespace.get(isVerticalAxis) ? "vertical" : "horizontal");
|
|
3353
|
+
$2__namespace.set_attribute(div_5, "aria-valuemax", $0);
|
|
3354
|
+
$2__namespace.set_attribute(div_5, "aria-valuenow", $1);
|
|
3355
|
+
$2__namespace.set_attribute(div_5, "aria-valuetext", $2__namespace.get(viewState).activeId);
|
|
3356
|
+
$2__namespace.set_text(text, $2__namespace.get(viewState).activeId);
|
|
3357
|
+
},
|
|
3358
|
+
[
|
|
3359
|
+
() => Math.max(0, $2__namespace.get(allowedIds).length - 1),
|
|
3360
|
+
() => Math.max(0, $2__namespace.get(activeIdx))
|
|
3361
|
+
]
|
|
3362
|
+
);
|
|
3192
3363
|
$2__namespace.append($$anchor, div);
|
|
3193
3364
|
return $2__namespace.pop($$exports);
|
|
3194
3365
|
}
|