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