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