@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/index.js
CHANGED
|
@@ -20,6 +20,18 @@ var SheetStack = class {
|
|
|
20
20
|
this.entries.splice(idx, 1);
|
|
21
21
|
this.recompute();
|
|
22
22
|
}
|
|
23
|
+
promote(id) {
|
|
24
|
+
const idx = this.entries.findIndex((e) => e.id === id);
|
|
25
|
+
if (idx === -1) return;
|
|
26
|
+
if (idx !== this.entries.length - 1) {
|
|
27
|
+
const [entry] = this.entries.splice(idx, 1);
|
|
28
|
+
this.entries.push(entry);
|
|
29
|
+
}
|
|
30
|
+
this.recompute();
|
|
31
|
+
}
|
|
32
|
+
update() {
|
|
33
|
+
this.recompute();
|
|
34
|
+
}
|
|
23
35
|
size() {
|
|
24
36
|
return this.entries.length;
|
|
25
37
|
}
|
|
@@ -27,9 +39,24 @@ var SheetStack = class {
|
|
|
27
39
|
this.entries = [];
|
|
28
40
|
}
|
|
29
41
|
recompute() {
|
|
42
|
+
let top = null;
|
|
43
|
+
const open = [];
|
|
44
|
+
for (const entry of this.entries) {
|
|
45
|
+
if (!entry.isOpen || entry.isOpen()) {
|
|
46
|
+
top = entry;
|
|
47
|
+
open.push(entry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!top && this.entries.length > 0) {
|
|
51
|
+
top = this.entries[this.entries.length - 1];
|
|
52
|
+
}
|
|
30
53
|
this.entries.forEach((entry, i) => {
|
|
31
54
|
entry.setZIndex(BASE_Z + i * STEP);
|
|
32
|
-
entry.setIsTop(
|
|
55
|
+
entry.setIsTop(entry === top);
|
|
56
|
+
if (entry.setDepth) {
|
|
57
|
+
const openIdx = open.indexOf(entry);
|
|
58
|
+
entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
|
|
59
|
+
}
|
|
33
60
|
});
|
|
34
61
|
}
|
|
35
62
|
};
|
|
@@ -234,11 +261,6 @@ var SnapResolver = class {
|
|
|
234
261
|
this.allowedIds = allowed ?? raw.map((p) => p.id);
|
|
235
262
|
this.recompute();
|
|
236
263
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Re-resolve raw → resolved against the current viewport / fit-measure,
|
|
239
|
-
* recompute maxAxisSize, invalidate range cache, notify engine. Called on
|
|
240
|
-
* resize, orientationchange, visualViewport changes, and snap-list swaps.
|
|
241
|
-
*/
|
|
242
264
|
recompute() {
|
|
243
265
|
this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
|
|
244
266
|
this.maxAxisSize = this.resolved.reduce(
|
|
@@ -248,15 +270,10 @@ var SnapResolver = class {
|
|
|
248
270
|
this.rangeCache = null;
|
|
249
271
|
this.onMaxAxisSizeChange?.(this.maxAxisSize);
|
|
250
272
|
}
|
|
251
|
-
/** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
|
|
252
273
|
setRaw(raw) {
|
|
253
274
|
this.raw = raw;
|
|
254
275
|
this.recompute();
|
|
255
276
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Replace the allow-list. Range cache depends on `(resolvedSnaps ×
|
|
258
|
-
* allowedIds)` so flipping ids alone invalidates without a full recompute.
|
|
259
|
-
*/
|
|
260
277
|
setAllowedIds(ids) {
|
|
261
278
|
this.allowedIds = ids;
|
|
262
279
|
this.rangeCache = null;
|
|
@@ -264,11 +281,6 @@ var SnapResolver = class {
|
|
|
264
281
|
findById(id) {
|
|
265
282
|
return findById(id, this.resolved);
|
|
266
283
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Pure drag-settle target resolution. Engine passes the gesture-specific
|
|
269
|
-
* fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
|
|
270
|
-
* `size`/`activeId` since those are still engine-owned.
|
|
271
|
-
*/
|
|
272
284
|
findDragSettleTarget(args) {
|
|
273
285
|
return findDragSettleTarget({
|
|
274
286
|
...args,
|
|
@@ -277,18 +289,9 @@ var SnapResolver = class {
|
|
|
277
289
|
maxAxisSize: this.maxAxisSize
|
|
278
290
|
});
|
|
279
291
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
|
|
282
|
-
* applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
|
|
283
|
-
* to one recompute per geometry/allow-list change.
|
|
284
|
-
*/
|
|
285
292
|
getAllowedRange() {
|
|
286
293
|
return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
|
|
287
294
|
}
|
|
288
|
-
/**
|
|
289
|
-
* Read-only view of resolved snaps. Returned as `readonly` to discourage
|
|
290
|
-
* accidental mutation by feature install sites that close over this list.
|
|
291
|
-
*/
|
|
292
295
|
getResolvedSnaps() {
|
|
293
296
|
return this.resolved;
|
|
294
297
|
}
|
|
@@ -298,18 +301,6 @@ var SnapResolver = class {
|
|
|
298
301
|
getMaxAxisSize() {
|
|
299
302
|
return this.maxAxisSize;
|
|
300
303
|
}
|
|
301
|
-
/**
|
|
302
|
-
* External clamp override. The resize-observer feature compares
|
|
303
|
-
* `getMaxAxisSize()` against the live viewport and shrinks if the largest
|
|
304
|
-
* resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
|
|
305
|
-
* window resize). The next `recompute()` overwrites this clamp from the
|
|
306
|
-
* resolved-snap reduction.
|
|
307
|
-
*
|
|
308
|
-
* Does NOT invalidate the range cache — viewport clamping doesn't change
|
|
309
|
-
* `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
|
|
310
|
-
* so the resolver owns the layout-axis style write end-to-end (symmetric
|
|
311
|
-
* with `recompute()` — feature call sites no longer duplicate the write).
|
|
312
|
-
*/
|
|
313
304
|
setMaxAxisSize(n) {
|
|
314
305
|
this.maxAxisSize = n;
|
|
315
306
|
this.onMaxAxisSizeChange?.(n);
|
|
@@ -356,14 +347,17 @@ function installAutoCollapse(deps) {
|
|
|
356
347
|
const fire = () => {
|
|
357
348
|
if (deps.isDestroyed()) return;
|
|
358
349
|
if (deps.isDragging()) return;
|
|
359
|
-
const
|
|
360
|
-
|
|
350
|
+
const active = deps.resolveSnap(deps.getActiveId());
|
|
351
|
+
if (!active || active.size === 0) return;
|
|
352
|
+
let target = null;
|
|
353
|
+
for (const id of deps.getAllowedIds()) {
|
|
361
354
|
const snap = deps.resolveSnap(id);
|
|
362
|
-
|
|
363
|
-
|
|
355
|
+
if (!snap || snap.size <= 0) continue;
|
|
356
|
+
if (!target || snap.size < target.size) target = snap;
|
|
357
|
+
}
|
|
364
358
|
if (!target) return;
|
|
365
|
-
if (target ===
|
|
366
|
-
deps.snapTo(target);
|
|
359
|
+
if (target.id === active.id || target.size >= active.size) return;
|
|
360
|
+
deps.snapTo(target.id);
|
|
367
361
|
};
|
|
368
362
|
const reset = () => {
|
|
369
363
|
clearTimer();
|
|
@@ -374,6 +368,7 @@ function installAutoCollapse(deps) {
|
|
|
374
368
|
}, ms);
|
|
375
369
|
};
|
|
376
370
|
const unsubscribe = deps.on("snap", () => reset());
|
|
371
|
+
if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
|
|
377
372
|
return () => {
|
|
378
373
|
clearTimer();
|
|
379
374
|
unsubscribe();
|
|
@@ -386,6 +381,7 @@ function notifyLinkedSheets(sheets, self) {
|
|
|
386
381
|
for (const linked of sheets) {
|
|
387
382
|
if (linked === self) continue;
|
|
388
383
|
try {
|
|
384
|
+
if (linked.state.size === 0) continue;
|
|
389
385
|
const ids = linked.getAllowedIds();
|
|
390
386
|
const target = ids.find((id) => id !== "closed");
|
|
391
387
|
if (target && target !== linked.state.activeId) {
|
|
@@ -432,7 +428,7 @@ function installRoute(deps) {
|
|
|
432
428
|
const onPop = (event) => {
|
|
433
429
|
if (deps.isDestroyed()) return;
|
|
434
430
|
const state = event.state;
|
|
435
|
-
if (state
|
|
431
|
+
if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
|
|
436
432
|
routePushed = false;
|
|
437
433
|
void deps.close();
|
|
438
434
|
}
|
|
@@ -586,13 +582,17 @@ function installResizeObserver(deps) {
|
|
|
586
582
|
if (viewportSize2 > 0 && viewportSize2 < max) {
|
|
587
583
|
deps.setMaxAxisSize(viewportSize2);
|
|
588
584
|
}
|
|
585
|
+
const wasAnimating = deps.isAnimating?.() ?? false;
|
|
589
586
|
deps.cancelInFlight();
|
|
590
587
|
deps.newCycle();
|
|
591
588
|
const current = deps.resolveActiveSnap();
|
|
592
589
|
const newMax = deps.getMaxAxisSize();
|
|
593
590
|
const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
|
|
594
591
|
deps.setSize(targetSize);
|
|
595
|
-
if (current && !deps.isDragging())
|
|
592
|
+
if (current && !deps.isDragging()) {
|
|
593
|
+
deps.applySize(targetSize);
|
|
594
|
+
if (wasAnimating) deps.resyncAfterCancel?.();
|
|
595
|
+
}
|
|
596
596
|
};
|
|
597
597
|
let resizeObserver = null;
|
|
598
598
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -684,73 +684,130 @@ var WriteSentinel = class {
|
|
|
684
684
|
constructor() {
|
|
685
685
|
__publicField(this, "last", -1);
|
|
686
686
|
}
|
|
687
|
-
/**
|
|
688
|
-
* Returns `true` (and updates the cached value) if `next` differs from the
|
|
689
|
-
* cached `last` by more than `epsilon`. Returns `false` (and leaves the
|
|
690
|
-
* cache untouched) when the delta is within epsilon — caller skips the
|
|
691
|
-
* CSSOM write.
|
|
692
|
-
*/
|
|
693
687
|
shouldWrite(next, epsilon) {
|
|
694
688
|
if (Math.abs(next - this.last) <= epsilon) return false;
|
|
695
689
|
this.last = next;
|
|
696
690
|
return true;
|
|
697
691
|
}
|
|
698
|
-
/**
|
|
699
|
-
* Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
|
|
700
|
-
* non-negative `next` exceeds the epsilon comparison. Called from setters
|
|
701
|
-
* that mutate state under the cache (range/mode/enabled flips that change
|
|
702
|
-
* the progress→opacity mapping but don't move `progress` itself).
|
|
703
|
-
*/
|
|
704
692
|
invalidate() {
|
|
705
693
|
this.last = -1;
|
|
706
694
|
}
|
|
707
|
-
/**
|
|
708
|
-
* Record `value` as the last-written without performing a write. Used
|
|
709
|
-
* when a setter writes a literal value through a different path (e.g.
|
|
710
|
-
* `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
|
|
711
|
-
* `shouldWrite` calls to short-circuit against that known cached state.
|
|
712
|
-
*/
|
|
713
695
|
setLastWritten(value) {
|
|
714
696
|
this.last = value;
|
|
715
697
|
}
|
|
716
|
-
/** Read-only accessor for diagnostic / dedup-aware peek. */
|
|
717
698
|
get value() {
|
|
718
699
|
return this.last;
|
|
719
700
|
}
|
|
720
701
|
};
|
|
721
702
|
|
|
703
|
+
// src/core/primitives/devWarn.ts
|
|
704
|
+
function devWarn(...args) {
|
|
705
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
|
|
706
|
+
console.warn(...args);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/core/primitives/overlay-position.ts
|
|
711
|
+
function resolveSheetAnchoredStyle(mode, position, inset) {
|
|
712
|
+
const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
|
|
713
|
+
switch (mode) {
|
|
714
|
+
case "bottom":
|
|
715
|
+
if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
|
|
716
|
+
if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
|
|
717
|
+
return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
718
|
+
case "top":
|
|
719
|
+
if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
|
|
720
|
+
if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
|
|
721
|
+
return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
722
|
+
case "left":
|
|
723
|
+
if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
|
|
724
|
+
if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
|
|
725
|
+
return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
726
|
+
case "right":
|
|
727
|
+
if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
|
|
728
|
+
if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
|
|
729
|
+
return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function applyOverlayPosition(ws, mode, position, inset) {
|
|
733
|
+
switch (position) {
|
|
734
|
+
case "top-left":
|
|
735
|
+
ws.top = inset;
|
|
736
|
+
ws.left = inset;
|
|
737
|
+
break;
|
|
738
|
+
case "top-center":
|
|
739
|
+
ws.top = inset;
|
|
740
|
+
ws.left = "50%";
|
|
741
|
+
ws.transform = "translateX(-50%)";
|
|
742
|
+
break;
|
|
743
|
+
case "top-right":
|
|
744
|
+
ws.top = inset;
|
|
745
|
+
ws.right = inset;
|
|
746
|
+
break;
|
|
747
|
+
case "center-left":
|
|
748
|
+
ws.top = "50%";
|
|
749
|
+
ws.left = inset;
|
|
750
|
+
ws.transform = "translateY(-50%)";
|
|
751
|
+
break;
|
|
752
|
+
case "center":
|
|
753
|
+
ws.top = "50%";
|
|
754
|
+
ws.left = "50%";
|
|
755
|
+
ws.transform = "translate(-50%, -50%)";
|
|
756
|
+
break;
|
|
757
|
+
case "center-right":
|
|
758
|
+
ws.top = "50%";
|
|
759
|
+
ws.right = inset;
|
|
760
|
+
ws.transform = "translateY(-50%)";
|
|
761
|
+
break;
|
|
762
|
+
case "bottom-left":
|
|
763
|
+
ws.bottom = inset;
|
|
764
|
+
ws.left = inset;
|
|
765
|
+
break;
|
|
766
|
+
case "bottom-center":
|
|
767
|
+
ws.bottom = inset;
|
|
768
|
+
ws.left = "50%";
|
|
769
|
+
ws.transform = "translateX(-50%)";
|
|
770
|
+
break;
|
|
771
|
+
case "bottom-right":
|
|
772
|
+
ws.bottom = inset;
|
|
773
|
+
ws.right = inset;
|
|
774
|
+
break;
|
|
775
|
+
case "sheet-top-left":
|
|
776
|
+
case "sheet-top-center":
|
|
777
|
+
case "sheet-top-right": {
|
|
778
|
+
const style = resolveSheetAnchoredStyle(mode, position, inset);
|
|
779
|
+
if (style.top !== void 0) ws.top = style.top;
|
|
780
|
+
if (style.bottom !== void 0) ws.bottom = style.bottom;
|
|
781
|
+
if (style.left !== void 0) ws.left = style.left;
|
|
782
|
+
if (style.right !== void 0) ws.right = style.right;
|
|
783
|
+
if (style.transform !== void 0) ws.transform = style.transform;
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
722
789
|
// src/core/controllers/scrim-controller.ts
|
|
790
|
+
var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
|
|
723
791
|
var ScrimController = class {
|
|
724
792
|
constructor(deps, opts) {
|
|
725
|
-
// ── DOM refs (mirror engine's; cached for hot path) ─────────────────
|
|
726
793
|
__publicField(this, "screenComponent");
|
|
727
794
|
__publicField(this, "backdrop");
|
|
728
795
|
__publicField(this, "mode");
|
|
729
|
-
// ── Engine callbacks ────────────────────────────────────────────────
|
|
730
796
|
__publicField(this, "isDestroyed");
|
|
731
797
|
__publicField(this, "isTopSheet");
|
|
732
798
|
__publicField(this, "getAllowedIds");
|
|
733
799
|
__publicField(this, "getResolvedSnaps");
|
|
734
800
|
__publicField(this, "snapToFn");
|
|
735
|
-
|
|
801
|
+
__publicField(this, "closeFn");
|
|
802
|
+
__publicField(this, "scrimInteractive", false);
|
|
736
803
|
__publicField(this, "scrimMode");
|
|
737
804
|
__publicField(this, "backdropRange");
|
|
738
805
|
__publicField(this, "screenRange");
|
|
739
|
-
// setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
|
|
740
|
-
// ranges so re-enable can restore prior values without a remount. Public
|
|
741
|
-
// so tests / introspection can read it without an `as any` cast.
|
|
742
806
|
__publicField(this, "scrimEnabled", true);
|
|
743
807
|
__publicField(this, "savedScrimRanges", null);
|
|
744
|
-
// ── Tap-to-close ────────────────────────────────────────────────────
|
|
745
808
|
__publicField(this, "scrimTapToCloseEnabled", false);
|
|
746
809
|
__publicField(this, "detachScrimTap", null);
|
|
747
|
-
// ── Scrim overlay slot (positioned floating element) ────────────────
|
|
748
810
|
__publicField(this, "scrimOverlayTeardown", null);
|
|
749
|
-
// ── Hot-path opacity dedup caches (per-frame writes) ────────────────
|
|
750
|
-
// Numeric sentinels migrated to WriteSentinel primitive — single
|
|
751
|
-
// invalidation surface, shared with the engine's size/progress sentinels.
|
|
752
|
-
// The two string-typed sentinels (`Pointer`, `Display`) keep their inline
|
|
753
|
-
// pattern: equality on string union, no epsilon, different reset semantics.
|
|
754
811
|
__publicField(this, "backdropOpacitySentinel", new WriteSentinel());
|
|
755
812
|
__publicField(this, "screenOpacitySentinel", new WriteSentinel());
|
|
756
813
|
__publicField(this, "lastBackdropPointer", "");
|
|
@@ -763,6 +820,7 @@ var ScrimController = class {
|
|
|
763
820
|
this.getAllowedIds = deps.getAllowedIds;
|
|
764
821
|
this.getResolvedSnaps = deps.getResolvedSnaps;
|
|
765
822
|
this.snapToFn = deps.snapTo;
|
|
823
|
+
this.closeFn = deps.close;
|
|
766
824
|
this.scrimMode = opts.scrimMode ?? "full";
|
|
767
825
|
const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
|
|
768
826
|
this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
|
|
@@ -785,41 +843,15 @@ var ScrimController = class {
|
|
|
785
843
|
}
|
|
786
844
|
}
|
|
787
845
|
}
|
|
788
|
-
/**
|
|
789
|
-
* Invalidate the opacity dedup caches. Called from any setter that mutates
|
|
790
|
-
* range/mode/enabled — opacity is a pure function of (progress, range,
|
|
791
|
-
* enabled), so a config change makes the cached values stale even if
|
|
792
|
-
* `progress` itself didn't move. Next `applyOpacity(progress, false)` call
|
|
793
|
-
* sees the -1 sentinel and re-runs the math instead of short-circuiting.
|
|
794
|
-
*
|
|
795
|
-
* Public for the engine: `setSnapPoints` mutates resolved geometry which
|
|
796
|
-
* shifts `progress→opacity` mapping under the cache; engine calls this to
|
|
797
|
-
* keep the next render frame correct on a static sheet.
|
|
798
|
-
* @internal
|
|
799
|
-
*/
|
|
800
846
|
invalidateOpacityCache() {
|
|
801
847
|
this.backdropOpacitySentinel.invalidate();
|
|
802
848
|
this.screenOpacitySentinel.invalidate();
|
|
803
849
|
}
|
|
804
|
-
/** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
|
|
805
850
|
attach() {
|
|
806
851
|
if (this.scrimTapToCloseEnabled) {
|
|
807
852
|
this.setScrimTapToClose(true);
|
|
808
853
|
}
|
|
809
854
|
}
|
|
810
|
-
// ── Hot-path: opacity computation called per-frame from engine.applySize ──
|
|
811
|
-
/**
|
|
812
|
-
* Per-frame opacity writer. Computes backdrop + screen opacity from the
|
|
813
|
-
* sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
|
|
814
|
-
* caches so steady-state frames don't trigger style recalc.
|
|
815
|
-
*
|
|
816
|
-
* `progressChanged` is the engine's already-computed dedup gate — when
|
|
817
|
-
* `false`, opacity is by construction a pure function of `progress` so
|
|
818
|
-
* cached values are still valid; we short-circuit the Math.min/max +
|
|
819
|
-
* division work entirely. Setters that mutate ranges/mode/enabled
|
|
820
|
-
* invalidate the cache by writing -1 sentinels so the next call re-runs
|
|
821
|
-
* the math.
|
|
822
|
-
*/
|
|
823
855
|
applyOpacity(progress, progressChanged) {
|
|
824
856
|
if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
|
|
825
857
|
return;
|
|
@@ -865,80 +897,30 @@ var ScrimController = class {
|
|
|
865
897
|
}
|
|
866
898
|
}
|
|
867
899
|
}
|
|
868
|
-
// ── Public setters (engine forwards these 1:1) ─────────────────────
|
|
869
|
-
/**
|
|
870
|
-
* Replace the [start, end] progress window in which the backdrop fades 0→1.
|
|
871
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
872
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
873
|
-
* @example
|
|
874
|
-
* // Backdrop only starts darkening after the sheet crosses 30% open.
|
|
875
|
-
* engine.setBackdropRange([0.3, 1]);
|
|
876
|
-
*/
|
|
877
900
|
setBackdropRange(range, applySize) {
|
|
878
901
|
if (this.isDestroyed()) return;
|
|
879
902
|
this.backdropRange = range;
|
|
880
903
|
this.invalidateOpacityCache();
|
|
881
904
|
applySize();
|
|
882
905
|
}
|
|
883
|
-
/**
|
|
884
|
-
* Replace the [start, end] progress window in which the screen scrim fades 0→1.
|
|
885
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
886
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
887
|
-
* @example
|
|
888
|
-
* // Scrim is fully opaque only when the sheet is past 50% — softer feel.
|
|
889
|
-
* engine.setScreenRange([0, 0.5]);
|
|
890
|
-
*/
|
|
891
906
|
setScreenRange(range, applySize) {
|
|
892
907
|
if (this.isDestroyed()) return;
|
|
893
908
|
this.screenRange = range;
|
|
894
909
|
this.invalidateOpacityCache();
|
|
895
910
|
applySize();
|
|
896
911
|
}
|
|
897
|
-
/**
|
|
898
|
-
* Set the scrim background color. Pass `null`/`undefined` to clear the
|
|
899
|
-
* inline write and let consumer CSS take over.
|
|
900
|
-
* @param color - Any valid CSS color string, or `null`/`undefined` to clear.
|
|
901
|
-
* @example
|
|
902
|
-
* engine.setScrimColor("rgba(0, 0, 0, 0.6)");
|
|
903
|
-
* engine.setScrimColor(null); // hand back to consumer CSS
|
|
904
|
-
*/
|
|
905
912
|
setScrimColor(color) {
|
|
906
913
|
if (this.isDestroyed()) return;
|
|
907
914
|
this.applyScrimStyles({ color: color === void 0 ? null : color });
|
|
908
915
|
}
|
|
909
|
-
/**
|
|
910
|
-
* Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
|
|
911
|
-
* @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
|
|
912
|
-
* `null`/`undefined` to remove the filter.
|
|
913
|
-
* @example
|
|
914
|
-
* engine.setScrimBlur("12px"); // frosted-glass effect
|
|
915
|
-
* engine.setScrimBlur(null); // disable blur
|
|
916
|
-
*/
|
|
917
916
|
setScrimBlur(blur) {
|
|
918
917
|
if (this.isDestroyed()) return;
|
|
919
918
|
this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
|
|
920
919
|
}
|
|
921
|
-
/**
|
|
922
|
-
* Toggle whether the scrim layer captures pointer events.
|
|
923
|
-
* @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
|
|
924
|
-
* @example
|
|
925
|
-
* engine.setScrimInteractive(true); // scrim swallows clicks
|
|
926
|
-
* engine.setScrimInteractive(false); // clicks pass through to content below
|
|
927
|
-
*/
|
|
928
920
|
setScrimInteractive(interactive) {
|
|
929
921
|
if (this.isDestroyed()) return;
|
|
930
922
|
this.applyScrimStyles({ interactive });
|
|
931
923
|
}
|
|
932
|
-
/**
|
|
933
|
-
* Switch scrim positioning at runtime.
|
|
934
|
-
* @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
|
|
935
|
-
* so only the area NOT covered by the sheet is dimmed), or
|
|
936
|
-
* `"off"` (display:none + opacity:0 — no remount needed).
|
|
937
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
938
|
-
* @example
|
|
939
|
-
* engine.setScrimMode("above-sheet"); // dim only the area above the sheet
|
|
940
|
-
* engine.setScrimMode("off"); // disable scrim layer
|
|
941
|
-
*/
|
|
942
924
|
setScrimMode(mode, applySize) {
|
|
943
925
|
if (this.isDestroyed()) return;
|
|
944
926
|
if (mode === this.scrimMode) return;
|
|
@@ -968,15 +950,6 @@ var ScrimController = class {
|
|
|
968
950
|
}
|
|
969
951
|
applySize();
|
|
970
952
|
}
|
|
971
|
-
/**
|
|
972
|
-
* Enable or disable click-on-scrim → snap to first non-zero allowed snap.
|
|
973
|
-
* Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
|
|
974
|
-
* click listener actually fires.
|
|
975
|
-
* @param enabled - `true` installs the click listener; `false` removes it.
|
|
976
|
-
* @example
|
|
977
|
-
* engine.setScrimTapToClose(true); // tap outside sheet collapses it
|
|
978
|
-
* engine.setScrimTapToClose(false); // require explicit close button
|
|
979
|
-
*/
|
|
980
953
|
setScrimTapToClose(enabled) {
|
|
981
954
|
if (this.isDestroyed()) return;
|
|
982
955
|
if (!this.screenComponent) return;
|
|
@@ -989,11 +962,15 @@ var ScrimController = class {
|
|
|
989
962
|
}
|
|
990
963
|
const onClick = () => {
|
|
991
964
|
if (this.isDestroyed()) return;
|
|
965
|
+
if (this.closeFn) {
|
|
966
|
+
this.closeFn();
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
992
969
|
const allowed = this.getAllowedIds();
|
|
993
970
|
const snaps = this.getResolvedSnaps();
|
|
994
971
|
const fallback = allowed.find((id) => {
|
|
995
972
|
const snap = findById(id, snaps);
|
|
996
|
-
return snap !== null && snap.size
|
|
973
|
+
return snap !== null && snap.size === 0;
|
|
997
974
|
});
|
|
998
975
|
if (fallback) this.snapToFn(fallback);
|
|
999
976
|
};
|
|
@@ -1006,20 +983,11 @@ var ScrimController = class {
|
|
|
1006
983
|
this.scrimTapToCloseEnabled = false;
|
|
1007
984
|
}
|
|
1008
985
|
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Toggle the scrim layer without remounting. Disabling stashes current
|
|
1011
|
-
* `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
|
|
1012
|
-
* formula → 0 across the whole range); re-enabling restores the saved values.
|
|
1013
|
-
* @param enabled - `false` hides scrim, `true` restores prior ranges.
|
|
1014
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
1015
|
-
* @example
|
|
1016
|
-
* engine.setScrimEnabled(false); // hide scrim, ranges remembered
|
|
1017
|
-
* engine.setScrimEnabled(true); // restore prior ranges (no remount)
|
|
1018
|
-
*/
|
|
1019
986
|
setScrimEnabled(enabled, applySize) {
|
|
1020
987
|
if (this.isDestroyed()) return;
|
|
1021
988
|
if (enabled === this.scrimEnabled) return;
|
|
1022
989
|
this.scrimEnabled = enabled;
|
|
990
|
+
this.invalidateOpacityCache();
|
|
1023
991
|
if (!enabled) {
|
|
1024
992
|
this.savedScrimRanges = {
|
|
1025
993
|
screen: this.screenRange,
|
|
@@ -1034,20 +1002,6 @@ var ScrimController = class {
|
|
|
1034
1002
|
}
|
|
1035
1003
|
applySize();
|
|
1036
1004
|
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Batch-update multiple scrim fields with a single applySize() at the end.
|
|
1039
|
-
* If `preset` is set, it seeds color/blur/interactive/range first; explicit
|
|
1040
|
-
* fields override.
|
|
1041
|
-
* @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
|
|
1042
|
-
* color, blur, interactive, range.
|
|
1043
|
-
* @param applySize - Engine-supplied recompute hook; runs ONCE per call.
|
|
1044
|
-
* @example
|
|
1045
|
-
* engine.setScrim({
|
|
1046
|
-
* preset: "frosted",
|
|
1047
|
-
* mode: "above-sheet",
|
|
1048
|
-
* tapToClose: true,
|
|
1049
|
-
* });
|
|
1050
|
-
*/
|
|
1051
1005
|
setScrim(opts, applySize) {
|
|
1052
1006
|
if (this.isDestroyed()) return;
|
|
1053
1007
|
if (opts.preset) {
|
|
@@ -1057,7 +1011,12 @@ var ScrimController = class {
|
|
|
1057
1011
|
blur: cfg.blur ?? null,
|
|
1058
1012
|
interactive: cfg.interactive
|
|
1059
1013
|
});
|
|
1060
|
-
this.
|
|
1014
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
1015
|
+
this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
|
|
1016
|
+
} else {
|
|
1017
|
+
this.screenRange = cfg.range;
|
|
1018
|
+
this.backdropRange = cfg.range;
|
|
1019
|
+
}
|
|
1061
1020
|
}
|
|
1062
1021
|
const noop = () => {
|
|
1063
1022
|
};
|
|
@@ -1070,27 +1029,18 @@ var ScrimController = class {
|
|
|
1070
1029
|
interactive: opts.interactive
|
|
1071
1030
|
});
|
|
1072
1031
|
if (opts.range !== void 0) {
|
|
1073
|
-
this.
|
|
1032
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
1033
|
+
this.savedScrimRanges = {
|
|
1034
|
+
screen: opts.range,
|
|
1035
|
+
backdrop: this.savedScrimRanges.backdrop
|
|
1036
|
+
};
|
|
1037
|
+
} else {
|
|
1038
|
+
this.screenRange = opts.range;
|
|
1039
|
+
}
|
|
1074
1040
|
}
|
|
1075
1041
|
this.invalidateOpacityCache();
|
|
1076
1042
|
applySize();
|
|
1077
1043
|
}
|
|
1078
|
-
/**
|
|
1079
|
-
* Inject a positioned floating element into the scrim layer (e.g. a close
|
|
1080
|
-
* button anchored top-right). Replaces any prior overlay so repeated calls
|
|
1081
|
-
* (reactive effects re-running) don't accumulate wrappers.
|
|
1082
|
-
* @param opts - Children element, optional `inset` (default `"16px"`),
|
|
1083
|
-
* `position` (9-way grid, default `"top-right"`), and
|
|
1084
|
-
* `interactive` flag (default `true`).
|
|
1085
|
-
* @returns Teardown function that removes the wrapper from the DOM.
|
|
1086
|
-
* @example
|
|
1087
|
-
* const teardown = engine.setScrimOverlay({
|
|
1088
|
-
* children: closeBtn,
|
|
1089
|
-
* position: "top-right",
|
|
1090
|
-
* inset: "20px",
|
|
1091
|
-
* });
|
|
1092
|
-
* // later: teardown(); // remove the floating button
|
|
1093
|
-
*/
|
|
1094
1044
|
setScrimOverlay(opts) {
|
|
1095
1045
|
if (this.isDestroyed()) return () => {
|
|
1096
1046
|
};
|
|
@@ -1098,61 +1048,38 @@ var ScrimController = class {
|
|
|
1098
1048
|
};
|
|
1099
1049
|
this.scrimOverlayTeardown?.();
|
|
1100
1050
|
const screen = this.screenComponent;
|
|
1101
|
-
const
|
|
1051
|
+
const insetRaw = opts.inset ?? "16px";
|
|
1052
|
+
const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
|
|
1053
|
+
if (inset !== insetRaw) {
|
|
1054
|
+
devWarn(
|
|
1055
|
+
`[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
|
|
1059
|
+
devWarn(
|
|
1060
|
+
"[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."
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1102
1063
|
const position = opts.position ?? "top-right";
|
|
1103
1064
|
const interactive = opts.interactive ?? true;
|
|
1104
1065
|
const wrapper = screen.ownerDocument.createElement("div");
|
|
1105
1066
|
wrapper.className = "bs-scrim-overlay";
|
|
1106
1067
|
const ws = wrapper.style;
|
|
1107
1068
|
ws.position = "absolute";
|
|
1108
|
-
|
|
1109
|
-
case "top-left":
|
|
1110
|
-
ws.top = inset;
|
|
1111
|
-
ws.left = inset;
|
|
1112
|
-
break;
|
|
1113
|
-
case "top-center":
|
|
1114
|
-
ws.top = inset;
|
|
1115
|
-
ws.left = "50%";
|
|
1116
|
-
ws.transform = "translateX(-50%)";
|
|
1117
|
-
break;
|
|
1118
|
-
case "top-right":
|
|
1119
|
-
ws.top = inset;
|
|
1120
|
-
ws.right = inset;
|
|
1121
|
-
break;
|
|
1122
|
-
case "center-left":
|
|
1123
|
-
ws.top = "50%";
|
|
1124
|
-
ws.left = inset;
|
|
1125
|
-
ws.transform = "translateY(-50%)";
|
|
1126
|
-
break;
|
|
1127
|
-
case "center":
|
|
1128
|
-
ws.top = "50%";
|
|
1129
|
-
ws.left = "50%";
|
|
1130
|
-
ws.transform = "translate(-50%, -50%)";
|
|
1131
|
-
break;
|
|
1132
|
-
case "center-right":
|
|
1133
|
-
ws.top = "50%";
|
|
1134
|
-
ws.right = inset;
|
|
1135
|
-
ws.transform = "translateY(-50%)";
|
|
1136
|
-
break;
|
|
1137
|
-
case "bottom-left":
|
|
1138
|
-
ws.bottom = inset;
|
|
1139
|
-
ws.left = inset;
|
|
1140
|
-
break;
|
|
1141
|
-
case "bottom-center":
|
|
1142
|
-
ws.bottom = inset;
|
|
1143
|
-
ws.left = "50%";
|
|
1144
|
-
ws.transform = "translateX(-50%)";
|
|
1145
|
-
break;
|
|
1146
|
-
case "bottom-right":
|
|
1147
|
-
ws.bottom = inset;
|
|
1148
|
-
ws.right = inset;
|
|
1149
|
-
break;
|
|
1150
|
-
}
|
|
1069
|
+
applyOverlayPosition(ws, this.mode, position, inset);
|
|
1151
1070
|
if (interactive) {
|
|
1152
1071
|
ws.pointerEvents = "auto";
|
|
1153
1072
|
}
|
|
1154
1073
|
wrapper.appendChild(opts.children);
|
|
1155
|
-
screen.
|
|
1074
|
+
const host = screen.parentElement;
|
|
1075
|
+
if (!host) {
|
|
1076
|
+
devWarn(
|
|
1077
|
+
"[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
|
|
1078
|
+
);
|
|
1079
|
+
return () => {
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
host.appendChild(wrapper);
|
|
1156
1083
|
const teardown = () => {
|
|
1157
1084
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
1158
1085
|
if (this.scrimOverlayTeardown === teardown) {
|
|
@@ -1162,7 +1089,6 @@ var ScrimController = class {
|
|
|
1162
1089
|
this.scrimOverlayTeardown = teardown;
|
|
1163
1090
|
return teardown;
|
|
1164
1091
|
}
|
|
1165
|
-
/** Tear down toggle-state listeners + remove any injected overlay wrapper. */
|
|
1166
1092
|
destroy() {
|
|
1167
1093
|
this.detachScrimTap?.();
|
|
1168
1094
|
this.detachScrimTap = null;
|
|
@@ -1181,8 +1107,6 @@ var ScrimController = class {
|
|
|
1181
1107
|
this.lastBackdropPointer = "";
|
|
1182
1108
|
this.lastScreenDisplay = "";
|
|
1183
1109
|
}
|
|
1184
|
-
// ── Private helpers ────────────────────────────────────────────────
|
|
1185
|
-
/** Centralized scrim style writer — atomic 3-property write across all setters. */
|
|
1186
1110
|
applyScrimStyles(opts) {
|
|
1187
1111
|
if (!this.screenComponent) return;
|
|
1188
1112
|
const s = this.screenComponent.style;
|
|
@@ -1201,10 +1125,10 @@ var ScrimController = class {
|
|
|
1201
1125
|
}
|
|
1202
1126
|
}
|
|
1203
1127
|
if (opts.interactive !== void 0) {
|
|
1128
|
+
this.scrimInteractive = opts.interactive;
|
|
1204
1129
|
s.pointerEvents = opts.interactive ? "auto" : "none";
|
|
1205
1130
|
}
|
|
1206
1131
|
}
|
|
1207
|
-
/** Pin the screen overlay to the area NOT occupied by the sheet. */
|
|
1208
1132
|
applyAboveSheetInset() {
|
|
1209
1133
|
if (!this.screenComponent) return;
|
|
1210
1134
|
const insetByMode = {
|
|
@@ -1216,7 +1140,7 @@ var ScrimController = class {
|
|
|
1216
1140
|
const s = this.screenComponent.style;
|
|
1217
1141
|
s.position = "fixed";
|
|
1218
1142
|
s.inset = insetByMode[this.mode];
|
|
1219
|
-
s.pointerEvents = "none";
|
|
1143
|
+
s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
|
|
1220
1144
|
}
|
|
1221
1145
|
};
|
|
1222
1146
|
|
|
@@ -1304,14 +1228,20 @@ var runSpring = ({
|
|
|
1304
1228
|
if (cancelled) return;
|
|
1305
1229
|
const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
|
|
1306
1230
|
lastTime = now;
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1231
|
+
const steps = Math.max(1, Math.ceil(dt * 240));
|
|
1232
|
+
const subDt = dt / steps;
|
|
1233
|
+
let stepsLeft = steps;
|
|
1309
1234
|
while (stepsLeft-- > 0) {
|
|
1310
1235
|
const force = -cfg.stiffness * (x - to) - cfg.damping * v;
|
|
1311
1236
|
const accel = force / cfg.mass;
|
|
1312
1237
|
v += accel * subDt;
|
|
1313
1238
|
x += v * subDt;
|
|
1314
1239
|
}
|
|
1240
|
+
if (!Number.isFinite(x) || !Number.isFinite(v)) {
|
|
1241
|
+
onUpdate(to, 0);
|
|
1242
|
+
resolve();
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1315
1245
|
onUpdate(x, v);
|
|
1316
1246
|
const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
|
|
1317
1247
|
if (isAtRest) {
|
|
@@ -1375,13 +1305,9 @@ var AnimationRunner = class {
|
|
|
1375
1305
|
__publicField(this, "duration");
|
|
1376
1306
|
__publicField(this, "easing");
|
|
1377
1307
|
__publicField(this, "respectReducedMotion");
|
|
1378
|
-
/** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
|
|
1379
1308
|
__publicField(this, "reducedMotion", false);
|
|
1380
|
-
/** Disposes the matchMedia change listener — drained from destroy(). */
|
|
1381
1309
|
__publicField(this, "detachReducedMotion", null);
|
|
1382
|
-
/** Computed once — both inputs (option + API presence) stable for life. */
|
|
1383
1310
|
__publicField(this, "viewTransitionsAvailable");
|
|
1384
|
-
/** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
|
|
1385
1311
|
__publicField(this, "currentTween", null);
|
|
1386
1312
|
__publicField(this, "currentSpring", null);
|
|
1387
1313
|
this.element = deps.element;
|
|
@@ -1412,26 +1338,13 @@ var AnimationRunner = class {
|
|
|
1412
1338
|
}
|
|
1413
1339
|
}
|
|
1414
1340
|
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Live derived flag — `true` whenever a spring/tween handle is in flight.
|
|
1417
|
-
* Reading the canonical handles (instead of mirroring into a separate
|
|
1418
|
-
* boolean) eliminates the reduced-motion early-return latent bug where
|
|
1419
|
-
* the flag stayed true forever, and removes the cross-controller
|
|
1420
|
-
* setIsAnimating callback.
|
|
1421
|
-
*/
|
|
1422
1341
|
get isAnimating() {
|
|
1423
1342
|
return this.currentTween !== null || this.currentSpring !== null;
|
|
1424
1343
|
}
|
|
1425
|
-
/** Cancel any in-flight tween/spring without touching engine state. */
|
|
1426
1344
|
cancel() {
|
|
1427
1345
|
this.currentTween?.cancel();
|
|
1428
1346
|
this.currentSpring?.cancel();
|
|
1429
1347
|
}
|
|
1430
|
-
/**
|
|
1431
|
-
* Animate from current size to `target`. Resolves when the animation
|
|
1432
|
-
* completes; resolves early if cancelled mid-flight (caller checks an
|
|
1433
|
-
* abort signal post-await for cycle-invariant correctness).
|
|
1434
|
-
*/
|
|
1435
1348
|
async animateTo(target, velocityPxPerMs) {
|
|
1436
1349
|
if (target === this.getSize()) return;
|
|
1437
1350
|
this.currentTween?.cancel();
|
|
@@ -1472,7 +1385,6 @@ var AnimationRunner = class {
|
|
|
1472
1385
|
if (!this.isDragging()) this.element.style.willChange = "auto";
|
|
1473
1386
|
}
|
|
1474
1387
|
}
|
|
1475
|
-
/** Tear down the matchMedia listener. Engine calls from destroy(). */
|
|
1476
1388
|
destroy() {
|
|
1477
1389
|
this.detachReducedMotion?.();
|
|
1478
1390
|
this.detachReducedMotion = null;
|
|
@@ -1504,9 +1416,13 @@ var isVisible = (el) => {
|
|
|
1504
1416
|
var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
|
|
1505
1417
|
(el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
|
|
1506
1418
|
);
|
|
1419
|
+
var trapStack = [];
|
|
1507
1420
|
var installFocusTrap = (container, options = {}) => {
|
|
1508
1421
|
if (typeof document === "undefined") return () => {
|
|
1509
1422
|
};
|
|
1423
|
+
const token = {};
|
|
1424
|
+
trapStack.push(token);
|
|
1425
|
+
const isActive = () => trapStack[trapStack.length - 1] === token;
|
|
1510
1426
|
const previouslyFocused = document.activeElement;
|
|
1511
1427
|
const focusInitial = () => {
|
|
1512
1428
|
const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
|
|
@@ -1514,6 +1430,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1514
1430
|
};
|
|
1515
1431
|
focusInitial();
|
|
1516
1432
|
const handleKey = (e) => {
|
|
1433
|
+
if (!isActive()) return;
|
|
1517
1434
|
if (e.key === "Escape" && options.onEscape) {
|
|
1518
1435
|
options.onEscape();
|
|
1519
1436
|
return;
|
|
@@ -1536,6 +1453,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1536
1453
|
}
|
|
1537
1454
|
};
|
|
1538
1455
|
const handleFocusIn = (e) => {
|
|
1456
|
+
if (!isActive()) return;
|
|
1539
1457
|
const target = e.target;
|
|
1540
1458
|
if (target && !container.contains(target)) {
|
|
1541
1459
|
const list = focusables(container);
|
|
@@ -1548,6 +1466,8 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
1548
1466
|
return () => {
|
|
1549
1467
|
if (released) return;
|
|
1550
1468
|
released = true;
|
|
1469
|
+
const idx = trapStack.indexOf(token);
|
|
1470
|
+
if (idx !== -1) trapStack.splice(idx, 1);
|
|
1551
1471
|
document.removeEventListener("keydown", handleKey, true);
|
|
1552
1472
|
document.removeEventListener("focusin", handleFocusIn, true);
|
|
1553
1473
|
if (previouslyFocused && document.contains(previouslyFocused)) {
|
|
@@ -1603,11 +1523,17 @@ function createInertSiblings(rootProvider) {
|
|
|
1603
1523
|
return {
|
|
1604
1524
|
apply() {
|
|
1605
1525
|
if (typeof document === "undefined") return;
|
|
1606
|
-
|
|
1607
|
-
|
|
1526
|
+
let top = rootProvider();
|
|
1527
|
+
while (top.parentElement && top.parentElement !== document.body) {
|
|
1528
|
+
top = top.parentElement;
|
|
1529
|
+
}
|
|
1530
|
+
const parent = top.parentElement ?? document.body;
|
|
1608
1531
|
for (const child of Array.from(parent.children)) {
|
|
1609
|
-
if (child ===
|
|
1532
|
+
if (child === top) continue;
|
|
1610
1533
|
if (child.hasAttribute("inert")) continue;
|
|
1534
|
+
if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1611
1537
|
child.setAttribute("inert", "");
|
|
1612
1538
|
tracked.push(child);
|
|
1613
1539
|
}
|
|
@@ -1623,7 +1549,6 @@ function createInertSiblings(rootProvider) {
|
|
|
1623
1549
|
var LifecycleController = class {
|
|
1624
1550
|
constructor(deps, opts) {
|
|
1625
1551
|
__publicField(this, "element");
|
|
1626
|
-
__publicField(this, "closeFn");
|
|
1627
1552
|
__publicField(this, "focusTrapEnabled");
|
|
1628
1553
|
__publicField(this, "initialFocus");
|
|
1629
1554
|
__publicField(this, "closeOnEscape");
|
|
@@ -1632,16 +1557,11 @@ var LifecycleController = class {
|
|
|
1632
1557
|
__publicField(this, "inertSiblings");
|
|
1633
1558
|
__publicField(this, "shouldApplyInertSiblings");
|
|
1634
1559
|
__publicField(this, "returnFocus");
|
|
1635
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
1636
1560
|
__publicField(this, "releaseFocusTrap", null);
|
|
1637
1561
|
__publicField(this, "releaseScrollLock", null);
|
|
1638
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
1639
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
1640
|
-
* released or never-installed instances. */
|
|
1641
1562
|
__publicField(this, "installed", false);
|
|
1642
1563
|
__publicField(this, "destroyed", false);
|
|
1643
1564
|
this.element = deps.element;
|
|
1644
|
-
this.closeFn = deps.close;
|
|
1645
1565
|
this.focusTrapEnabled = opts.focusTrap ?? false;
|
|
1646
1566
|
this.initialFocus = opts.initialFocus;
|
|
1647
1567
|
this.closeOnEscape = opts.closeOnEscape ?? true;
|
|
@@ -1651,14 +1571,9 @@ var LifecycleController = class {
|
|
|
1651
1571
|
this.returnFocus = opts.returnFocus;
|
|
1652
1572
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
1653
1573
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
1658
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
1659
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
1660
|
-
* No-op when destroyed.
|
|
1661
|
-
*/
|
|
1574
|
+
get isInstalled() {
|
|
1575
|
+
return this.installed;
|
|
1576
|
+
}
|
|
1662
1577
|
install() {
|
|
1663
1578
|
if (this.destroyed) return;
|
|
1664
1579
|
this.installed = true;
|
|
@@ -1667,30 +1582,17 @@ var LifecycleController = class {
|
|
|
1667
1582
|
}
|
|
1668
1583
|
if (this.focusTrapEnabled && !this.releaseFocusTrap) {
|
|
1669
1584
|
this.releaseFocusTrap = installFocusTrap(this.element, {
|
|
1670
|
-
initialFocus: this.initialFocus
|
|
1671
|
-
onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
|
|
1585
|
+
initialFocus: this.initialFocus
|
|
1672
1586
|
});
|
|
1673
1587
|
}
|
|
1674
1588
|
if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
|
|
1675
1589
|
this.inertSiblings.apply();
|
|
1676
1590
|
}
|
|
1677
1591
|
}
|
|
1678
|
-
/**
|
|
1679
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
1680
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
1681
|
-
* on the next `release()`. No-op when destroyed.
|
|
1682
|
-
*/
|
|
1683
1592
|
setReturnFocus(target) {
|
|
1684
1593
|
if (this.destroyed) return;
|
|
1685
1594
|
this.returnFocus = target;
|
|
1686
1595
|
}
|
|
1687
|
-
/**
|
|
1688
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
1689
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
1690
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
1691
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
1692
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
1693
|
-
*/
|
|
1694
1596
|
release() {
|
|
1695
1597
|
if (!this.installed) return;
|
|
1696
1598
|
this.installed = false;
|
|
@@ -1704,12 +1606,6 @@ var LifecycleController = class {
|
|
|
1704
1606
|
target?.focus?.();
|
|
1705
1607
|
}
|
|
1706
1608
|
}
|
|
1707
|
-
/**
|
|
1708
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
1709
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
1710
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
1711
|
-
* restore.
|
|
1712
|
-
*/
|
|
1713
1609
|
destroy() {
|
|
1714
1610
|
this.release();
|
|
1715
1611
|
this.destroyed = true;
|
|
@@ -1719,12 +1615,10 @@ var LifecycleController = class {
|
|
|
1719
1615
|
// src/core/gestures.ts
|
|
1720
1616
|
var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
|
|
1721
1617
|
var SampleRing = class {
|
|
1722
|
-
// current count, ≤ capacity
|
|
1723
1618
|
constructor(capacity) {
|
|
1724
1619
|
__publicField(this, "slots");
|
|
1725
1620
|
__publicField(this, "capacity");
|
|
1726
1621
|
__publicField(this, "head", 0);
|
|
1727
|
-
// next write index
|
|
1728
1622
|
__publicField(this, "size", 0);
|
|
1729
1623
|
this.capacity = capacity;
|
|
1730
1624
|
this.slots = new Array(capacity);
|
|
@@ -1737,12 +1631,6 @@ var SampleRing = class {
|
|
|
1737
1631
|
this.head = (this.head + 1) % this.capacity;
|
|
1738
1632
|
if (this.size < this.capacity) this.size++;
|
|
1739
1633
|
}
|
|
1740
|
-
/**
|
|
1741
|
-
* Drop oldest entries whose timestamp is below `cutoff`, but never shrink
|
|
1742
|
-
* below `minKeep`. Mirrors the original
|
|
1743
|
-
* `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
|
|
1744
|
-
* but without copying — just advances the logical tail.
|
|
1745
|
-
*/
|
|
1746
1634
|
pruneBefore(cutoff, minKeep) {
|
|
1747
1635
|
while (this.size > minKeep) {
|
|
1748
1636
|
const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
|
|
@@ -1849,10 +1737,11 @@ var installGestures = (handle, mode, callbacks) => {
|
|
|
1849
1737
|
var attachGestures = installGestures;
|
|
1850
1738
|
|
|
1851
1739
|
// src/core/primitives/rubber-band.ts
|
|
1852
|
-
var RUBBER_BAND_FACTOR = 0.55;
|
|
1853
1740
|
function rubberBand(overshoot, maxAxisSize) {
|
|
1854
1741
|
const cap = Math.min(maxAxisSize * 0.15, 80);
|
|
1855
|
-
|
|
1742
|
+
if (cap <= 0) return 0;
|
|
1743
|
+
const abs = Math.abs(overshoot);
|
|
1744
|
+
return Math.sign(overshoot) * (cap * abs / (abs + cap));
|
|
1856
1745
|
}
|
|
1857
1746
|
|
|
1858
1747
|
// src/core/features/soft-keyboard.ts
|
|
@@ -1879,28 +1768,14 @@ var GestureController = class {
|
|
|
1879
1768
|
__publicField(this, "detach", null);
|
|
1880
1769
|
__publicField(this, "isDragging_", false);
|
|
1881
1770
|
__publicField(this, "dragStartSize", 0);
|
|
1882
|
-
// Only "touch" gestures are eligible for soft-keyboard dismissal —
|
|
1883
|
-
// mouse/trackpad users don't have a keyboard to dismiss.
|
|
1884
1771
|
__publicField(this, "currentPointerKind", "touch");
|
|
1885
|
-
// One blur per drag — flipping focus every move event races the keyboard's
|
|
1886
|
-
// own animations on iOS and makes the page jitter.
|
|
1887
1772
|
__publicField(this, "keyboardDismissed", false);
|
|
1888
|
-
// Reused payload object for the "drag" event — mutated each onMove instead
|
|
1889
|
-
// of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
|
|
1890
|
-
// would otherwise churn tens of KB through the GC. Mirrors browser
|
|
1891
|
-
// PointerEvent semantics: consumers must NOT retain the reference across
|
|
1892
|
-
// frames (clone if needed). See SheetEventMap.drag JSDoc.
|
|
1893
1773
|
__publicField(this, "dragPayload", { size: 0, delta: 0 });
|
|
1894
1774
|
this.deps = deps;
|
|
1895
1775
|
}
|
|
1896
|
-
/** True between `dragstart` and `dragend`/`onCancel`. */
|
|
1897
1776
|
get isDragging() {
|
|
1898
1777
|
return this.isDragging_;
|
|
1899
1778
|
}
|
|
1900
|
-
/**
|
|
1901
|
-
* Bind pointer listeners on the handle and return a teardown. Engine pushes
|
|
1902
|
-
* the teardown into its TeardownStack so destroy() drains it once.
|
|
1903
|
-
*/
|
|
1904
1779
|
install() {
|
|
1905
1780
|
if (this.detach) return this.detach;
|
|
1906
1781
|
this.detach = installGestures(this.deps.handle, this.deps.mode, {
|
|
@@ -1951,17 +1826,16 @@ var GestureController = class {
|
|
|
1951
1826
|
this.isDragging_ = false;
|
|
1952
1827
|
this.keyboardDismissed = false;
|
|
1953
1828
|
this.deps.getRoot()?.removeAttribute("data-dragging");
|
|
1829
|
+
this.deps.emit("dragend", {
|
|
1830
|
+
size: this.deps.getDragContext().size,
|
|
1831
|
+
velocity: 0
|
|
1832
|
+
});
|
|
1954
1833
|
const restoreTo = this.dragStartSize;
|
|
1955
1834
|
void this.deps.animateTo(restoreTo, 0);
|
|
1956
1835
|
}
|
|
1957
1836
|
});
|
|
1958
1837
|
return this.detach;
|
|
1959
1838
|
}
|
|
1960
|
-
/**
|
|
1961
|
-
* Force-clear drag visual state. Engine calls from destroy() when destroyed
|
|
1962
|
-
* mid-drag — gesture detach won't synthesize an onCancel, so we have to
|
|
1963
|
-
* roll back the data-attribute + will-change ourselves.
|
|
1964
|
-
*/
|
|
1965
1839
|
forceClearDragState() {
|
|
1966
1840
|
if (!this.isDragging_) return;
|
|
1967
1841
|
this.isDragging_ = false;
|
|
@@ -1980,11 +1854,6 @@ var AriaSliderWriter = class {
|
|
|
1980
1854
|
mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
|
|
1981
1855
|
);
|
|
1982
1856
|
}
|
|
1983
|
-
/**
|
|
1984
|
-
* Update slider position. `allowedIds` length determines `aria-valuemax`;
|
|
1985
|
-
* `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
|
|
1986
|
-
* Empty list → strip every dynamic attr so AT skips the handle entirely.
|
|
1987
|
-
*/
|
|
1988
1857
|
setValue(allowedIds, activeId) {
|
|
1989
1858
|
if (allowedIds.length === 0) {
|
|
1990
1859
|
this.handle.removeAttribute("aria-valuemin");
|
|
@@ -2034,6 +1903,287 @@ function createScrollCache(deps) {
|
|
|
2034
1903
|
};
|
|
2035
1904
|
}
|
|
2036
1905
|
|
|
1906
|
+
// src/core/primitives/anchor-animations.ts
|
|
1907
|
+
var DEFAULT_DURATION3 = 200;
|
|
1908
|
+
var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1909
|
+
var PRESET_FRAMES = {
|
|
1910
|
+
fade: {
|
|
1911
|
+
enter: [{ opacity: 0 }, { opacity: 1 }],
|
|
1912
|
+
exit: [{ opacity: 1 }, { opacity: 0 }]
|
|
1913
|
+
},
|
|
1914
|
+
scale: {
|
|
1915
|
+
enter: [
|
|
1916
|
+
{ opacity: 0, transform: "scale(0.85)" },
|
|
1917
|
+
{ opacity: 1, transform: "scale(1)" }
|
|
1918
|
+
],
|
|
1919
|
+
exit: [
|
|
1920
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
1921
|
+
{ opacity: 0, transform: "scale(0.85)" }
|
|
1922
|
+
]
|
|
1923
|
+
},
|
|
1924
|
+
slide: {
|
|
1925
|
+
enter: [
|
|
1926
|
+
{ opacity: 0, transform: "translateY(10px)" },
|
|
1927
|
+
{ opacity: 1, transform: "translateY(0)" }
|
|
1928
|
+
],
|
|
1929
|
+
exit: [
|
|
1930
|
+
{ opacity: 1, transform: "translateY(0)" },
|
|
1931
|
+
{ opacity: 0, transform: "translateY(10px)" }
|
|
1932
|
+
]
|
|
1933
|
+
},
|
|
1934
|
+
pop: {
|
|
1935
|
+
enter: [
|
|
1936
|
+
{ opacity: 0, transform: "scale(0.6)", offset: 0 },
|
|
1937
|
+
{ opacity: 1, transform: "scale(1.06)", offset: 0.7 },
|
|
1938
|
+
{ opacity: 1, transform: "scale(1)", offset: 1 }
|
|
1939
|
+
],
|
|
1940
|
+
exit: [
|
|
1941
|
+
{ opacity: 1, transform: "scale(1)" },
|
|
1942
|
+
{ opacity: 0, transform: "scale(0.6)" }
|
|
1943
|
+
]
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
var INSTANT = {
|
|
1947
|
+
finished: Promise.resolve(),
|
|
1948
|
+
cancel: () => {
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
function resolveTransitionFrames(spec, phase) {
|
|
1952
|
+
const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
|
|
1953
|
+
if (spec === "none") return fallback;
|
|
1954
|
+
if (spec === void 0 || typeof spec === "string") {
|
|
1955
|
+
const preset2 = PRESET_FRAMES[spec ?? "fade"];
|
|
1956
|
+
return {
|
|
1957
|
+
frames: preset2[phase],
|
|
1958
|
+
duration: DEFAULT_DURATION3,
|
|
1959
|
+
easing: DEFAULT_EASING
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
const custom = phase === "enter" ? spec.enter : spec.exit;
|
|
1963
|
+
if (custom) {
|
|
1964
|
+
return {
|
|
1965
|
+
frames: custom,
|
|
1966
|
+
duration: spec.duration ?? DEFAULT_DURATION3,
|
|
1967
|
+
easing: spec.easing ?? DEFAULT_EASING
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
if (spec.preset === "none") return fallback;
|
|
1971
|
+
const preset = PRESET_FRAMES[spec.preset ?? "fade"];
|
|
1972
|
+
return {
|
|
1973
|
+
frames: preset[phase],
|
|
1974
|
+
duration: spec.duration ?? DEFAULT_DURATION3,
|
|
1975
|
+
easing: spec.easing ?? DEFAULT_EASING
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
function runAnchorTransition(el, spec, phase) {
|
|
1979
|
+
const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
|
|
1980
|
+
if (respectReduced && prefersReducedMotion()) return INSTANT;
|
|
1981
|
+
const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
|
|
1982
|
+
if (!frames || duration <= 0) return INSTANT;
|
|
1983
|
+
if (typeof el.animate !== "function") return INSTANT;
|
|
1984
|
+
const animation = el.animate(frames, {
|
|
1985
|
+
duration,
|
|
1986
|
+
easing,
|
|
1987
|
+
fill: "both"
|
|
1988
|
+
});
|
|
1989
|
+
const finished = animation.finished.then(() => void 0).catch(() => void 0);
|
|
1990
|
+
return {
|
|
1991
|
+
finished,
|
|
1992
|
+
cancel: () => {
|
|
1993
|
+
try {
|
|
1994
|
+
animation.cancel();
|
|
1995
|
+
} catch {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// src/core/features/sheet-anchors.ts
|
|
2003
|
+
function attachAnchor(deps, opts) {
|
|
2004
|
+
const doc = deps.host.ownerDocument;
|
|
2005
|
+
const wrapper = doc.createElement("div");
|
|
2006
|
+
wrapper.className = "bs-anchor";
|
|
2007
|
+
const interactive = opts.interactive ?? true;
|
|
2008
|
+
const position = opts.position ?? "sheet-top-right";
|
|
2009
|
+
const isDock = position === "dock-bottom" || position === "dock-top";
|
|
2010
|
+
const ws = wrapper.style;
|
|
2011
|
+
ws.position = "fixed";
|
|
2012
|
+
if (isDock) {
|
|
2013
|
+
ws.left = "0";
|
|
2014
|
+
ws.right = "0";
|
|
2015
|
+
if (position === "dock-bottom") {
|
|
2016
|
+
ws.bottom = "0";
|
|
2017
|
+
} else {
|
|
2018
|
+
ws.top = "0";
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
|
|
2022
|
+
}
|
|
2023
|
+
if (opts.fadeRange) {
|
|
2024
|
+
const [r0, r1] = opts.fadeRange;
|
|
2025
|
+
const span = Math.max(r1 - r0, 1e-4);
|
|
2026
|
+
ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
|
|
2027
|
+
}
|
|
2028
|
+
wrapper.appendChild(opts.element);
|
|
2029
|
+
deps.host.appendChild(wrapper);
|
|
2030
|
+
let visible = null;
|
|
2031
|
+
let inFlight = null;
|
|
2032
|
+
const shouldShow = (state) => {
|
|
2033
|
+
if (typeof opts.showOn === "function") return opts.showOn(state);
|
|
2034
|
+
if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
|
|
2035
|
+
return isDock || state.size > 0;
|
|
2036
|
+
};
|
|
2037
|
+
const applyVisibility = (next, animate) => {
|
|
2038
|
+
if (next === visible) return;
|
|
2039
|
+
visible = next;
|
|
2040
|
+
inFlight?.cancel();
|
|
2041
|
+
inFlight = null;
|
|
2042
|
+
wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
|
|
2043
|
+
if (next) {
|
|
2044
|
+
wrapper.style.visibility = "";
|
|
2045
|
+
if (animate) {
|
|
2046
|
+
inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
|
|
2047
|
+
}
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (!animate) {
|
|
2051
|
+
wrapper.style.visibility = "hidden";
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
const handle = runAnchorTransition(opts.element, opts.animation, "exit");
|
|
2055
|
+
inFlight = handle;
|
|
2056
|
+
void handle.finished.then(() => {
|
|
2057
|
+
if (inFlight === handle && visible === false) {
|
|
2058
|
+
wrapper.style.visibility = "hidden";
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
};
|
|
2062
|
+
const evaluate = (animate) => {
|
|
2063
|
+
if (deps.isDestroyed()) return;
|
|
2064
|
+
applyVisibility(shouldShow(deps.getState()), animate);
|
|
2065
|
+
};
|
|
2066
|
+
const offs = [
|
|
2067
|
+
deps.on("snap", () => evaluate(true)),
|
|
2068
|
+
deps.on("open", () => evaluate(true)),
|
|
2069
|
+
deps.on("close", () => evaluate(true))
|
|
2070
|
+
];
|
|
2071
|
+
evaluate(false);
|
|
2072
|
+
return {
|
|
2073
|
+
wrapper,
|
|
2074
|
+
detach: () => {
|
|
2075
|
+
offs.forEach((off) => off());
|
|
2076
|
+
inFlight?.cancel();
|
|
2077
|
+
inFlight = null;
|
|
2078
|
+
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
2079
|
+
},
|
|
2080
|
+
syncZ: (z) => {
|
|
2081
|
+
wrapper.style.zIndex = String(z);
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// src/core/features/scrim-stages.ts
|
|
2087
|
+
var matchesId = (def, activeId) => {
|
|
2088
|
+
if (def.for === void 0) return false;
|
|
2089
|
+
return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
|
|
2090
|
+
};
|
|
2091
|
+
var matchesRange = (def, progress) => {
|
|
2092
|
+
if (!def.forRange) return false;
|
|
2093
|
+
const [from, to] = def.forRange;
|
|
2094
|
+
return progress >= from && progress <= to;
|
|
2095
|
+
};
|
|
2096
|
+
function installScrimStages(deps, opts) {
|
|
2097
|
+
const doc = deps.host.ownerDocument;
|
|
2098
|
+
const entries = opts.stages.map((def) => {
|
|
2099
|
+
const wrapper = doc.createElement("div");
|
|
2100
|
+
wrapper.className = "bs-scrim-stage";
|
|
2101
|
+
const ws = wrapper.style;
|
|
2102
|
+
ws.position = "absolute";
|
|
2103
|
+
applyOverlayPosition(
|
|
2104
|
+
ws,
|
|
2105
|
+
deps.mode,
|
|
2106
|
+
def.position ?? opts.position ?? "center",
|
|
2107
|
+
def.inset ?? opts.inset ?? "16px"
|
|
2108
|
+
);
|
|
2109
|
+
ws.visibility = "hidden";
|
|
2110
|
+
ws.pointerEvents = "none";
|
|
2111
|
+
wrapper.appendChild(def.element);
|
|
2112
|
+
deps.host.appendChild(wrapper);
|
|
2113
|
+
return { def, wrapper, inFlight: null };
|
|
2114
|
+
});
|
|
2115
|
+
let active = null;
|
|
2116
|
+
const resolveActive = () => {
|
|
2117
|
+
const state = deps.getState();
|
|
2118
|
+
if (state.size === 0) {
|
|
2119
|
+
return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
|
|
2120
|
+
}
|
|
2121
|
+
return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
|
|
2122
|
+
};
|
|
2123
|
+
const hide = (entry, animate) => {
|
|
2124
|
+
entry.inFlight?.cancel();
|
|
2125
|
+
entry.wrapper.style.pointerEvents = "none";
|
|
2126
|
+
if (!animate) {
|
|
2127
|
+
entry.inFlight = null;
|
|
2128
|
+
entry.wrapper.style.visibility = "hidden";
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
const handle = runAnchorTransition(
|
|
2132
|
+
entry.def.element,
|
|
2133
|
+
entry.def.animation ?? opts.animation,
|
|
2134
|
+
"exit"
|
|
2135
|
+
);
|
|
2136
|
+
entry.inFlight = handle;
|
|
2137
|
+
void handle.finished.then(() => {
|
|
2138
|
+
if (entry.inFlight === handle && active !== entry) {
|
|
2139
|
+
entry.wrapper.style.visibility = "hidden";
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
};
|
|
2143
|
+
const show = (entry, animate) => {
|
|
2144
|
+
entry.inFlight?.cancel();
|
|
2145
|
+
entry.inFlight = null;
|
|
2146
|
+
entry.wrapper.style.visibility = "";
|
|
2147
|
+
entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
|
|
2148
|
+
if (animate) {
|
|
2149
|
+
entry.inFlight = runAnchorTransition(
|
|
2150
|
+
entry.def.element,
|
|
2151
|
+
entry.def.animation ?? opts.animation,
|
|
2152
|
+
"enter"
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
const update = (animate) => {
|
|
2157
|
+
if (deps.isDestroyed()) return;
|
|
2158
|
+
const next = resolveActive();
|
|
2159
|
+
if (next === active) return;
|
|
2160
|
+
const prev = active;
|
|
2161
|
+
active = next;
|
|
2162
|
+
if (prev) hide(prev, animate);
|
|
2163
|
+
if (next) show(next, animate);
|
|
2164
|
+
};
|
|
2165
|
+
const offs = [
|
|
2166
|
+
deps.on("snap", () => update(true)),
|
|
2167
|
+
deps.on("open", () => update(true)),
|
|
2168
|
+
deps.on("close", () => update(true))
|
|
2169
|
+
];
|
|
2170
|
+
if (entries.some((e) => e.def.forRange)) {
|
|
2171
|
+
offs.push(deps.on("progress", () => update(true)));
|
|
2172
|
+
}
|
|
2173
|
+
update(false);
|
|
2174
|
+
return () => {
|
|
2175
|
+
offs.forEach((off) => off());
|
|
2176
|
+
for (const entry of entries) {
|
|
2177
|
+
entry.inFlight?.cancel();
|
|
2178
|
+
entry.inFlight = null;
|
|
2179
|
+
if (entry.wrapper.parentNode) {
|
|
2180
|
+
entry.wrapper.parentNode.removeChild(entry.wrapper);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
active = null;
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2037
2187
|
// src/core/primitives/engine-options.ts
|
|
2038
2188
|
var DEFAULT_FLICK_VELOCITY = 0.65;
|
|
2039
2189
|
var DEFAULT_DRAG_THRESHOLD = 18;
|
|
@@ -2058,9 +2208,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2058
2208
|
scrimMode: opts.scrimMode,
|
|
2059
2209
|
scrimColor: opts.scrimColor,
|
|
2060
2210
|
scrimBlur: opts.scrimBlur,
|
|
2061
|
-
// Auto-promote pointer-events when tap-to-close is on so the click
|
|
2062
|
-
// listener actually receives events. Caller can still force `false`
|
|
2063
|
-
// explicitly to opt out — `??` only kicks in for `undefined`.
|
|
2064
2211
|
scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
|
|
2065
2212
|
scrimTapToClose: opts.scrimTapToClose,
|
|
2066
2213
|
scrimPreset: opts.scrimPreset,
|
|
@@ -2081,10 +2228,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2081
2228
|
closeOnEscape: opts.closeOnEscape,
|
|
2082
2229
|
lockBodyScroll: opts.lockBodyScroll,
|
|
2083
2230
|
inertSiblings: opts.inertSiblings,
|
|
2084
|
-
// Folded in from `extras` so the lifecycle bag is fully assembled at
|
|
2085
|
-
// the call site — engine no longer needs a spread+override at
|
|
2086
|
-
// controller construction. When `extras` is omitted, the field stays
|
|
2087
|
-
// undefined and the controller uses its own default predicate.
|
|
2088
2231
|
shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
|
|
2089
2232
|
}
|
|
2090
2233
|
};
|
|
@@ -2119,6 +2262,7 @@ var BottomSheetEngine = class {
|
|
|
2119
2262
|
__publicField(this, "scrollContainer");
|
|
2120
2263
|
__publicField(this, "backdrop");
|
|
2121
2264
|
__publicField(this, "screenComponent");
|
|
2265
|
+
__publicField(this, "scrimParent", null);
|
|
2122
2266
|
__publicField(this, "mode");
|
|
2123
2267
|
__publicField(this, "flickVelocity");
|
|
2124
2268
|
__publicField(this, "dragThreshold");
|
|
@@ -2129,40 +2273,22 @@ var BottomSheetEngine = class {
|
|
|
2129
2273
|
__publicField(this, "lifecycle");
|
|
2130
2274
|
__publicField(this, "closeOnBack");
|
|
2131
2275
|
__publicField(this, "routedTo");
|
|
2132
|
-
// Engine retains `snapPointsRaw` for the "find a closed-id" query in
|
|
2133
|
-
// close() (raw input, not resolved geometry). Resolver owns geometry.
|
|
2134
2276
|
__publicField(this, "snapPointsRaw");
|
|
2135
2277
|
__publicField(this, "snaps");
|
|
2136
2278
|
__publicField(this, "activeId");
|
|
2137
2279
|
__publicField(this, "size", 0);
|
|
2138
|
-
// Constructed in attach(); a plugin's install() could read `state` before
|
|
2139
|
-
// attach completes if the constructor throws partway through. State getter
|
|
2140
|
-
// null-guards via `this.gesture?.isDragging ?? false`.
|
|
2141
2280
|
__publicField(this, "gesture");
|
|
2142
2281
|
__publicField(this, "rootEl", null);
|
|
2143
2282
|
__publicField(this, "destroyed", false);
|
|
2144
|
-
// In-flight ViewTransition handle. A second snapTo while a VT is in flight
|
|
2145
|
-
// would otherwise let Chromium reject the second's promise and flicker —
|
|
2146
|
-
// newCycle() calls skipTransition() to abort cleanly.
|
|
2147
2283
|
__publicField(this, "currentViewTransition", null);
|
|
2148
|
-
// Each cycle captures the signal at entry; when a newer cycle aborts the
|
|
2149
|
-
// controller, the captured signal flips to `aborted=true` and post-await
|
|
2150
|
-
// guards short-circuit (prevents stale snap/open/close emits).
|
|
2151
2284
|
__publicField(this, "currentAbort", new AbortController());
|
|
2152
|
-
// Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
|
|
2153
|
-
// visually distinguishable; gating CSSOM writes drops 30-50% of writes
|
|
2154
|
-
// during the spring's settle tail.
|
|
2155
2285
|
__publicField(this, "sizeWriteSentinel", new WriteSentinel());
|
|
2156
2286
|
__publicField(this, "progressWriteSentinel", new WriteSentinel());
|
|
2157
2287
|
__publicField(this, "isTopSheet", true);
|
|
2158
|
-
// Pooled payload — identity documented in SheetEventMap.progress JSDoc;
|
|
2159
|
-
// consumers must not retain across frames.
|
|
2160
2288
|
__publicField(this, "progressPayload", {
|
|
2161
2289
|
value: 0,
|
|
2162
2290
|
size: 0
|
|
2163
2291
|
});
|
|
2164
|
-
// Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
|
|
2165
|
-
// Inner `range` object identity is also stable (mutated in place).
|
|
2166
2292
|
__publicField(this, "dragContextBuf", {
|
|
2167
2293
|
size: 0,
|
|
2168
2294
|
maxAxisSize: 0,
|
|
@@ -2173,13 +2299,13 @@ var BottomSheetEngine = class {
|
|
|
2173
2299
|
__publicField(this, "persistKey");
|
|
2174
2300
|
__publicField(this, "linkedSheets", []);
|
|
2175
2301
|
__publicField(this, "scrollCache");
|
|
2176
|
-
// Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
|
|
2177
|
-
// tear down first before the infrastructure they depend on. Toggle-semantic
|
|
2178
|
-
// listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
|
|
2179
|
-
// fields on their controllers.
|
|
2180
2302
|
__publicField(this, "teardowns", new TeardownStack());
|
|
2181
|
-
|
|
2182
|
-
|
|
2303
|
+
__publicField(this, "anchors", []);
|
|
2304
|
+
__publicField(this, "anchorHost", null);
|
|
2305
|
+
__publicField(this, "stackZ", 100);
|
|
2306
|
+
__publicField(this, "detachScrimStages", null);
|
|
2307
|
+
__publicField(this, "stackEffectEnabled", false);
|
|
2308
|
+
__publicField(this, "stackEffectPrimed", false);
|
|
2183
2309
|
__publicField(this, "transformTemplate");
|
|
2184
2310
|
const resolved = resolveEngineOptions(opts, {
|
|
2185
2311
|
shouldApplyInertSiblings: () => {
|
|
@@ -2194,7 +2320,8 @@ var BottomSheetEngine = class {
|
|
|
2194
2320
|
this.handle = opts.handle ?? opts.element;
|
|
2195
2321
|
this.scrollContainer = opts.scrollContainer;
|
|
2196
2322
|
this.backdrop = opts.backdrop;
|
|
2197
|
-
this.screenComponent = opts.screenComponent;
|
|
2323
|
+
this.screenComponent = opts.scrim ?? opts.screenComponent;
|
|
2324
|
+
this.scrimParent = this.screenComponent?.parentElement ?? null;
|
|
2198
2325
|
this.mode = resolved.mode;
|
|
2199
2326
|
this.transformTemplate = buildTransformTemplate(this.mode);
|
|
2200
2327
|
this.aria = new AriaSliderWriter(this.handle, this.mode);
|
|
@@ -2212,6 +2339,9 @@ var BottomSheetEngine = class {
|
|
|
2212
2339
|
getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
|
|
2213
2340
|
snapTo: (id) => {
|
|
2214
2341
|
void this.snapTo(id);
|
|
2342
|
+
},
|
|
2343
|
+
close: () => {
|
|
2344
|
+
void this.close();
|
|
2215
2345
|
}
|
|
2216
2346
|
},
|
|
2217
2347
|
resolved.scrim
|
|
@@ -2222,8 +2352,6 @@ var BottomSheetEngine = class {
|
|
|
2222
2352
|
getRootEl: () => this.rootEl,
|
|
2223
2353
|
applySize: (size) => this.applySize(size),
|
|
2224
2354
|
getSize: () => this.size,
|
|
2225
|
-
// Optional chain protects synthetic test paths that drive animateTo
|
|
2226
|
-
// before attach() (gesture is constructed there).
|
|
2227
2355
|
isDragging: () => this.gesture?.isDragging ?? false
|
|
2228
2356
|
},
|
|
2229
2357
|
resolved.animation
|
|
@@ -2237,6 +2365,7 @@ var BottomSheetEngine = class {
|
|
|
2237
2365
|
);
|
|
2238
2366
|
this.closeOnBack = resolved.closeOnBack;
|
|
2239
2367
|
this.routedTo = opts.routedTo;
|
|
2368
|
+
this.stackEffectEnabled = opts.stackEffect ?? false;
|
|
2240
2369
|
this.snapPointsRaw = opts.snapPoints;
|
|
2241
2370
|
this.persistKey = opts.persistKey;
|
|
2242
2371
|
if (opts.linkedSheets) {
|
|
@@ -2249,7 +2378,7 @@ var BottomSheetEngine = class {
|
|
|
2249
2378
|
this.snapPointsRaw,
|
|
2250
2379
|
resolved.initialAllowed,
|
|
2251
2380
|
this.mode,
|
|
2252
|
-
() => this.handle.offsetHeight,
|
|
2381
|
+
() => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
|
|
2253
2382
|
(maxAxisSize) => {
|
|
2254
2383
|
this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
|
|
2255
2384
|
}
|
|
@@ -2328,21 +2457,6 @@ var BottomSheetEngine = class {
|
|
|
2328
2457
|
on(event, fn) {
|
|
2329
2458
|
return this.bus.on(event, fn);
|
|
2330
2459
|
}
|
|
2331
|
-
/**
|
|
2332
|
-
* Register a plugin. `install` runs synchronously; if it returns a function,
|
|
2333
|
-
* it's captured for teardown during `destroy()`. No-op if already destroyed
|
|
2334
|
-
* — installing into a torn-down engine would leak listeners.
|
|
2335
|
-
*
|
|
2336
|
-
* Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
|
|
2337
|
-
* so a buggy plugin can't take the engine down with it: the engine stays
|
|
2338
|
-
* usable, sibling plugins still install, and the error surfaces in dev tools
|
|
2339
|
-
* on the next tick instead of being swallowed. Mirrors the per-fn isolation
|
|
2340
|
-
* model used for teardowns.
|
|
2341
|
-
*
|
|
2342
|
-
* Plugins that accept the second `scope` argument get transactional cleanup:
|
|
2343
|
-
* push partial-install cleanups via `scope.add(fn)` and the engine drains
|
|
2344
|
-
* them on throw, OR merges them into destroy-time teardowns on success.
|
|
2345
|
-
*/
|
|
2346
2460
|
use(plugin) {
|
|
2347
2461
|
if (this.destroyed) {
|
|
2348
2462
|
console.warn(
|
|
@@ -2352,9 +2466,6 @@ var BottomSheetEngine = class {
|
|
|
2352
2466
|
}
|
|
2353
2467
|
const scoped = [];
|
|
2354
2468
|
const scope = {
|
|
2355
|
-
// typeof check — not just truthiness — so a JS-only plugin pushing a
|
|
2356
|
-
// bogus value is silently rejected instead of queueing a confusing
|
|
2357
|
-
// TypeError on destroy() drain.
|
|
2358
2469
|
add: (fn) => {
|
|
2359
2470
|
if (typeof fn === "function") scoped.push(fn);
|
|
2360
2471
|
}
|
|
@@ -2383,36 +2494,13 @@ var BottomSheetEngine = class {
|
|
|
2383
2494
|
}
|
|
2384
2495
|
return this;
|
|
2385
2496
|
}
|
|
2386
|
-
/**
|
|
2387
|
-
* Replace the linked-sheets list at runtime. Use when peer engines aren't
|
|
2388
|
-
* available at construction time (mounted later in the tree). The wiring
|
|
2389
|
-
* to the `open` event is independent of this list — it reads `linkedSheets`
|
|
2390
|
-
* lazily on emit, so this setter is a simple field swap.
|
|
2391
|
-
*/
|
|
2392
2497
|
setLinkedSheets(sheets) {
|
|
2393
2498
|
if (this.destroyed) return;
|
|
2394
2499
|
this.linkedSheets = sheets;
|
|
2395
2500
|
}
|
|
2396
|
-
/** Read-only view of the current allow-list, for cross-sheet target negotiation. */
|
|
2397
2501
|
getAllowedIds() {
|
|
2398
2502
|
return this.snaps.getAllowedIds().slice();
|
|
2399
2503
|
}
|
|
2400
|
-
/**
|
|
2401
|
-
* Animate to a snap point by id. Resolves when the animation completes.
|
|
2402
|
-
*
|
|
2403
|
-
* Two call shapes:
|
|
2404
|
-
* ```ts
|
|
2405
|
-
* engine.snapTo("full"); // default velocity
|
|
2406
|
-
* engine.snapTo("full", 0.5); // px/ms initial velocity
|
|
2407
|
-
* engine.snapTo("full", { velocity: 0.5, signal }); // abortable
|
|
2408
|
-
* ```
|
|
2409
|
-
*
|
|
2410
|
-
* When `opts.signal` is provided, calling `controller.abort()` cancels the
|
|
2411
|
-
* in-flight tween/spring and the returned promise resolves without firing
|
|
2412
|
-
* `snap`. The captured `AbortSignal` is checked after each await so external
|
|
2413
|
-
* aborts ride the same cycle invariant as internal ones (resize, destroy,
|
|
2414
|
-
* concurrent snapTo).
|
|
2415
|
-
*/
|
|
2416
2504
|
async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
|
|
2417
2505
|
if (this.destroyed) return;
|
|
2418
2506
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2471,16 +2559,6 @@ var BottomSheetEngine = class {
|
|
|
2471
2559
|
this.handleClose();
|
|
2472
2560
|
}
|
|
2473
2561
|
}
|
|
2474
|
-
/**
|
|
2475
|
-
* Drive the sheet to an arbitrary px size, bypassing snap matching.
|
|
2476
|
-
* Useful for preview scrubs, animation chaining, or programmatic gestures.
|
|
2477
|
-
* Animates with the configured spring/tween. Resolves when the animation
|
|
2478
|
-
* completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
|
|
2479
|
-
*
|
|
2480
|
-
* Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
|
|
2481
|
-
* nor `snap` (the sheet isn't settling to a known snap). `progress` events
|
|
2482
|
-
* still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
|
|
2483
|
-
*/
|
|
2484
2562
|
async dragTo(targetSize, velocityOrOpts = 0) {
|
|
2485
2563
|
if (this.destroyed) return;
|
|
2486
2564
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2510,28 +2588,13 @@ var BottomSheetEngine = class {
|
|
|
2510
2588
|
}
|
|
2511
2589
|
setAllowed(ids, snap) {
|
|
2512
2590
|
if (this.destroyed) return;
|
|
2513
|
-
const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
|
|
2514
|
-
if (!willSnap) {
|
|
2515
|
-
this.animation.cancel();
|
|
2516
|
-
this.newCycle();
|
|
2517
|
-
}
|
|
2518
2591
|
this.snaps.setAllowedIds(ids);
|
|
2519
2592
|
this.updateAriaSlider();
|
|
2520
2593
|
if (snap && ids.includes(snap)) {
|
|
2521
|
-
void this.snapTo(
|
|
2522
|
-
snap,
|
|
2523
|
-
0,
|
|
2524
|
-
/* internal */
|
|
2525
|
-
true
|
|
2526
|
-
);
|
|
2594
|
+
void this.snapTo(snap, 0, true);
|
|
2527
2595
|
} else if (!ids.includes(this.activeId)) {
|
|
2528
2596
|
const fallback = ids[0];
|
|
2529
|
-
if (fallback) void this.snapTo(
|
|
2530
|
-
fallback,
|
|
2531
|
-
0,
|
|
2532
|
-
/* internal */
|
|
2533
|
-
true
|
|
2534
|
-
);
|
|
2597
|
+
if (fallback) void this.snapTo(fallback, 0, true);
|
|
2535
2598
|
}
|
|
2536
2599
|
}
|
|
2537
2600
|
setBackdropRange(range) {
|
|
@@ -2549,21 +2612,6 @@ var BottomSheetEngine = class {
|
|
|
2549
2612
|
setScrimInteractive(interactive) {
|
|
2550
2613
|
this.scrim.setScrimInteractive(interactive);
|
|
2551
2614
|
}
|
|
2552
|
-
/**
|
|
2553
|
-
* Batch-update multiple scrim fields in one call. Apply order is:
|
|
2554
|
-
* 1. `preset` — fills baseline color/blur/range/interactive.
|
|
2555
|
-
* 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
|
|
2556
|
-
* 3. `enabled` — flips the opacity contribution on/off.
|
|
2557
|
-
* 4. `tapToClose` — installs/tears the click listener.
|
|
2558
|
-
* 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
|
|
2559
|
-
* 6. ONE `applySize` so the per-frame opacity stays consistent.
|
|
2560
|
-
*
|
|
2561
|
-
* Performance: collecting changes here (vs calling individual setters)
|
|
2562
|
-
* avoids repeated `applySize` calls during a multi-field swap. Setters
|
|
2563
|
-
* called here suppress their own internal `applySize` either by passing
|
|
2564
|
-
* through fast-paths (mode/enabled no-op when value unchanged) or by
|
|
2565
|
-
* being visual-only (color/blur/interactive don't influence opacity).
|
|
2566
|
-
*/
|
|
2567
2615
|
setScrim(opts) {
|
|
2568
2616
|
this.scrim.setScrim(opts, () => this.applySize(this.size));
|
|
2569
2617
|
}
|
|
@@ -2579,11 +2627,79 @@ var BottomSheetEngine = class {
|
|
|
2579
2627
|
setScrimOverlay(opts) {
|
|
2580
2628
|
return this.scrim.setScrimOverlay(opts);
|
|
2581
2629
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2630
|
+
addAnchor(opts) {
|
|
2631
|
+
if (this.destroyed || typeof document === "undefined") return () => {
|
|
2632
|
+
};
|
|
2633
|
+
if (!this.anchorHost) {
|
|
2634
|
+
this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
|
|
2635
|
+
this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
|
|
2636
|
+
this.anchorHost.style.setProperty(
|
|
2637
|
+
"--bs-progress",
|
|
2638
|
+
String(this.computeProgress(this.size))
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
const handle = attachAnchor(
|
|
2642
|
+
{
|
|
2643
|
+
mode: this.mode,
|
|
2644
|
+
host: this.anchorHost,
|
|
2645
|
+
getState: () => ({
|
|
2646
|
+
activeId: this.activeId,
|
|
2647
|
+
size: this.size,
|
|
2648
|
+
progress: this.computeProgress(this.size)
|
|
2649
|
+
}),
|
|
2650
|
+
on: (event, fn) => this.on(event, fn),
|
|
2651
|
+
isDestroyed: () => this.destroyed
|
|
2652
|
+
},
|
|
2653
|
+
opts
|
|
2654
|
+
);
|
|
2655
|
+
handle.syncZ(this.stackZ + 1);
|
|
2656
|
+
this.anchors.push(handle);
|
|
2657
|
+
const detach = () => {
|
|
2658
|
+
const idx = this.anchors.indexOf(handle);
|
|
2659
|
+
if (idx === -1) return;
|
|
2660
|
+
this.anchors.splice(idx, 1);
|
|
2661
|
+
handle.detach();
|
|
2662
|
+
};
|
|
2663
|
+
this.teardowns.add(detach);
|
|
2664
|
+
return detach;
|
|
2665
|
+
}
|
|
2666
|
+
setScrimStages(opts) {
|
|
2667
|
+
if (this.destroyed || typeof document === "undefined") return () => {
|
|
2668
|
+
};
|
|
2669
|
+
this.detachScrimStages?.();
|
|
2670
|
+
this.detachScrimStages = null;
|
|
2671
|
+
if (!opts) return () => {
|
|
2672
|
+
};
|
|
2673
|
+
const host = this.screenComponent?.parentElement;
|
|
2674
|
+
if (!host) {
|
|
2675
|
+
console.warn(
|
|
2676
|
+
"[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
|
|
2677
|
+
);
|
|
2678
|
+
return () => {
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
const detachInstalled = installScrimStages(
|
|
2682
|
+
{
|
|
2683
|
+
mode: this.mode,
|
|
2684
|
+
host,
|
|
2685
|
+
getState: () => ({
|
|
2686
|
+
activeId: this.activeId,
|
|
2687
|
+
size: this.size,
|
|
2688
|
+
progress: this.computeProgress(this.size)
|
|
2689
|
+
}),
|
|
2690
|
+
on: (event, fn) => this.on(event, fn),
|
|
2691
|
+
isDestroyed: () => this.destroyed
|
|
2692
|
+
},
|
|
2693
|
+
opts
|
|
2694
|
+
);
|
|
2695
|
+
const detach = () => {
|
|
2696
|
+
if (this.detachScrimStages === detach) this.detachScrimStages = null;
|
|
2697
|
+
detachInstalled();
|
|
2698
|
+
};
|
|
2699
|
+
this.detachScrimStages = detach;
|
|
2700
|
+
this.teardowns.add(() => this.detachScrimStages?.());
|
|
2701
|
+
return detach;
|
|
2702
|
+
}
|
|
2587
2703
|
getScrimState() {
|
|
2588
2704
|
return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
|
|
2589
2705
|
}
|
|
@@ -2629,24 +2745,11 @@ var BottomSheetEngine = class {
|
|
|
2629
2745
|
emit(event, payload) {
|
|
2630
2746
|
this.bus.emit(event, payload);
|
|
2631
2747
|
}
|
|
2632
|
-
/**
|
|
2633
|
-
* Abort the current cycle's signal and replace the controller. Returns the
|
|
2634
|
-
* fresh signal so the caller can capture it locally before any await — when
|
|
2635
|
-
* a future newCycle() / destroy aborts our controller, the captured signal
|
|
2636
|
-
* flips to aborted and post-await guards short-circuit.
|
|
2637
|
-
*/
|
|
2638
2748
|
newCycle() {
|
|
2639
2749
|
this.currentAbort.abort();
|
|
2640
2750
|
this.currentAbort = new AbortController();
|
|
2641
2751
|
return this.currentAbort.signal;
|
|
2642
2752
|
}
|
|
2643
|
-
/**
|
|
2644
|
-
* Fire `before-snap` and report whether a listener cancelled it. The
|
|
2645
|
-
* `cancel()` callback exposed to listeners is frozen after the synchronous
|
|
2646
|
-
* emit phase so async callers can't silently miss — they get a console
|
|
2647
|
-
* warning. Returns `true` when cancelled so callers can early-return /
|
|
2648
|
-
* spring-back without re-implementing the cancel/frozen pattern.
|
|
2649
|
-
*/
|
|
2650
2753
|
emitBeforeSnap(target, previousId) {
|
|
2651
2754
|
let cancelled = false;
|
|
2652
2755
|
let frozen = false;
|
|
@@ -2680,59 +2783,54 @@ var BottomSheetEngine = class {
|
|
|
2680
2783
|
this.snaps.setRaw(this.snapPointsRaw);
|
|
2681
2784
|
this.scrim.invalidateOpacityCache();
|
|
2682
2785
|
}
|
|
2683
|
-
/**
|
|
2684
|
-
* Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
|
|
2685
|
-
* Engine wrapper kept so existing call sites don't all need updating;
|
|
2686
|
-
* resolver-level memoization survives across method invocations.
|
|
2687
|
-
*/
|
|
2688
2786
|
getAllowedRange() {
|
|
2689
2787
|
return this.snaps.getAllowedRange();
|
|
2690
2788
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
* without it screen readers can't announce drag/snap progress to keyboard
|
|
2697
|
-
* + AT users (WCAG 4.1.2 Name, Role, Value).
|
|
2698
|
-
*
|
|
2699
|
-
* Engine does NOT set `role="slider"` itself — that's the adapter's or
|
|
2700
|
-
* consumer markup's responsibility. We only decorate with dynamic values.
|
|
2701
|
-
*
|
|
2702
|
-
* Empty allowed-list is malformed input (settable via `setAllowed([])`);
|
|
2703
|
-
* short-circuit to remove ARIA value attributes so screen readers see a
|
|
2704
|
-
* no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
|
|
2705
|
-
* requires `valuemax > valuemin` for `role="slider"`).
|
|
2706
|
-
*/
|
|
2789
|
+
allowedIdsBySize() {
|
|
2790
|
+
return this.snaps.getAllowedIds().slice().sort(
|
|
2791
|
+
(a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2707
2794
|
updateAriaSlider() {
|
|
2708
|
-
this.aria.setValue(this.
|
|
2795
|
+
this.aria.setValue(this.allowedIdsBySize(), this.activeId);
|
|
2709
2796
|
}
|
|
2710
2797
|
registerInStack() {
|
|
2711
2798
|
this.teardowns.add(sheetStack.push({
|
|
2712
2799
|
id: this.id,
|
|
2713
2800
|
setZIndex: (z) => {
|
|
2801
|
+
this.stackZ = z;
|
|
2714
2802
|
this.element.style.zIndex = String(z);
|
|
2715
2803
|
if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
|
|
2804
|
+
for (const anchor of this.anchors) anchor.syncZ(z + 1);
|
|
2716
2805
|
},
|
|
2717
2806
|
setIsTop: (isTop) => {
|
|
2718
2807
|
this.isTopSheet = isTop;
|
|
2719
2808
|
if (this.backdrop) {
|
|
2720
2809
|
this.backdrop.style.display = isTop ? "" : "none";
|
|
2721
2810
|
}
|
|
2722
|
-
}
|
|
2811
|
+
},
|
|
2812
|
+
isOpen: () => this.size > 0,
|
|
2813
|
+
setDepth: (depth) => this.applyStackDepth(depth)
|
|
2723
2814
|
}));
|
|
2724
2815
|
}
|
|
2816
|
+
applyStackDepth(depth) {
|
|
2817
|
+
if (!this.stackEffectEnabled) return;
|
|
2818
|
+
this.element.setAttribute("data-stack-depth", String(depth));
|
|
2819
|
+
if (!this.stackEffectPrimed) {
|
|
2820
|
+
this.stackEffectPrimed = true;
|
|
2821
|
+
const isVertical = this.mode === "bottom" || this.mode === "top";
|
|
2822
|
+
this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
|
|
2823
|
+
this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
|
|
2824
|
+
}
|
|
2825
|
+
const scale = Math.max(1 - depth * 0.04, 0.86);
|
|
2826
|
+
this.element.style.scale = scale === 1 ? "" : String(scale);
|
|
2827
|
+
}
|
|
2725
2828
|
attach() {
|
|
2726
2829
|
this.gesture = new GestureController({
|
|
2727
2830
|
handle: this.handle,
|
|
2728
2831
|
element: this.element,
|
|
2729
2832
|
mode: this.mode,
|
|
2730
2833
|
getRoot: () => this.rootEl,
|
|
2731
|
-
// Single coalesced reader — narrows the DI surface and removes 3 getter
|
|
2732
|
-
// dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
|
|
2733
|
-
// (this.dragContextBuf) — see field JSDoc for the retention contract.
|
|
2734
|
-
// Inner `range` object is mutated in place too so callers caching its
|
|
2735
|
-
// identity between frames stay valid.
|
|
2736
2834
|
getDragContext: () => {
|
|
2737
2835
|
const buf = this.dragContextBuf;
|
|
2738
2836
|
const r = this.getAllowedRange();
|
|
@@ -2786,7 +2884,7 @@ var BottomSheetEngine = class {
|
|
|
2786
2884
|
container: this.scrollContainer,
|
|
2787
2885
|
isDragging: () => this.gesture?.isDragging ?? false,
|
|
2788
2886
|
isAnimating: () => this.animation.isAnimating,
|
|
2789
|
-
getAllowedIds: () => this.
|
|
2887
|
+
getAllowedIds: () => this.allowedIdsBySize(),
|
|
2790
2888
|
getActiveId: () => this.activeId,
|
|
2791
2889
|
snapTo: (id) => {
|
|
2792
2890
|
void this.snapTo(id);
|
|
@@ -2815,11 +2913,13 @@ var BottomSheetEngine = class {
|
|
|
2815
2913
|
},
|
|
2816
2914
|
newCycle: () => {
|
|
2817
2915
|
this.newCycle();
|
|
2818
|
-
}
|
|
2916
|
+
},
|
|
2917
|
+
isAnimating: () => this.animation.isAnimating,
|
|
2918
|
+
resyncAfterCancel: () => this.resyncAfterResize()
|
|
2819
2919
|
}));
|
|
2820
2920
|
if (this.lifecycle.closeOnEscape) {
|
|
2821
2921
|
const onKey = (e) => {
|
|
2822
|
-
if (this.destroyed) return;
|
|
2922
|
+
if (this.destroyed || e.defaultPrevented) return;
|
|
2823
2923
|
if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
|
|
2824
2924
|
void this.close();
|
|
2825
2925
|
}
|
|
@@ -2851,7 +2951,7 @@ var BottomSheetEngine = class {
|
|
|
2851
2951
|
handle: this.handle,
|
|
2852
2952
|
mode: this.mode,
|
|
2853
2953
|
isDestroyed: () => this.destroyed,
|
|
2854
|
-
getAllowedIds: () => this.
|
|
2954
|
+
getAllowedIds: () => this.allowedIdsBySize(),
|
|
2855
2955
|
getActiveId: () => this.activeId,
|
|
2856
2956
|
snapTo: (id) => {
|
|
2857
2957
|
void this.snapTo(id);
|
|
@@ -2872,6 +2972,7 @@ var BottomSheetEngine = class {
|
|
|
2872
2972
|
});
|
|
2873
2973
|
if (!target) return;
|
|
2874
2974
|
const previousId = this.activeId;
|
|
2975
|
+
if (target.id === previousId && target.size === this.size) return;
|
|
2875
2976
|
if (this.emitBeforeSnap(target, previousId)) {
|
|
2876
2977
|
const restore = this.snaps.findById(previousId);
|
|
2877
2978
|
if (restore) {
|
|
@@ -2880,6 +2981,7 @@ var BottomSheetEngine = class {
|
|
|
2880
2981
|
return;
|
|
2881
2982
|
}
|
|
2882
2983
|
const previousSize = this.size;
|
|
2984
|
+
const previousSnapSize = this.snaps.findById(previousId)?.size;
|
|
2883
2985
|
this.scrollCache.cache(previousId, previousSize, target.size);
|
|
2884
2986
|
this.activeId = target.id;
|
|
2885
2987
|
void this.animation.animateTo(target.size, velocity).then(() => {
|
|
@@ -2888,9 +2990,10 @@ var BottomSheetEngine = class {
|
|
|
2888
2990
|
this.updateAriaSlider();
|
|
2889
2991
|
this.emit("snap", { id: target.id, size: target.size });
|
|
2890
2992
|
this.haptic();
|
|
2891
|
-
if (
|
|
2993
|
+
if (previousSnapSize === 0 && target.size > 0) {
|
|
2892
2994
|
this.emit("open", { id: target.id });
|
|
2893
2995
|
this.handleOpen();
|
|
2996
|
+
notifyLinkedSheets(this.linkedSheets, this);
|
|
2894
2997
|
}
|
|
2895
2998
|
if (target.size === 0) {
|
|
2896
2999
|
this.emit("close", void 0);
|
|
@@ -2898,7 +3001,6 @@ var BottomSheetEngine = class {
|
|
|
2898
3001
|
}
|
|
2899
3002
|
});
|
|
2900
3003
|
}
|
|
2901
|
-
/** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
|
|
2902
3004
|
haptic() {
|
|
2903
3005
|
if (typeof navigator !== "undefined" && "vibrate" in navigator) {
|
|
2904
3006
|
try {
|
|
@@ -2914,6 +3016,12 @@ var BottomSheetEngine = class {
|
|
|
2914
3016
|
style.transform = this.transformTemplate(offset);
|
|
2915
3017
|
if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
|
|
2916
3018
|
style.setProperty("--bs-size", `${size}px`);
|
|
3019
|
+
if (this.scrimParent) {
|
|
3020
|
+
this.scrimParent.style.setProperty("--bs-size", `${size}px`);
|
|
3021
|
+
}
|
|
3022
|
+
if (this.anchorHost && this.anchorHost !== this.scrimParent) {
|
|
3023
|
+
this.anchorHost.style.setProperty("--bs-size", `${size}px`);
|
|
3024
|
+
}
|
|
2917
3025
|
}
|
|
2918
3026
|
const progress = this.computeProgress(size);
|
|
2919
3027
|
const progressChanged = this.progressWriteSentinel.shouldWrite(
|
|
@@ -2922,6 +3030,9 @@ var BottomSheetEngine = class {
|
|
|
2922
3030
|
);
|
|
2923
3031
|
if (progressChanged) {
|
|
2924
3032
|
style.setProperty("--bs-progress", String(progress));
|
|
3033
|
+
if (this.anchorHost) {
|
|
3034
|
+
this.anchorHost.style.setProperty("--bs-progress", String(progress));
|
|
3035
|
+
}
|
|
2925
3036
|
}
|
|
2926
3037
|
this.scrim.applyOpacity(progress, progressChanged);
|
|
2927
3038
|
if (progressChanged && this.bus.listenerCount("progress") > 0) {
|
|
@@ -2936,6 +3047,7 @@ var BottomSheetEngine = class {
|
|
|
2936
3047
|
return Math.min(Math.max((size - min) / (max - min), 0), 1);
|
|
2937
3048
|
}
|
|
2938
3049
|
handleOpen() {
|
|
3050
|
+
sheetStack.promote(this.id);
|
|
2939
3051
|
try {
|
|
2940
3052
|
this.lifecycle.install();
|
|
2941
3053
|
} catch (err) {
|
|
@@ -2949,8 +3061,24 @@ var BottomSheetEngine = class {
|
|
|
2949
3061
|
});
|
|
2950
3062
|
}
|
|
2951
3063
|
}
|
|
3064
|
+
resyncAfterResize() {
|
|
3065
|
+
if (this.destroyed) return;
|
|
3066
|
+
const target = this.snaps.findById(this.activeId);
|
|
3067
|
+
if (!target) return;
|
|
3068
|
+
this.updateAriaSlider();
|
|
3069
|
+
this.emit("snap", { id: target.id, size: target.size });
|
|
3070
|
+
if (target.size > 0 && !this.lifecycle.isInstalled) {
|
|
3071
|
+
this.emit("open", { id: target.id });
|
|
3072
|
+
this.handleOpen();
|
|
3073
|
+
notifyLinkedSheets(this.linkedSheets, this);
|
|
3074
|
+
} else if (target.size === 0 && this.lifecycle.isInstalled) {
|
|
3075
|
+
this.emit("close", void 0);
|
|
3076
|
+
this.handleClose();
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
2952
3079
|
handleClose() {
|
|
2953
3080
|
this.lifecycle.release();
|
|
3081
|
+
sheetStack.update();
|
|
2954
3082
|
}
|
|
2955
3083
|
};
|
|
2956
3084
|
|
|
@@ -3045,8 +3173,6 @@ var OverlayEngine = class {
|
|
|
3045
3173
|
__publicField(this, "exitEasing");
|
|
3046
3174
|
__publicField(this, "backdropOpacity");
|
|
3047
3175
|
__publicField(this, "backdropFilter");
|
|
3048
|
-
// Definite-assignment: written via assignSwipeFromConfig in the
|
|
3049
|
-
// constructor — TS can't follow the helper, mark the assertion explicit.
|
|
3050
3176
|
__publicField(this, "swipeToCloseEnabled");
|
|
3051
3177
|
__publicField(this, "swipeThreshold");
|
|
3052
3178
|
__publicField(this, "swipeVelocityThreshold");
|
|
@@ -3061,18 +3187,16 @@ var OverlayEngine = class {
|
|
|
3061
3187
|
__publicField(this, "closeOnOutsidePointerEnabled");
|
|
3062
3188
|
__publicField(this, "detachOutsidePointer", null);
|
|
3063
3189
|
__publicField(this, "detachSwipe", null);
|
|
3190
|
+
__publicField(this, "respectReducedMotionEnabled");
|
|
3191
|
+
__publicField(this, "isTop", true);
|
|
3192
|
+
__publicField(this, "backPushed", false);
|
|
3193
|
+
__publicField(this, "movedToTarget", false);
|
|
3064
3194
|
__publicField(this, "onOpenCb");
|
|
3065
3195
|
__publicField(this, "onCloseCb");
|
|
3066
3196
|
__publicField(this, "isOpen_", false);
|
|
3067
3197
|
__publicField(this, "isAnimating_", false);
|
|
3068
3198
|
__publicField(this, "destroyed", false);
|
|
3069
|
-
// Tracks whether setOverlayChildren ever populated the panel — lets
|
|
3070
|
-
// clearOverlayChildren skip the DOM mutation when nothing was injected
|
|
3071
|
-
// (so the field also acts as a zero-allocation feature flag).
|
|
3072
3199
|
__publicField(this, "hasInjectedChildren", false);
|
|
3073
|
-
// Bumped on every open/close. awaitTransition captures it and ignores
|
|
3074
|
-
// transitionend/timeout fired by older cycles — prevents cross-cycle
|
|
3075
|
-
// listener bleed during rapid toggling.
|
|
3076
3200
|
__publicField(this, "cycleNonce", 0);
|
|
3077
3201
|
__publicField(this, "bus", createEventBus());
|
|
3078
3202
|
__publicField(this, "detachEscape", null);
|
|
@@ -3103,6 +3227,7 @@ var OverlayEngine = class {
|
|
|
3103
3227
|
this.mountTarget = opts.mountTo;
|
|
3104
3228
|
this.peekRaw = opts.peek;
|
|
3105
3229
|
this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
|
|
3230
|
+
this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
|
|
3106
3231
|
this.onOpenCb = opts.onOpen;
|
|
3107
3232
|
this.onCloseCb = opts.onClose;
|
|
3108
3233
|
if (this.peekRaw !== void 0 && this.edge !== "bottom") {
|
|
@@ -3125,9 +3250,6 @@ var OverlayEngine = class {
|
|
|
3125
3250
|
closeOnEscape: this.closeOnEscape,
|
|
3126
3251
|
lockBodyScroll: this.bodyScrollLockEnabled,
|
|
3127
3252
|
inertSiblings: this.inertSiblingsEnabled,
|
|
3128
|
-
// Overlay-specific guard: only paint body siblings inert when the
|
|
3129
|
-
// overlay element is actually a body descendant. Detached / shadow-
|
|
3130
|
-
// DOM / portal overlays would otherwise lock unrelated trees.
|
|
3131
3253
|
shouldApplyInertSiblings: () => {
|
|
3132
3254
|
const bodyChildren = Array.from(document.body.children);
|
|
3133
3255
|
return bodyChildren.some(
|
|
@@ -3153,15 +3275,17 @@ var OverlayEngine = class {
|
|
|
3153
3275
|
this.emit("before-open", void 0);
|
|
3154
3276
|
this.isOpen_ = true;
|
|
3155
3277
|
this.isAnimating_ = true;
|
|
3278
|
+
sheetStack.promote(this.id);
|
|
3156
3279
|
this.element.removeAttribute("hidden");
|
|
3157
3280
|
this.element.setAttribute("data-state", "entering");
|
|
3158
3281
|
if (this.focusTrapEnabled) {
|
|
3159
3282
|
this.element.setAttribute("aria-modal", "true");
|
|
3160
3283
|
}
|
|
3161
|
-
const
|
|
3284
|
+
const dur = this.effectiveDuration();
|
|
3285
|
+
const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
|
|
3162
3286
|
this.element.style.transition = transitionStr;
|
|
3163
3287
|
if (this.backdrop) {
|
|
3164
|
-
this.backdrop.style.transition = `opacity ${
|
|
3288
|
+
this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
|
|
3165
3289
|
}
|
|
3166
3290
|
const cycle = ++this.cycleNonce;
|
|
3167
3291
|
return new Promise((resolve) => {
|
|
@@ -3173,7 +3297,7 @@ var OverlayEngine = class {
|
|
|
3173
3297
|
this.element.style.opacity = "1";
|
|
3174
3298
|
if (this.backdrop) {
|
|
3175
3299
|
this.backdrop.style.opacity = String(this.backdropOpacity);
|
|
3176
|
-
this.backdrop.style.pointerEvents = "auto";
|
|
3300
|
+
this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
|
|
3177
3301
|
if (this.backdropFilter) {
|
|
3178
3302
|
this.backdrop.style.backdropFilter = this.backdropFilter;
|
|
3179
3303
|
this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
|
|
@@ -3209,7 +3333,10 @@ var OverlayEngine = class {
|
|
|
3209
3333
|
});
|
|
3210
3334
|
return resolve();
|
|
3211
3335
|
}
|
|
3212
|
-
this.awaitTransition(
|
|
3336
|
+
this.awaitTransition(
|
|
3337
|
+
cycle,
|
|
3338
|
+
this.enterAnimation === "fade" ? "opacity" : "transform"
|
|
3339
|
+
).then(() => {
|
|
3213
3340
|
if (this.destroyed || cycle !== this.cycleNonce) return resolve();
|
|
3214
3341
|
this.isAnimating_ = false;
|
|
3215
3342
|
this.element.setAttribute("data-state", "open");
|
|
@@ -3226,11 +3353,14 @@ var OverlayEngine = class {
|
|
|
3226
3353
|
this.emit("before-close", void 0);
|
|
3227
3354
|
this.isOpen_ = false;
|
|
3228
3355
|
this.isAnimating_ = true;
|
|
3356
|
+
sheetStack.update();
|
|
3357
|
+
this.popBackMarker(reason);
|
|
3229
3358
|
this.element.setAttribute("data-state", "exiting");
|
|
3230
|
-
const
|
|
3359
|
+
const dur = this.effectiveDuration();
|
|
3360
|
+
const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
|
|
3231
3361
|
this.element.style.transition = transitionStr;
|
|
3232
3362
|
if (this.backdrop) {
|
|
3233
|
-
this.backdrop.style.transition = `opacity ${
|
|
3363
|
+
this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
|
|
3234
3364
|
}
|
|
3235
3365
|
const cycle = ++this.cycleNonce;
|
|
3236
3366
|
return new Promise((resolve) => {
|
|
@@ -3241,7 +3371,7 @@ var OverlayEngine = class {
|
|
|
3241
3371
|
this.exitAnimation,
|
|
3242
3372
|
this.peekPx
|
|
3243
3373
|
);
|
|
3244
|
-
this.element.style.opacity = "0";
|
|
3374
|
+
this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
|
|
3245
3375
|
if (this.backdrop) {
|
|
3246
3376
|
this.backdrop.style.opacity = "0";
|
|
3247
3377
|
this.backdrop.style.pointerEvents = "none";
|
|
@@ -3251,7 +3381,10 @@ var OverlayEngine = class {
|
|
|
3251
3381
|
}
|
|
3252
3382
|
}
|
|
3253
3383
|
this.releaseInteractiveListeners();
|
|
3254
|
-
this.awaitTransition(
|
|
3384
|
+
this.awaitTransition(
|
|
3385
|
+
cycle,
|
|
3386
|
+
this.exitAnimation === "fade" ? "opacity" : "transform"
|
|
3387
|
+
).then(() => {
|
|
3255
3388
|
if (this.destroyed || cycle !== this.cycleNonce) return resolve();
|
|
3256
3389
|
this.isAnimating_ = false;
|
|
3257
3390
|
if (this.peekPx === void 0) {
|
|
@@ -3279,14 +3412,12 @@ var OverlayEngine = class {
|
|
|
3279
3412
|
this.releaseInteractiveListeners();
|
|
3280
3413
|
this.releaseStackEntry?.();
|
|
3281
3414
|
this.releaseStackEntry = null;
|
|
3415
|
+
const hadOriginalParent = this.originalParent !== null;
|
|
3282
3416
|
this.restoreMount();
|
|
3417
|
+
if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
|
|
3418
|
+
this.element.parentNode.removeChild(this.element);
|
|
3419
|
+
}
|
|
3283
3420
|
}
|
|
3284
|
-
/**
|
|
3285
|
-
* Runtime visual setter — opacity is applied immediately when open so a
|
|
3286
|
-
* live-controls panel can preview the change without re-opening. When
|
|
3287
|
-
* closed we only stash the field; next open() picks it up so we don't
|
|
3288
|
-
* mutate hidden DOM (avoids paint thrash on detached panels).
|
|
3289
|
-
*/
|
|
3290
3421
|
setBackdropOpacity(opacity) {
|
|
3291
3422
|
if (this.destroyed) return;
|
|
3292
3423
|
this.backdropOpacity = opacity;
|
|
@@ -3294,11 +3425,6 @@ var OverlayEngine = class {
|
|
|
3294
3425
|
this.backdrop.style.opacity = String(opacity);
|
|
3295
3426
|
}
|
|
3296
3427
|
}
|
|
3297
|
-
/**
|
|
3298
|
-
* Runtime visual setter for `backdrop-filter`. `undefined` clears the
|
|
3299
|
-
* filter (mirrors the close() behaviour). Defers to next open() when the
|
|
3300
|
-
* overlay is currently closed.
|
|
3301
|
-
*/
|
|
3302
3428
|
setBackdropFilter(filter) {
|
|
3303
3429
|
if (this.destroyed) return;
|
|
3304
3430
|
this.backdropFilter = filter;
|
|
@@ -3312,12 +3438,6 @@ var OverlayEngine = class {
|
|
|
3312
3438
|
style.webkitBackdropFilter = "";
|
|
3313
3439
|
}
|
|
3314
3440
|
}
|
|
3315
|
-
/**
|
|
3316
|
-
* Toggle / reconfigure swipe-to-close at runtime. Re-resolves the flat
|
|
3317
|
-
* threshold fields, then installs or tears down the pointer listener so
|
|
3318
|
-
* the change applies to the CURRENT open cycle. Idempotent: safe to call
|
|
3319
|
-
* with the same config repeatedly.
|
|
3320
|
-
*/
|
|
3321
3441
|
setSwipeToClose(config) {
|
|
3322
3442
|
if (this.destroyed) return;
|
|
3323
3443
|
const wasEnabled = this.swipeToCloseEnabled;
|
|
@@ -3330,7 +3450,6 @@ var OverlayEngine = class {
|
|
|
3330
3450
|
this.detachSwipe = null;
|
|
3331
3451
|
}
|
|
3332
3452
|
}
|
|
3333
|
-
/** Animation setters only stage the field — applied on next open/close. */
|
|
3334
3453
|
setEnterAnimation(animation) {
|
|
3335
3454
|
if (this.destroyed) return;
|
|
3336
3455
|
this.enterAnimation = animation;
|
|
@@ -3339,23 +3458,15 @@ var OverlayEngine = class {
|
|
|
3339
3458
|
if (this.destroyed) return;
|
|
3340
3459
|
this.exitAnimation = animation;
|
|
3341
3460
|
}
|
|
3342
|
-
/** Updates the field used at next close() — no immediate side effect. */
|
|
3343
3461
|
setReturnFocus(target) {
|
|
3344
3462
|
if (this.destroyed) return;
|
|
3345
3463
|
this.lifecycle.setReturnFocus(target);
|
|
3346
3464
|
}
|
|
3347
|
-
/**
|
|
3348
|
-
* Batch update — preset first, then individual overrides. Applied as a
|
|
3349
|
-
* single DOM-write phase when open so live-controls don't trigger
|
|
3350
|
-
* cascading reflows. Safe to call when closed (defers to next open).
|
|
3351
|
-
*/
|
|
3352
3465
|
setOverlay(opts) {
|
|
3353
3466
|
if (this.destroyed) return;
|
|
3354
3467
|
const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
|
|
3355
3468
|
const merged = preset ? {
|
|
3356
3469
|
backdropOpacity: preset.backdropOpacity,
|
|
3357
|
-
// Preset's `undefined` filter means "no filter" — surface as null
|
|
3358
|
-
// so the merged step clears the field instead of inheriting old.
|
|
3359
3470
|
backdropFilter: preset.backdropFilter === void 0 ? null : preset.backdropFilter,
|
|
3360
3471
|
swipeToClose: preset.swipeToClose,
|
|
3361
3472
|
enterAnimation: preset.enterAnimation,
|
|
@@ -3406,6 +3517,36 @@ var OverlayEngine = class {
|
|
|
3406
3517
|
}
|
|
3407
3518
|
}
|
|
3408
3519
|
}
|
|
3520
|
+
if (opts.duration !== void 0) this.duration = opts.duration;
|
|
3521
|
+
if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
|
|
3522
|
+
if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
|
|
3523
|
+
if (opts.closeOnEscape !== void 0) {
|
|
3524
|
+
this.closeOnEscape = opts.closeOnEscape;
|
|
3525
|
+
if (!opts.closeOnEscape) {
|
|
3526
|
+
this.detachEscape?.();
|
|
3527
|
+
this.detachEscape = null;
|
|
3528
|
+
} else if (this.isOpen_) {
|
|
3529
|
+
this.installInteractiveListeners();
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
if (opts.closeOnBackdrop !== void 0) {
|
|
3533
|
+
this.closeOnBackdropEnabled = opts.closeOnBackdrop;
|
|
3534
|
+
if (!opts.closeOnBackdrop) {
|
|
3535
|
+
this.detachBackdrop?.();
|
|
3536
|
+
this.detachBackdrop = null;
|
|
3537
|
+
} else if (this.isOpen_) {
|
|
3538
|
+
this.installInteractiveListeners();
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
if (opts.closeOnOutsidePointer !== void 0) {
|
|
3542
|
+
this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
|
|
3543
|
+
if (!opts.closeOnOutsidePointer) {
|
|
3544
|
+
this.detachOutsidePointer?.();
|
|
3545
|
+
this.detachOutsidePointer = null;
|
|
3546
|
+
} else if (this.isOpen_) {
|
|
3547
|
+
this.installInteractiveListeners();
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3409
3550
|
if (opts.children !== void 0) {
|
|
3410
3551
|
if (opts.children === null) {
|
|
3411
3552
|
this.clearOverlayChildren();
|
|
@@ -3414,30 +3555,17 @@ var OverlayEngine = class {
|
|
|
3414
3555
|
}
|
|
3415
3556
|
}
|
|
3416
3557
|
}
|
|
3417
|
-
/**
|
|
3418
|
-
* Runtime children injection — replace the panel's contents without
|
|
3419
|
-
* unmounting the engine. `replaceChildren()` is a single atomic DOM op,
|
|
3420
|
-
* so callers can swap content rapidly without intermediate flicker.
|
|
3421
|
-
* Function form is resolved exactly ONCE per call (cheap lazy content).
|
|
3422
|
-
*/
|
|
3423
3558
|
setOverlayChildren(children) {
|
|
3424
3559
|
if (this.destroyed) return;
|
|
3425
3560
|
const node = typeof children === "function" ? children() : children;
|
|
3426
3561
|
this.element.replaceChildren(node);
|
|
3427
3562
|
this.hasInjectedChildren = true;
|
|
3428
3563
|
}
|
|
3429
|
-
/**
|
|
3430
|
-
* Clear injected children. No-op when nothing was injected so consumers
|
|
3431
|
-
* who never used setOverlayChildren can call this defensively without
|
|
3432
|
-
* paying for a DOM mutation.
|
|
3433
|
-
*/
|
|
3434
3564
|
clearOverlayChildren() {
|
|
3435
3565
|
if (this.destroyed || !this.hasInjectedChildren) return;
|
|
3436
3566
|
this.element.replaceChildren();
|
|
3437
3567
|
this.hasInjectedChildren = false;
|
|
3438
3568
|
}
|
|
3439
|
-
// Shared swipe-config normalizer — used by constructor, setSwipeToClose,
|
|
3440
|
-
// and setOverlay so the union/object shape is decoded in exactly one place.
|
|
3441
3569
|
assignSwipeFromConfig(swipe) {
|
|
3442
3570
|
if (typeof swipe === "object" && swipe !== null) {
|
|
3443
3571
|
this.swipeToCloseEnabled = swipe.enabled ?? true;
|
|
@@ -3452,6 +3580,22 @@ var OverlayEngine = class {
|
|
|
3452
3580
|
emit(event, payload) {
|
|
3453
3581
|
this.bus.emit(event, payload);
|
|
3454
3582
|
}
|
|
3583
|
+
effectiveDuration() {
|
|
3584
|
+
return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
|
|
3585
|
+
}
|
|
3586
|
+
popBackMarker(reason) {
|
|
3587
|
+
if (!this.backPushed) return;
|
|
3588
|
+
this.backPushed = false;
|
|
3589
|
+
if (reason === "back") return;
|
|
3590
|
+
const state = history.state;
|
|
3591
|
+
if (state && state.__bsOverlay === this.id) {
|
|
3592
|
+
try {
|
|
3593
|
+
history.back();
|
|
3594
|
+
} catch {
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3455
3599
|
resolvePeekPx(raw) {
|
|
3456
3600
|
if (typeof raw === "number") return Math.max(0, raw);
|
|
3457
3601
|
try {
|
|
@@ -3473,6 +3617,7 @@ var OverlayEngine = class {
|
|
|
3473
3617
|
this.originalParent = this.element.parentNode;
|
|
3474
3618
|
this.originalNextSibling = this.element.nextSibling;
|
|
3475
3619
|
target.appendChild(this.element);
|
|
3620
|
+
this.movedToTarget = true;
|
|
3476
3621
|
this.mounted = true;
|
|
3477
3622
|
}
|
|
3478
3623
|
restoreMount() {
|
|
@@ -3506,7 +3651,7 @@ var OverlayEngine = class {
|
|
|
3506
3651
|
this.backdrop.style.willChange = "opacity";
|
|
3507
3652
|
}
|
|
3508
3653
|
}
|
|
3509
|
-
awaitTransition(cycle) {
|
|
3654
|
+
awaitTransition(cycle, property = "transform") {
|
|
3510
3655
|
return new Promise((resolve) => {
|
|
3511
3656
|
let done = false;
|
|
3512
3657
|
const finish = () => {
|
|
@@ -3521,10 +3666,10 @@ var OverlayEngine = class {
|
|
|
3521
3666
|
finish();
|
|
3522
3667
|
return;
|
|
3523
3668
|
}
|
|
3524
|
-
if (e.propertyName ===
|
|
3669
|
+
if (e.target === this.element && e.propertyName === property) finish();
|
|
3525
3670
|
};
|
|
3526
3671
|
this.element.addEventListener("transitionend", onEnd);
|
|
3527
|
-
const timer = window.setTimeout(finish, this.
|
|
3672
|
+
const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
|
|
3528
3673
|
});
|
|
3529
3674
|
}
|
|
3530
3675
|
installInteractiveListeners() {
|
|
@@ -3534,7 +3679,10 @@ var OverlayEngine = class {
|
|
|
3534
3679
|
}
|
|
3535
3680
|
if (this.closeOnEscape && !this.detachEscape) {
|
|
3536
3681
|
const handler = (e) => {
|
|
3537
|
-
if (e.
|
|
3682
|
+
if (e.defaultPrevented) return;
|
|
3683
|
+
if (e.key === "Escape" && this.isOpen_ && this.isTop) {
|
|
3684
|
+
void this.close("escape");
|
|
3685
|
+
}
|
|
3538
3686
|
};
|
|
3539
3687
|
document.addEventListener("keydown", handler);
|
|
3540
3688
|
this.detachEscape = () => document.removeEventListener("keydown", handler);
|
|
@@ -3547,10 +3695,14 @@ var OverlayEngine = class {
|
|
|
3547
3695
|
if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
|
|
3548
3696
|
try {
|
|
3549
3697
|
history.pushState({ __bsOverlay: this.id }, "");
|
|
3698
|
+
this.backPushed = true;
|
|
3550
3699
|
} catch {
|
|
3551
3700
|
}
|
|
3552
3701
|
const onPop = () => {
|
|
3553
|
-
if (this.isOpen_
|
|
3702
|
+
if (this.isOpen_ && this.isTop) {
|
|
3703
|
+
this.backPushed = false;
|
|
3704
|
+
void this.close("back");
|
|
3705
|
+
}
|
|
3554
3706
|
};
|
|
3555
3707
|
window.addEventListener("popstate", onPop);
|
|
3556
3708
|
this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
|
|
@@ -3581,55 +3733,57 @@ var OverlayEngine = class {
|
|
|
3581
3733
|
this.detachOutsidePointer = null;
|
|
3582
3734
|
this.lifecycle.release();
|
|
3583
3735
|
}
|
|
3584
|
-
/**
|
|
3585
|
-
* Light pointer-driven dismiss. Tracks finger along the edge axis, follows
|
|
3586
|
-
* with translate, dismisses if past `swipeThreshold` (fraction of panel
|
|
3587
|
-
* size) or release velocity exceeds `swipeVelocityThreshold` (px/ms).
|
|
3588
|
-
* Pure CSS-transform during drag — no rAF or physics required since the
|
|
3589
|
-
* panel only moves one direction.
|
|
3590
|
-
*/
|
|
3591
3736
|
installSwipeToClose() {
|
|
3592
3737
|
const el = this.element;
|
|
3593
3738
|
const edge = this.edge;
|
|
3594
3739
|
const isVertical = edge === "bottom" || edge === "top";
|
|
3595
3740
|
const sign = edge === "bottom" || edge === "right" ? 1 : -1;
|
|
3596
|
-
const
|
|
3597
|
-
const velocityThreshold = this.swipeVelocityThreshold;
|
|
3741
|
+
const SLOP = 8;
|
|
3598
3742
|
let startX = 0;
|
|
3599
3743
|
let startY = 0;
|
|
3600
3744
|
let startT = 0;
|
|
3745
|
+
let tracking = false;
|
|
3601
3746
|
let dragging = false;
|
|
3602
3747
|
let pointerId = null;
|
|
3748
|
+
const previousTouchAction = el.style.touchAction;
|
|
3749
|
+
el.style.touchAction = isVertical ? "pan-x" : "pan-y";
|
|
3603
3750
|
const onDown = (e) => {
|
|
3604
3751
|
if (e.button !== void 0 && e.button !== 0) return;
|
|
3605
3752
|
pointerId = e.pointerId;
|
|
3606
3753
|
startX = e.clientX;
|
|
3607
3754
|
startY = e.clientY;
|
|
3608
3755
|
startT = performance.now();
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
try {
|
|
3612
|
-
el.setPointerCapture(e.pointerId);
|
|
3613
|
-
} catch {
|
|
3614
|
-
}
|
|
3756
|
+
tracking = true;
|
|
3757
|
+
dragging = false;
|
|
3615
3758
|
};
|
|
3616
3759
|
const onMove = (e) => {
|
|
3617
|
-
if (!
|
|
3760
|
+
if (!tracking || e.pointerId !== pointerId) return;
|
|
3618
3761
|
const delta = isVertical ? e.clientY - startY : e.clientX - startX;
|
|
3762
|
+
if (!dragging) {
|
|
3763
|
+
if (Math.abs(delta) < SLOP) return;
|
|
3764
|
+
dragging = true;
|
|
3765
|
+
el.style.transition = "none";
|
|
3766
|
+
try {
|
|
3767
|
+
el.setPointerCapture(e.pointerId);
|
|
3768
|
+
} catch {
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3619
3771
|
const offset = Math.max(0, sign * delta);
|
|
3620
3772
|
el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
|
|
3621
3773
|
};
|
|
3622
3774
|
const finish = (e) => {
|
|
3623
|
-
if (!
|
|
3624
|
-
|
|
3775
|
+
if (!tracking || e.pointerId !== pointerId) return;
|
|
3776
|
+
tracking = false;
|
|
3625
3777
|
pointerId = null;
|
|
3778
|
+
if (!dragging) return;
|
|
3779
|
+
dragging = false;
|
|
3626
3780
|
const dt = performance.now() - startT || 1;
|
|
3627
3781
|
const delta = isVertical ? e.clientY - startY : e.clientX - startX;
|
|
3628
3782
|
const offset = Math.max(0, sign * delta);
|
|
3629
3783
|
const velocity = sign * delta / dt;
|
|
3630
3784
|
const size = isVertical ? el.offsetHeight : el.offsetWidth;
|
|
3631
|
-
const past = offset > size *
|
|
3632
|
-
el.style.transition = `transform ${this.
|
|
3785
|
+
const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
|
|
3786
|
+
el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
|
|
3633
3787
|
if (past) {
|
|
3634
3788
|
void this.close("swipe");
|
|
3635
3789
|
} else {
|
|
@@ -3641,6 +3795,7 @@ var OverlayEngine = class {
|
|
|
3641
3795
|
el.addEventListener("pointerup", finish);
|
|
3642
3796
|
el.addEventListener("pointercancel", finish);
|
|
3643
3797
|
return () => {
|
|
3798
|
+
el.style.touchAction = previousTouchAction;
|
|
3644
3799
|
el.removeEventListener("pointerdown", onDown);
|
|
3645
3800
|
el.removeEventListener("pointermove", onMove);
|
|
3646
3801
|
el.removeEventListener("pointerup", finish);
|
|
@@ -3655,15 +3810,17 @@ var OverlayEngine = class {
|
|
|
3655
3810
|
if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
|
|
3656
3811
|
},
|
|
3657
3812
|
setIsTop: (isTop) => {
|
|
3813
|
+
this.isTop = isTop;
|
|
3658
3814
|
if (this.backdrop) {
|
|
3659
3815
|
this.backdrop.style.display = isTop ? "" : "none";
|
|
3660
3816
|
}
|
|
3661
|
-
}
|
|
3817
|
+
},
|
|
3818
|
+
isOpen: () => this.isOpen_
|
|
3662
3819
|
});
|
|
3663
3820
|
}
|
|
3664
3821
|
};
|
|
3665
3822
|
var createOverlay = (opts) => new OverlayEngine(opts);
|
|
3666
3823
|
|
|
3667
|
-
export { BottomSheetEngine, DEFAULT_SPRING, OVERLAY_PRESETS, OverlayEngine as Overlay, OverlayEngine, SCRIM_PRESETS, allowedRange, attachGestures, createOverlay, createSheetManager, easeOutBack, easeOutCubic, findById, findNearest, installFocusTrap, installGestures, lockBodyScroll, prefersReducedMotion, resolveSnap, resolveSnapList, runSpring, sheetStack, tween };
|
|
3824
|
+
export { BottomSheetEngine, DEFAULT_SPRING, OVERLAY_PRESETS, OverlayEngine as Overlay, OverlayEngine, SCRIM_PRESETS, allowedRange, applyOverlayPosition, attachGestures, createOverlay, createSheetManager, easeOutBack, easeOutCubic, findById, findNearest, installFocusTrap, installGestures, lockBodyScroll, prefersReducedMotion, resolveSheetAnchoredStyle, resolveSnap, resolveSnapList, runAnchorTransition, runSpring, sheetStack, tween };
|
|
3668
3825
|
//# sourceMappingURL=index.js.map
|
|
3669
3826
|
//# sourceMappingURL=index.js.map
|