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