@surdeddd/bottom-sheet 0.1.0 → 0.2.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 +6 -0
- package/dist/BottomSheetEngine-CIT9Htti.d.cts +219 -0
- package/dist/BottomSheetEngine-Df9MwBbm.d.ts +219 -0
- package/dist/element.cjs +95 -440
- package/dist/element.d.cts +3 -56
- package/dist/element.d.ts +3 -56
- package/dist/element.iife.global.js +2 -2
- package/dist/element.js +95 -440
- package/dist/index.cjs +84 -494
- package/dist/index.d.cts +3 -84
- package/dist/index.d.ts +3 -84
- package/dist/index.js +84 -494
- 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 +0 -87
- package/dist/overlay.d.cts +1 -137
- package/dist/overlay.d.ts +1 -137
- package/dist/overlay.js +0 -87
- package/dist/preact.d.cts +2 -2
- package/dist/preact.d.ts +2 -2
- package/dist/preact.js +0 -1
- package/dist/qwik.cjs +87 -439
- package/dist/qwik.d.cts +3 -24
- package/dist/qwik.d.ts +3 -24
- package/dist/qwik.js +87 -439
- package/dist/react.cjs +91 -496
- package/dist/react.d.cts +16 -158
- package/dist/react.d.ts +16 -158
- package/dist/react.js +91 -496
- package/dist/solid.cjs +87 -439
- package/dist/solid.d.cts +4 -31
- package/dist/solid.d.ts +4 -31
- package/dist/solid.js +87 -439
- package/dist/styles.css +27 -102
- package/dist/svelte.cjs +139 -462
- package/dist/svelte.d.ts +9 -2
- package/dist/svelte.js +139 -462
- 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 +104 -446
- package/dist/vue.d.ts +7 -1
- package/dist/vue.js +105 -447
- package/package.json +3 -3
- package/dist/BottomSheetEngine-CvSVbo6c.d.cts +0 -704
- package/dist/BottomSheetEngine-DbO8rW_3.d.ts +0 -704
package/dist/index.cjs
CHANGED
|
@@ -236,11 +236,6 @@ var SnapResolver = class {
|
|
|
236
236
|
this.allowedIds = allowed ?? raw.map((p) => p.id);
|
|
237
237
|
this.recompute();
|
|
238
238
|
}
|
|
239
|
-
/**
|
|
240
|
-
* Re-resolve raw → resolved against the current viewport / fit-measure,
|
|
241
|
-
* recompute maxAxisSize, invalidate range cache, notify engine. Called on
|
|
242
|
-
* resize, orientationchange, visualViewport changes, and snap-list swaps.
|
|
243
|
-
*/
|
|
244
239
|
recompute() {
|
|
245
240
|
this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
|
|
246
241
|
this.maxAxisSize = this.resolved.reduce(
|
|
@@ -250,15 +245,10 @@ var SnapResolver = class {
|
|
|
250
245
|
this.rangeCache = null;
|
|
251
246
|
this.onMaxAxisSizeChange?.(this.maxAxisSize);
|
|
252
247
|
}
|
|
253
|
-
/** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
|
|
254
248
|
setRaw(raw) {
|
|
255
249
|
this.raw = raw;
|
|
256
250
|
this.recompute();
|
|
257
251
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Replace the allow-list. Range cache depends on `(resolvedSnaps ×
|
|
260
|
-
* allowedIds)` so flipping ids alone invalidates without a full recompute.
|
|
261
|
-
*/
|
|
262
252
|
setAllowedIds(ids) {
|
|
263
253
|
this.allowedIds = ids;
|
|
264
254
|
this.rangeCache = null;
|
|
@@ -266,11 +256,6 @@ var SnapResolver = class {
|
|
|
266
256
|
findById(id) {
|
|
267
257
|
return findById(id, this.resolved);
|
|
268
258
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Pure drag-settle target resolution. Engine passes the gesture-specific
|
|
271
|
-
* fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
|
|
272
|
-
* `size`/`activeId` since those are still engine-owned.
|
|
273
|
-
*/
|
|
274
259
|
findDragSettleTarget(args) {
|
|
275
260
|
return findDragSettleTarget({
|
|
276
261
|
...args,
|
|
@@ -279,18 +264,9 @@ var SnapResolver = class {
|
|
|
279
264
|
maxAxisSize: this.maxAxisSize
|
|
280
265
|
});
|
|
281
266
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
|
|
284
|
-
* applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
|
|
285
|
-
* to one recompute per geometry/allow-list change.
|
|
286
|
-
*/
|
|
287
267
|
getAllowedRange() {
|
|
288
268
|
return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
|
|
289
269
|
}
|
|
290
|
-
/**
|
|
291
|
-
* Read-only view of resolved snaps. Returned as `readonly` to discourage
|
|
292
|
-
* accidental mutation by feature install sites that close over this list.
|
|
293
|
-
*/
|
|
294
270
|
getResolvedSnaps() {
|
|
295
271
|
return this.resolved;
|
|
296
272
|
}
|
|
@@ -300,18 +276,6 @@ var SnapResolver = class {
|
|
|
300
276
|
getMaxAxisSize() {
|
|
301
277
|
return this.maxAxisSize;
|
|
302
278
|
}
|
|
303
|
-
/**
|
|
304
|
-
* External clamp override. The resize-observer feature compares
|
|
305
|
-
* `getMaxAxisSize()` against the live viewport and shrinks if the largest
|
|
306
|
-
* resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
|
|
307
|
-
* window resize). The next `recompute()` overwrites this clamp from the
|
|
308
|
-
* resolved-snap reduction.
|
|
309
|
-
*
|
|
310
|
-
* Does NOT invalidate the range cache — viewport clamping doesn't change
|
|
311
|
-
* `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
|
|
312
|
-
* so the resolver owns the layout-axis style write end-to-end (symmetric
|
|
313
|
-
* with `recompute()` — feature call sites no longer duplicate the write).
|
|
314
|
-
*/
|
|
315
279
|
setMaxAxisSize(n) {
|
|
316
280
|
this.maxAxisSize = n;
|
|
317
281
|
this.onMaxAxisSizeChange?.(n);
|
|
@@ -686,73 +650,49 @@ var WriteSentinel = class {
|
|
|
686
650
|
constructor() {
|
|
687
651
|
__publicField(this, "last", -1);
|
|
688
652
|
}
|
|
689
|
-
/**
|
|
690
|
-
* Returns `true` (and updates the cached value) if `next` differs from the
|
|
691
|
-
* cached `last` by more than `epsilon`. Returns `false` (and leaves the
|
|
692
|
-
* cache untouched) when the delta is within epsilon — caller skips the
|
|
693
|
-
* CSSOM write.
|
|
694
|
-
*/
|
|
695
653
|
shouldWrite(next, epsilon) {
|
|
696
654
|
if (Math.abs(next - this.last) <= epsilon) return false;
|
|
697
655
|
this.last = next;
|
|
698
656
|
return true;
|
|
699
657
|
}
|
|
700
|
-
/**
|
|
701
|
-
* Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
|
|
702
|
-
* non-negative `next` exceeds the epsilon comparison. Called from setters
|
|
703
|
-
* that mutate state under the cache (range/mode/enabled flips that change
|
|
704
|
-
* the progress→opacity mapping but don't move `progress` itself).
|
|
705
|
-
*/
|
|
706
658
|
invalidate() {
|
|
707
659
|
this.last = -1;
|
|
708
660
|
}
|
|
709
|
-
/**
|
|
710
|
-
* Record `value` as the last-written without performing a write. Used
|
|
711
|
-
* when a setter writes a literal value through a different path (e.g.
|
|
712
|
-
* `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
|
|
713
|
-
* `shouldWrite` calls to short-circuit against that known cached state.
|
|
714
|
-
*/
|
|
715
661
|
setLastWritten(value) {
|
|
716
662
|
this.last = value;
|
|
717
663
|
}
|
|
718
|
-
/** Read-only accessor for diagnostic / dedup-aware peek. */
|
|
719
664
|
get value() {
|
|
720
665
|
return this.last;
|
|
721
666
|
}
|
|
722
667
|
};
|
|
723
668
|
|
|
669
|
+
// src/core/primitives/devWarn.ts
|
|
670
|
+
function devWarn(...args) {
|
|
671
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
|
|
672
|
+
console.warn(...args);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
724
676
|
// src/core/controllers/scrim-controller.ts
|
|
677
|
+
var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
|
|
725
678
|
var ScrimController = class {
|
|
726
679
|
constructor(deps, opts) {
|
|
727
|
-
// ── DOM refs (mirror engine's; cached for hot path) ─────────────────
|
|
728
680
|
__publicField(this, "screenComponent");
|
|
729
681
|
__publicField(this, "backdrop");
|
|
730
682
|
__publicField(this, "mode");
|
|
731
|
-
// ── Engine callbacks ────────────────────────────────────────────────
|
|
732
683
|
__publicField(this, "isDestroyed");
|
|
733
684
|
__publicField(this, "isTopSheet");
|
|
734
685
|
__publicField(this, "getAllowedIds");
|
|
735
686
|
__publicField(this, "getResolvedSnaps");
|
|
736
687
|
__publicField(this, "snapToFn");
|
|
737
|
-
// ── Mode + state ────────────────────────────────────────────────────
|
|
738
688
|
__publicField(this, "scrimMode");
|
|
739
689
|
__publicField(this, "backdropRange");
|
|
740
690
|
__publicField(this, "screenRange");
|
|
741
|
-
// setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
|
|
742
|
-
// ranges so re-enable can restore prior values without a remount. Public
|
|
743
|
-
// so tests / introspection can read it without an `as any` cast.
|
|
744
691
|
__publicField(this, "scrimEnabled", true);
|
|
745
692
|
__publicField(this, "savedScrimRanges", null);
|
|
746
|
-
// ── Tap-to-close ────────────────────────────────────────────────────
|
|
747
693
|
__publicField(this, "scrimTapToCloseEnabled", false);
|
|
748
694
|
__publicField(this, "detachScrimTap", null);
|
|
749
|
-
// ── Scrim overlay slot (positioned floating element) ────────────────
|
|
750
695
|
__publicField(this, "scrimOverlayTeardown", null);
|
|
751
|
-
// ── Hot-path opacity dedup caches (per-frame writes) ────────────────
|
|
752
|
-
// Numeric sentinels migrated to WriteSentinel primitive — single
|
|
753
|
-
// invalidation surface, shared with the engine's size/progress sentinels.
|
|
754
|
-
// The two string-typed sentinels (`Pointer`, `Display`) keep their inline
|
|
755
|
-
// pattern: equality on string union, no epsilon, different reset semantics.
|
|
756
696
|
__publicField(this, "backdropOpacitySentinel", new WriteSentinel());
|
|
757
697
|
__publicField(this, "screenOpacitySentinel", new WriteSentinel());
|
|
758
698
|
__publicField(this, "lastBackdropPointer", "");
|
|
@@ -787,41 +727,15 @@ var ScrimController = class {
|
|
|
787
727
|
}
|
|
788
728
|
}
|
|
789
729
|
}
|
|
790
|
-
/**
|
|
791
|
-
* Invalidate the opacity dedup caches. Called from any setter that mutates
|
|
792
|
-
* range/mode/enabled — opacity is a pure function of (progress, range,
|
|
793
|
-
* enabled), so a config change makes the cached values stale even if
|
|
794
|
-
* `progress` itself didn't move. Next `applyOpacity(progress, false)` call
|
|
795
|
-
* sees the -1 sentinel and re-runs the math instead of short-circuiting.
|
|
796
|
-
*
|
|
797
|
-
* Public for the engine: `setSnapPoints` mutates resolved geometry which
|
|
798
|
-
* shifts `progress→opacity` mapping under the cache; engine calls this to
|
|
799
|
-
* keep the next render frame correct on a static sheet.
|
|
800
|
-
* @internal
|
|
801
|
-
*/
|
|
802
730
|
invalidateOpacityCache() {
|
|
803
731
|
this.backdropOpacitySentinel.invalidate();
|
|
804
732
|
this.screenOpacitySentinel.invalidate();
|
|
805
733
|
}
|
|
806
|
-
/** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
|
|
807
734
|
attach() {
|
|
808
735
|
if (this.scrimTapToCloseEnabled) {
|
|
809
736
|
this.setScrimTapToClose(true);
|
|
810
737
|
}
|
|
811
738
|
}
|
|
812
|
-
// ── Hot-path: opacity computation called per-frame from engine.applySize ──
|
|
813
|
-
/**
|
|
814
|
-
* Per-frame opacity writer. Computes backdrop + screen opacity from the
|
|
815
|
-
* sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
|
|
816
|
-
* caches so steady-state frames don't trigger style recalc.
|
|
817
|
-
*
|
|
818
|
-
* `progressChanged` is the engine's already-computed dedup gate — when
|
|
819
|
-
* `false`, opacity is by construction a pure function of `progress` so
|
|
820
|
-
* cached values are still valid; we short-circuit the Math.min/max +
|
|
821
|
-
* division work entirely. Setters that mutate ranges/mode/enabled
|
|
822
|
-
* invalidate the cache by writing -1 sentinels so the next call re-runs
|
|
823
|
-
* the math.
|
|
824
|
-
*/
|
|
825
739
|
applyOpacity(progress, progressChanged) {
|
|
826
740
|
if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
|
|
827
741
|
return;
|
|
@@ -867,80 +781,30 @@ var ScrimController = class {
|
|
|
867
781
|
}
|
|
868
782
|
}
|
|
869
783
|
}
|
|
870
|
-
// ── Public setters (engine forwards these 1:1) ─────────────────────
|
|
871
|
-
/**
|
|
872
|
-
* Replace the [start, end] progress window in which the backdrop fades 0→1.
|
|
873
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
874
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
875
|
-
* @example
|
|
876
|
-
* // Backdrop only starts darkening after the sheet crosses 30% open.
|
|
877
|
-
* engine.setBackdropRange([0.3, 1]);
|
|
878
|
-
*/
|
|
879
784
|
setBackdropRange(range, applySize) {
|
|
880
785
|
if (this.isDestroyed()) return;
|
|
881
786
|
this.backdropRange = range;
|
|
882
787
|
this.invalidateOpacityCache();
|
|
883
788
|
applySize();
|
|
884
789
|
}
|
|
885
|
-
/**
|
|
886
|
-
* Replace the [start, end] progress window in which the screen scrim fades 0→1.
|
|
887
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
888
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
889
|
-
* @example
|
|
890
|
-
* // Scrim is fully opaque only when the sheet is past 50% — softer feel.
|
|
891
|
-
* engine.setScreenRange([0, 0.5]);
|
|
892
|
-
*/
|
|
893
790
|
setScreenRange(range, applySize) {
|
|
894
791
|
if (this.isDestroyed()) return;
|
|
895
792
|
this.screenRange = range;
|
|
896
793
|
this.invalidateOpacityCache();
|
|
897
794
|
applySize();
|
|
898
795
|
}
|
|
899
|
-
/**
|
|
900
|
-
* Set the scrim background color. Pass `null`/`undefined` to clear the
|
|
901
|
-
* inline write and let consumer CSS take over.
|
|
902
|
-
* @param color - Any valid CSS color string, or `null`/`undefined` to clear.
|
|
903
|
-
* @example
|
|
904
|
-
* engine.setScrimColor("rgba(0, 0, 0, 0.6)");
|
|
905
|
-
* engine.setScrimColor(null); // hand back to consumer CSS
|
|
906
|
-
*/
|
|
907
796
|
setScrimColor(color) {
|
|
908
797
|
if (this.isDestroyed()) return;
|
|
909
798
|
this.applyScrimStyles({ color: color === void 0 ? null : color });
|
|
910
799
|
}
|
|
911
|
-
/**
|
|
912
|
-
* Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
|
|
913
|
-
* @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
|
|
914
|
-
* `null`/`undefined` to remove the filter.
|
|
915
|
-
* @example
|
|
916
|
-
* engine.setScrimBlur("12px"); // frosted-glass effect
|
|
917
|
-
* engine.setScrimBlur(null); // disable blur
|
|
918
|
-
*/
|
|
919
800
|
setScrimBlur(blur) {
|
|
920
801
|
if (this.isDestroyed()) return;
|
|
921
802
|
this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
|
|
922
803
|
}
|
|
923
|
-
/**
|
|
924
|
-
* Toggle whether the scrim layer captures pointer events.
|
|
925
|
-
* @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
|
|
926
|
-
* @example
|
|
927
|
-
* engine.setScrimInteractive(true); // scrim swallows clicks
|
|
928
|
-
* engine.setScrimInteractive(false); // clicks pass through to content below
|
|
929
|
-
*/
|
|
930
804
|
setScrimInteractive(interactive) {
|
|
931
805
|
if (this.isDestroyed()) return;
|
|
932
806
|
this.applyScrimStyles({ interactive });
|
|
933
807
|
}
|
|
934
|
-
/**
|
|
935
|
-
* Switch scrim positioning at runtime.
|
|
936
|
-
* @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
|
|
937
|
-
* so only the area NOT covered by the sheet is dimmed), or
|
|
938
|
-
* `"off"` (display:none + opacity:0 — no remount needed).
|
|
939
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
940
|
-
* @example
|
|
941
|
-
* engine.setScrimMode("above-sheet"); // dim only the area above the sheet
|
|
942
|
-
* engine.setScrimMode("off"); // disable scrim layer
|
|
943
|
-
*/
|
|
944
808
|
setScrimMode(mode, applySize) {
|
|
945
809
|
if (this.isDestroyed()) return;
|
|
946
810
|
if (mode === this.scrimMode) return;
|
|
@@ -970,15 +834,6 @@ var ScrimController = class {
|
|
|
970
834
|
}
|
|
971
835
|
applySize();
|
|
972
836
|
}
|
|
973
|
-
/**
|
|
974
|
-
* Enable or disable click-on-scrim → snap to first non-zero allowed snap.
|
|
975
|
-
* Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
|
|
976
|
-
* click listener actually fires.
|
|
977
|
-
* @param enabled - `true` installs the click listener; `false` removes it.
|
|
978
|
-
* @example
|
|
979
|
-
* engine.setScrimTapToClose(true); // tap outside sheet collapses it
|
|
980
|
-
* engine.setScrimTapToClose(false); // require explicit close button
|
|
981
|
-
*/
|
|
982
837
|
setScrimTapToClose(enabled) {
|
|
983
838
|
if (this.isDestroyed()) return;
|
|
984
839
|
if (!this.screenComponent) return;
|
|
@@ -1008,16 +863,6 @@ var ScrimController = class {
|
|
|
1008
863
|
this.scrimTapToCloseEnabled = false;
|
|
1009
864
|
}
|
|
1010
865
|
}
|
|
1011
|
-
/**
|
|
1012
|
-
* Toggle the scrim layer without remounting. Disabling stashes current
|
|
1013
|
-
* `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
|
|
1014
|
-
* formula → 0 across the whole range); re-enabling restores the saved values.
|
|
1015
|
-
* @param enabled - `false` hides scrim, `true` restores prior ranges.
|
|
1016
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
1017
|
-
* @example
|
|
1018
|
-
* engine.setScrimEnabled(false); // hide scrim, ranges remembered
|
|
1019
|
-
* engine.setScrimEnabled(true); // restore prior ranges (no remount)
|
|
1020
|
-
*/
|
|
1021
866
|
setScrimEnabled(enabled, applySize) {
|
|
1022
867
|
if (this.isDestroyed()) return;
|
|
1023
868
|
if (enabled === this.scrimEnabled) return;
|
|
@@ -1036,20 +881,6 @@ var ScrimController = class {
|
|
|
1036
881
|
}
|
|
1037
882
|
applySize();
|
|
1038
883
|
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Batch-update multiple scrim fields with a single applySize() at the end.
|
|
1041
|
-
* If `preset` is set, it seeds color/blur/interactive/range first; explicit
|
|
1042
|
-
* fields override.
|
|
1043
|
-
* @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
|
|
1044
|
-
* color, blur, interactive, range.
|
|
1045
|
-
* @param applySize - Engine-supplied recompute hook; runs ONCE per call.
|
|
1046
|
-
* @example
|
|
1047
|
-
* engine.setScrim({
|
|
1048
|
-
* preset: "frosted",
|
|
1049
|
-
* mode: "above-sheet",
|
|
1050
|
-
* tapToClose: true,
|
|
1051
|
-
* });
|
|
1052
|
-
*/
|
|
1053
884
|
setScrim(opts, applySize) {
|
|
1054
885
|
if (this.isDestroyed()) return;
|
|
1055
886
|
if (opts.preset) {
|
|
@@ -1072,27 +903,18 @@ var ScrimController = class {
|
|
|
1072
903
|
interactive: opts.interactive
|
|
1073
904
|
});
|
|
1074
905
|
if (opts.range !== void 0) {
|
|
1075
|
-
this.
|
|
906
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
907
|
+
this.savedScrimRanges = {
|
|
908
|
+
screen: opts.range,
|
|
909
|
+
backdrop: this.savedScrimRanges.backdrop
|
|
910
|
+
};
|
|
911
|
+
} else {
|
|
912
|
+
this.screenRange = opts.range;
|
|
913
|
+
}
|
|
1076
914
|
}
|
|
1077
915
|
this.invalidateOpacityCache();
|
|
1078
916
|
applySize();
|
|
1079
917
|
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Inject a positioned floating element into the scrim layer (e.g. a close
|
|
1082
|
-
* button anchored top-right). Replaces any prior overlay so repeated calls
|
|
1083
|
-
* (reactive effects re-running) don't accumulate wrappers.
|
|
1084
|
-
* @param opts - Children element, optional `inset` (default `"16px"`),
|
|
1085
|
-
* `position` (9-way grid, default `"top-right"`), and
|
|
1086
|
-
* `interactive` flag (default `true`).
|
|
1087
|
-
* @returns Teardown function that removes the wrapper from the DOM.
|
|
1088
|
-
* @example
|
|
1089
|
-
* const teardown = engine.setScrimOverlay({
|
|
1090
|
-
* children: closeBtn,
|
|
1091
|
-
* position: "top-right",
|
|
1092
|
-
* inset: "20px",
|
|
1093
|
-
* });
|
|
1094
|
-
* // later: teardown(); // remove the floating button
|
|
1095
|
-
*/
|
|
1096
918
|
setScrimOverlay(opts) {
|
|
1097
919
|
if (this.isDestroyed()) return () => {
|
|
1098
920
|
};
|
|
@@ -1100,7 +922,18 @@ var ScrimController = class {
|
|
|
1100
922
|
};
|
|
1101
923
|
this.scrimOverlayTeardown?.();
|
|
1102
924
|
const screen = this.screenComponent;
|
|
1103
|
-
const
|
|
925
|
+
const insetRaw = opts.inset ?? "16px";
|
|
926
|
+
const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
|
|
927
|
+
if (inset !== insetRaw) {
|
|
928
|
+
devWarn(
|
|
929
|
+
`[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
|
|
933
|
+
devWarn(
|
|
934
|
+
"[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."
|
|
935
|
+
);
|
|
936
|
+
}
|
|
1104
937
|
const position = opts.position ?? "top-right";
|
|
1105
938
|
const interactive = opts.interactive ?? true;
|
|
1106
939
|
const wrapper = screen.ownerDocument.createElement("div");
|
|
@@ -1149,12 +982,30 @@ var ScrimController = class {
|
|
|
1149
982
|
ws.bottom = inset;
|
|
1150
983
|
ws.right = inset;
|
|
1151
984
|
break;
|
|
985
|
+
case "sheet-top-left":
|
|
986
|
+
case "sheet-top-center":
|
|
987
|
+
case "sheet-top-right":
|
|
988
|
+
applySheetAnchoredPosition(
|
|
989
|
+
ws,
|
|
990
|
+
this.mode,
|
|
991
|
+
position,
|
|
992
|
+
inset
|
|
993
|
+
);
|
|
994
|
+
break;
|
|
1152
995
|
}
|
|
1153
996
|
if (interactive) {
|
|
1154
997
|
ws.pointerEvents = "auto";
|
|
1155
998
|
}
|
|
1156
999
|
wrapper.appendChild(opts.children);
|
|
1157
|
-
screen.
|
|
1000
|
+
const host = screen.parentElement;
|
|
1001
|
+
if (!host) {
|
|
1002
|
+
devWarn(
|
|
1003
|
+
"[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
|
|
1004
|
+
);
|
|
1005
|
+
return () => {
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
host.appendChild(wrapper);
|
|
1158
1009
|
const teardown = () => {
|
|
1159
1010
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
1160
1011
|
if (this.scrimOverlayTeardown === teardown) {
|
|
@@ -1164,7 +1015,6 @@ var ScrimController = class {
|
|
|
1164
1015
|
this.scrimOverlayTeardown = teardown;
|
|
1165
1016
|
return teardown;
|
|
1166
1017
|
}
|
|
1167
|
-
/** Tear down toggle-state listeners + remove any injected overlay wrapper. */
|
|
1168
1018
|
destroy() {
|
|
1169
1019
|
this.detachScrimTap?.();
|
|
1170
1020
|
this.detachScrimTap = null;
|
|
@@ -1183,8 +1033,6 @@ var ScrimController = class {
|
|
|
1183
1033
|
this.lastBackdropPointer = "";
|
|
1184
1034
|
this.lastScreenDisplay = "";
|
|
1185
1035
|
}
|
|
1186
|
-
// ── Private helpers ────────────────────────────────────────────────
|
|
1187
|
-
/** Centralized scrim style writer — atomic 3-property write across all setters. */
|
|
1188
1036
|
applyScrimStyles(opts) {
|
|
1189
1037
|
if (!this.screenComponent) return;
|
|
1190
1038
|
const s = this.screenComponent.style;
|
|
@@ -1206,7 +1054,6 @@ var ScrimController = class {
|
|
|
1206
1054
|
s.pointerEvents = opts.interactive ? "auto" : "none";
|
|
1207
1055
|
}
|
|
1208
1056
|
}
|
|
1209
|
-
/** Pin the screen overlay to the area NOT occupied by the sheet. */
|
|
1210
1057
|
applyAboveSheetInset() {
|
|
1211
1058
|
if (!this.screenComponent) return;
|
|
1212
1059
|
const insetByMode = {
|
|
@@ -1221,6 +1068,35 @@ var ScrimController = class {
|
|
|
1221
1068
|
s.pointerEvents = "none";
|
|
1222
1069
|
}
|
|
1223
1070
|
};
|
|
1071
|
+
function resolveSheetAnchoredStyle(mode, position, inset) {
|
|
1072
|
+
const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
|
|
1073
|
+
switch (mode) {
|
|
1074
|
+
case "bottom":
|
|
1075
|
+
if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
|
|
1076
|
+
if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
|
|
1077
|
+
return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
1078
|
+
case "top":
|
|
1079
|
+
if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
|
|
1080
|
+
if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
|
|
1081
|
+
return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
1082
|
+
case "left":
|
|
1083
|
+
if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
|
|
1084
|
+
if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
|
|
1085
|
+
return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
1086
|
+
case "right":
|
|
1087
|
+
if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
|
|
1088
|
+
if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
|
|
1089
|
+
return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function applySheetAnchoredPosition(ws, mode, position, inset) {
|
|
1093
|
+
const style = resolveSheetAnchoredStyle(mode, position, inset);
|
|
1094
|
+
if (style.top !== void 0) ws.top = style.top;
|
|
1095
|
+
if (style.bottom !== void 0) ws.bottom = style.bottom;
|
|
1096
|
+
if (style.left !== void 0) ws.left = style.left;
|
|
1097
|
+
if (style.right !== void 0) ws.right = style.right;
|
|
1098
|
+
if (style.transform !== void 0) ws.transform = style.transform;
|
|
1099
|
+
}
|
|
1224
1100
|
|
|
1225
1101
|
// src/core/animation/animation.ts
|
|
1226
1102
|
var easeOutBack = (t) => {
|
|
@@ -1377,13 +1253,9 @@ var AnimationRunner = class {
|
|
|
1377
1253
|
__publicField(this, "duration");
|
|
1378
1254
|
__publicField(this, "easing");
|
|
1379
1255
|
__publicField(this, "respectReducedMotion");
|
|
1380
|
-
/** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
|
|
1381
1256
|
__publicField(this, "reducedMotion", false);
|
|
1382
|
-
/** Disposes the matchMedia change listener — drained from destroy(). */
|
|
1383
1257
|
__publicField(this, "detachReducedMotion", null);
|
|
1384
|
-
/** Computed once — both inputs (option + API presence) stable for life. */
|
|
1385
1258
|
__publicField(this, "viewTransitionsAvailable");
|
|
1386
|
-
/** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
|
|
1387
1259
|
__publicField(this, "currentTween", null);
|
|
1388
1260
|
__publicField(this, "currentSpring", null);
|
|
1389
1261
|
this.element = deps.element;
|
|
@@ -1414,26 +1286,13 @@ var AnimationRunner = class {
|
|
|
1414
1286
|
}
|
|
1415
1287
|
}
|
|
1416
1288
|
}
|
|
1417
|
-
/**
|
|
1418
|
-
* Live derived flag — `true` whenever a spring/tween handle is in flight.
|
|
1419
|
-
* Reading the canonical handles (instead of mirroring into a separate
|
|
1420
|
-
* boolean) eliminates the reduced-motion early-return latent bug where
|
|
1421
|
-
* the flag stayed true forever, and removes the cross-controller
|
|
1422
|
-
* setIsAnimating callback.
|
|
1423
|
-
*/
|
|
1424
1289
|
get isAnimating() {
|
|
1425
1290
|
return this.currentTween !== null || this.currentSpring !== null;
|
|
1426
1291
|
}
|
|
1427
|
-
/** Cancel any in-flight tween/spring without touching engine state. */
|
|
1428
1292
|
cancel() {
|
|
1429
1293
|
this.currentTween?.cancel();
|
|
1430
1294
|
this.currentSpring?.cancel();
|
|
1431
1295
|
}
|
|
1432
|
-
/**
|
|
1433
|
-
* Animate from current size to `target`. Resolves when the animation
|
|
1434
|
-
* completes; resolves early if cancelled mid-flight (caller checks an
|
|
1435
|
-
* abort signal post-await for cycle-invariant correctness).
|
|
1436
|
-
*/
|
|
1437
1296
|
async animateTo(target, velocityPxPerMs) {
|
|
1438
1297
|
if (target === this.getSize()) return;
|
|
1439
1298
|
this.currentTween?.cancel();
|
|
@@ -1474,7 +1333,6 @@ var AnimationRunner = class {
|
|
|
1474
1333
|
if (!this.isDragging()) this.element.style.willChange = "auto";
|
|
1475
1334
|
}
|
|
1476
1335
|
}
|
|
1477
|
-
/** Tear down the matchMedia listener. Engine calls from destroy(). */
|
|
1478
1336
|
destroy() {
|
|
1479
1337
|
this.detachReducedMotion?.();
|
|
1480
1338
|
this.detachReducedMotion = null;
|
|
@@ -1634,12 +1492,8 @@ var LifecycleController = class {
|
|
|
1634
1492
|
__publicField(this, "inertSiblings");
|
|
1635
1493
|
__publicField(this, "shouldApplyInertSiblings");
|
|
1636
1494
|
__publicField(this, "returnFocus");
|
|
1637
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
1638
1495
|
__publicField(this, "releaseFocusTrap", null);
|
|
1639
1496
|
__publicField(this, "releaseScrollLock", null);
|
|
1640
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
1641
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
1642
|
-
* released or never-installed instances. */
|
|
1643
1497
|
__publicField(this, "installed", false);
|
|
1644
1498
|
__publicField(this, "destroyed", false);
|
|
1645
1499
|
this.element = deps.element;
|
|
@@ -1653,14 +1507,6 @@ var LifecycleController = class {
|
|
|
1653
1507
|
this.returnFocus = opts.returnFocus;
|
|
1654
1508
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
1655
1509
|
}
|
|
1656
|
-
/**
|
|
1657
|
-
* Run the open-time install steps. Idempotent: each step checks its
|
|
1658
|
-
* existing teardown ref so a re-open during a half-cancelled close
|
|
1659
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
1660
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
1661
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
1662
|
-
* No-op when destroyed.
|
|
1663
|
-
*/
|
|
1664
1510
|
install() {
|
|
1665
1511
|
if (this.destroyed) return;
|
|
1666
1512
|
this.installed = true;
|
|
@@ -1677,22 +1523,10 @@ var LifecycleController = class {
|
|
|
1677
1523
|
this.inertSiblings.apply();
|
|
1678
1524
|
}
|
|
1679
1525
|
}
|
|
1680
|
-
/**
|
|
1681
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
1682
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
1683
|
-
* on the next `release()`. No-op when destroyed.
|
|
1684
|
-
*/
|
|
1685
1526
|
setReturnFocus(target) {
|
|
1686
1527
|
if (this.destroyed) return;
|
|
1687
1528
|
this.returnFocus = target;
|
|
1688
1529
|
}
|
|
1689
|
-
/**
|
|
1690
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
1691
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
1692
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
1693
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
1694
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
1695
|
-
*/
|
|
1696
1530
|
release() {
|
|
1697
1531
|
if (!this.installed) return;
|
|
1698
1532
|
this.installed = false;
|
|
@@ -1706,12 +1540,6 @@ var LifecycleController = class {
|
|
|
1706
1540
|
target?.focus?.();
|
|
1707
1541
|
}
|
|
1708
1542
|
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
1711
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
1712
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
1713
|
-
* restore.
|
|
1714
|
-
*/
|
|
1715
1543
|
destroy() {
|
|
1716
1544
|
this.release();
|
|
1717
1545
|
this.destroyed = true;
|
|
@@ -1721,12 +1549,10 @@ var LifecycleController = class {
|
|
|
1721
1549
|
// src/core/gestures.ts
|
|
1722
1550
|
var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
|
|
1723
1551
|
var SampleRing = class {
|
|
1724
|
-
// current count, ≤ capacity
|
|
1725
1552
|
constructor(capacity) {
|
|
1726
1553
|
__publicField(this, "slots");
|
|
1727
1554
|
__publicField(this, "capacity");
|
|
1728
1555
|
__publicField(this, "head", 0);
|
|
1729
|
-
// next write index
|
|
1730
1556
|
__publicField(this, "size", 0);
|
|
1731
1557
|
this.capacity = capacity;
|
|
1732
1558
|
this.slots = new Array(capacity);
|
|
@@ -1739,12 +1565,6 @@ var SampleRing = class {
|
|
|
1739
1565
|
this.head = (this.head + 1) % this.capacity;
|
|
1740
1566
|
if (this.size < this.capacity) this.size++;
|
|
1741
1567
|
}
|
|
1742
|
-
/**
|
|
1743
|
-
* Drop oldest entries whose timestamp is below `cutoff`, but never shrink
|
|
1744
|
-
* below `minKeep`. Mirrors the original
|
|
1745
|
-
* `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
|
|
1746
|
-
* but without copying — just advances the logical tail.
|
|
1747
|
-
*/
|
|
1748
1568
|
pruneBefore(cutoff, minKeep) {
|
|
1749
1569
|
while (this.size > minKeep) {
|
|
1750
1570
|
const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
|
|
@@ -1881,28 +1701,14 @@ var GestureController = class {
|
|
|
1881
1701
|
__publicField(this, "detach", null);
|
|
1882
1702
|
__publicField(this, "isDragging_", false);
|
|
1883
1703
|
__publicField(this, "dragStartSize", 0);
|
|
1884
|
-
// Only "touch" gestures are eligible for soft-keyboard dismissal —
|
|
1885
|
-
// mouse/trackpad users don't have a keyboard to dismiss.
|
|
1886
1704
|
__publicField(this, "currentPointerKind", "touch");
|
|
1887
|
-
// One blur per drag — flipping focus every move event races the keyboard's
|
|
1888
|
-
// own animations on iOS and makes the page jitter.
|
|
1889
1705
|
__publicField(this, "keyboardDismissed", false);
|
|
1890
|
-
// Reused payload object for the "drag" event — mutated each onMove instead
|
|
1891
|
-
// of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
|
|
1892
|
-
// would otherwise churn tens of KB through the GC. Mirrors browser
|
|
1893
|
-
// PointerEvent semantics: consumers must NOT retain the reference across
|
|
1894
|
-
// frames (clone if needed). See SheetEventMap.drag JSDoc.
|
|
1895
1706
|
__publicField(this, "dragPayload", { size: 0, delta: 0 });
|
|
1896
1707
|
this.deps = deps;
|
|
1897
1708
|
}
|
|
1898
|
-
/** True between `dragstart` and `dragend`/`onCancel`. */
|
|
1899
1709
|
get isDragging() {
|
|
1900
1710
|
return this.isDragging_;
|
|
1901
1711
|
}
|
|
1902
|
-
/**
|
|
1903
|
-
* Bind pointer listeners on the handle and return a teardown. Engine pushes
|
|
1904
|
-
* the teardown into its TeardownStack so destroy() drains it once.
|
|
1905
|
-
*/
|
|
1906
1712
|
install() {
|
|
1907
1713
|
if (this.detach) return this.detach;
|
|
1908
1714
|
this.detach = installGestures(this.deps.handle, this.deps.mode, {
|
|
@@ -1959,11 +1765,6 @@ var GestureController = class {
|
|
|
1959
1765
|
});
|
|
1960
1766
|
return this.detach;
|
|
1961
1767
|
}
|
|
1962
|
-
/**
|
|
1963
|
-
* Force-clear drag visual state. Engine calls from destroy() when destroyed
|
|
1964
|
-
* mid-drag — gesture detach won't synthesize an onCancel, so we have to
|
|
1965
|
-
* roll back the data-attribute + will-change ourselves.
|
|
1966
|
-
*/
|
|
1967
1768
|
forceClearDragState() {
|
|
1968
1769
|
if (!this.isDragging_) return;
|
|
1969
1770
|
this.isDragging_ = false;
|
|
@@ -1982,11 +1783,6 @@ var AriaSliderWriter = class {
|
|
|
1982
1783
|
mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
|
|
1983
1784
|
);
|
|
1984
1785
|
}
|
|
1985
|
-
/**
|
|
1986
|
-
* Update slider position. `allowedIds` length determines `aria-valuemax`;
|
|
1987
|
-
* `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
|
|
1988
|
-
* Empty list → strip every dynamic attr so AT skips the handle entirely.
|
|
1989
|
-
*/
|
|
1990
1786
|
setValue(allowedIds, activeId) {
|
|
1991
1787
|
if (allowedIds.length === 0) {
|
|
1992
1788
|
this.handle.removeAttribute("aria-valuemin");
|
|
@@ -2060,9 +1856,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2060
1856
|
scrimMode: opts.scrimMode,
|
|
2061
1857
|
scrimColor: opts.scrimColor,
|
|
2062
1858
|
scrimBlur: opts.scrimBlur,
|
|
2063
|
-
// Auto-promote pointer-events when tap-to-close is on so the click
|
|
2064
|
-
// listener actually receives events. Caller can still force `false`
|
|
2065
|
-
// explicitly to opt out — `??` only kicks in for `undefined`.
|
|
2066
1859
|
scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
|
|
2067
1860
|
scrimTapToClose: opts.scrimTapToClose,
|
|
2068
1861
|
scrimPreset: opts.scrimPreset,
|
|
@@ -2083,10 +1876,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2083
1876
|
closeOnEscape: opts.closeOnEscape,
|
|
2084
1877
|
lockBodyScroll: opts.lockBodyScroll,
|
|
2085
1878
|
inertSiblings: opts.inertSiblings,
|
|
2086
|
-
// Folded in from `extras` so the lifecycle bag is fully assembled at
|
|
2087
|
-
// the call site — engine no longer needs a spread+override at
|
|
2088
|
-
// controller construction. When `extras` is omitted, the field stays
|
|
2089
|
-
// undefined and the controller uses its own default predicate.
|
|
2090
1879
|
shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
|
|
2091
1880
|
}
|
|
2092
1881
|
};
|
|
@@ -2121,6 +1910,7 @@ var BottomSheetEngine = class {
|
|
|
2121
1910
|
__publicField(this, "scrollContainer");
|
|
2122
1911
|
__publicField(this, "backdrop");
|
|
2123
1912
|
__publicField(this, "screenComponent");
|
|
1913
|
+
__publicField(this, "scrimParent", null);
|
|
2124
1914
|
__publicField(this, "mode");
|
|
2125
1915
|
__publicField(this, "flickVelocity");
|
|
2126
1916
|
__publicField(this, "dragThreshold");
|
|
@@ -2131,40 +1921,22 @@ var BottomSheetEngine = class {
|
|
|
2131
1921
|
__publicField(this, "lifecycle");
|
|
2132
1922
|
__publicField(this, "closeOnBack");
|
|
2133
1923
|
__publicField(this, "routedTo");
|
|
2134
|
-
// Engine retains `snapPointsRaw` for the "find a closed-id" query in
|
|
2135
|
-
// close() (raw input, not resolved geometry). Resolver owns geometry.
|
|
2136
1924
|
__publicField(this, "snapPointsRaw");
|
|
2137
1925
|
__publicField(this, "snaps");
|
|
2138
1926
|
__publicField(this, "activeId");
|
|
2139
1927
|
__publicField(this, "size", 0);
|
|
2140
|
-
// Constructed in attach(); a plugin's install() could read `state` before
|
|
2141
|
-
// attach completes if the constructor throws partway through. State getter
|
|
2142
|
-
// null-guards via `this.gesture?.isDragging ?? false`.
|
|
2143
1928
|
__publicField(this, "gesture");
|
|
2144
1929
|
__publicField(this, "rootEl", null);
|
|
2145
1930
|
__publicField(this, "destroyed", false);
|
|
2146
|
-
// In-flight ViewTransition handle. A second snapTo while a VT is in flight
|
|
2147
|
-
// would otherwise let Chromium reject the second's promise and flicker —
|
|
2148
|
-
// newCycle() calls skipTransition() to abort cleanly.
|
|
2149
1931
|
__publicField(this, "currentViewTransition", null);
|
|
2150
|
-
// Each cycle captures the signal at entry; when a newer cycle aborts the
|
|
2151
|
-
// controller, the captured signal flips to `aborted=true` and post-await
|
|
2152
|
-
// guards short-circuit (prevents stale snap/open/close emits).
|
|
2153
1932
|
__publicField(this, "currentAbort", new AbortController());
|
|
2154
|
-
// Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
|
|
2155
|
-
// visually distinguishable; gating CSSOM writes drops 30-50% of writes
|
|
2156
|
-
// during the spring's settle tail.
|
|
2157
1933
|
__publicField(this, "sizeWriteSentinel", new WriteSentinel());
|
|
2158
1934
|
__publicField(this, "progressWriteSentinel", new WriteSentinel());
|
|
2159
1935
|
__publicField(this, "isTopSheet", true);
|
|
2160
|
-
// Pooled payload — identity documented in SheetEventMap.progress JSDoc;
|
|
2161
|
-
// consumers must not retain across frames.
|
|
2162
1936
|
__publicField(this, "progressPayload", {
|
|
2163
1937
|
value: 0,
|
|
2164
1938
|
size: 0
|
|
2165
1939
|
});
|
|
2166
|
-
// Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
|
|
2167
|
-
// Inner `range` object identity is also stable (mutated in place).
|
|
2168
1940
|
__publicField(this, "dragContextBuf", {
|
|
2169
1941
|
size: 0,
|
|
2170
1942
|
maxAxisSize: 0,
|
|
@@ -2175,13 +1947,7 @@ var BottomSheetEngine = class {
|
|
|
2175
1947
|
__publicField(this, "persistKey");
|
|
2176
1948
|
__publicField(this, "linkedSheets", []);
|
|
2177
1949
|
__publicField(this, "scrollCache");
|
|
2178
|
-
// Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
|
|
2179
|
-
// tear down first before the infrastructure they depend on. Toggle-semantic
|
|
2180
|
-
// listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
|
|
2181
|
-
// fields on their controllers.
|
|
2182
1950
|
__publicField(this, "teardowns", new TeardownStack());
|
|
2183
|
-
// Pre-computed transform template — built once per engine since `mode` is
|
|
2184
|
-
// fixed for its lifetime. Avoids the 4-arm switch at 60-120 Hz.
|
|
2185
1951
|
__publicField(this, "transformTemplate");
|
|
2186
1952
|
const resolved = resolveEngineOptions(opts, {
|
|
2187
1953
|
shouldApplyInertSiblings: () => {
|
|
@@ -2196,7 +1962,8 @@ var BottomSheetEngine = class {
|
|
|
2196
1962
|
this.handle = opts.handle ?? opts.element;
|
|
2197
1963
|
this.scrollContainer = opts.scrollContainer;
|
|
2198
1964
|
this.backdrop = opts.backdrop;
|
|
2199
|
-
this.screenComponent = opts.screenComponent;
|
|
1965
|
+
this.screenComponent = opts.scrim ?? opts.screenComponent;
|
|
1966
|
+
this.scrimParent = this.screenComponent?.parentElement ?? null;
|
|
2200
1967
|
this.mode = resolved.mode;
|
|
2201
1968
|
this.transformTemplate = buildTransformTemplate(this.mode);
|
|
2202
1969
|
this.aria = new AriaSliderWriter(this.handle, this.mode);
|
|
@@ -2224,8 +1991,6 @@ var BottomSheetEngine = class {
|
|
|
2224
1991
|
getRootEl: () => this.rootEl,
|
|
2225
1992
|
applySize: (size) => this.applySize(size),
|
|
2226
1993
|
getSize: () => this.size,
|
|
2227
|
-
// Optional chain protects synthetic test paths that drive animateTo
|
|
2228
|
-
// before attach() (gesture is constructed there).
|
|
2229
1994
|
isDragging: () => this.gesture?.isDragging ?? false
|
|
2230
1995
|
},
|
|
2231
1996
|
resolved.animation
|
|
@@ -2330,21 +2095,6 @@ var BottomSheetEngine = class {
|
|
|
2330
2095
|
on(event, fn) {
|
|
2331
2096
|
return this.bus.on(event, fn);
|
|
2332
2097
|
}
|
|
2333
|
-
/**
|
|
2334
|
-
* Register a plugin. `install` runs synchronously; if it returns a function,
|
|
2335
|
-
* it's captured for teardown during `destroy()`. No-op if already destroyed
|
|
2336
|
-
* — installing into a torn-down engine would leak listeners.
|
|
2337
|
-
*
|
|
2338
|
-
* Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
|
|
2339
|
-
* so a buggy plugin can't take the engine down with it: the engine stays
|
|
2340
|
-
* usable, sibling plugins still install, and the error surfaces in dev tools
|
|
2341
|
-
* on the next tick instead of being swallowed. Mirrors the per-fn isolation
|
|
2342
|
-
* model used for teardowns.
|
|
2343
|
-
*
|
|
2344
|
-
* Plugins that accept the second `scope` argument get transactional cleanup:
|
|
2345
|
-
* push partial-install cleanups via `scope.add(fn)` and the engine drains
|
|
2346
|
-
* them on throw, OR merges them into destroy-time teardowns on success.
|
|
2347
|
-
*/
|
|
2348
2098
|
use(plugin) {
|
|
2349
2099
|
if (this.destroyed) {
|
|
2350
2100
|
console.warn(
|
|
@@ -2354,9 +2104,6 @@ var BottomSheetEngine = class {
|
|
|
2354
2104
|
}
|
|
2355
2105
|
const scoped = [];
|
|
2356
2106
|
const scope = {
|
|
2357
|
-
// typeof check — not just truthiness — so a JS-only plugin pushing a
|
|
2358
|
-
// bogus value is silently rejected instead of queueing a confusing
|
|
2359
|
-
// TypeError on destroy() drain.
|
|
2360
2107
|
add: (fn) => {
|
|
2361
2108
|
if (typeof fn === "function") scoped.push(fn);
|
|
2362
2109
|
}
|
|
@@ -2385,36 +2132,13 @@ var BottomSheetEngine = class {
|
|
|
2385
2132
|
}
|
|
2386
2133
|
return this;
|
|
2387
2134
|
}
|
|
2388
|
-
/**
|
|
2389
|
-
* Replace the linked-sheets list at runtime. Use when peer engines aren't
|
|
2390
|
-
* available at construction time (mounted later in the tree). The wiring
|
|
2391
|
-
* to the `open` event is independent of this list — it reads `linkedSheets`
|
|
2392
|
-
* lazily on emit, so this setter is a simple field swap.
|
|
2393
|
-
*/
|
|
2394
2135
|
setLinkedSheets(sheets) {
|
|
2395
2136
|
if (this.destroyed) return;
|
|
2396
2137
|
this.linkedSheets = sheets;
|
|
2397
2138
|
}
|
|
2398
|
-
/** Read-only view of the current allow-list, for cross-sheet target negotiation. */
|
|
2399
2139
|
getAllowedIds() {
|
|
2400
2140
|
return this.snaps.getAllowedIds().slice();
|
|
2401
2141
|
}
|
|
2402
|
-
/**
|
|
2403
|
-
* Animate to a snap point by id. Resolves when the animation completes.
|
|
2404
|
-
*
|
|
2405
|
-
* Two call shapes:
|
|
2406
|
-
* ```ts
|
|
2407
|
-
* engine.snapTo("full"); // default velocity
|
|
2408
|
-
* engine.snapTo("full", 0.5); // px/ms initial velocity
|
|
2409
|
-
* engine.snapTo("full", { velocity: 0.5, signal }); // abortable
|
|
2410
|
-
* ```
|
|
2411
|
-
*
|
|
2412
|
-
* When `opts.signal` is provided, calling `controller.abort()` cancels the
|
|
2413
|
-
* in-flight tween/spring and the returned promise resolves without firing
|
|
2414
|
-
* `snap`. The captured `AbortSignal` is checked after each await so external
|
|
2415
|
-
* aborts ride the same cycle invariant as internal ones (resize, destroy,
|
|
2416
|
-
* concurrent snapTo).
|
|
2417
|
-
*/
|
|
2418
2142
|
async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
|
|
2419
2143
|
if (this.destroyed) return;
|
|
2420
2144
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2473,16 +2197,6 @@ var BottomSheetEngine = class {
|
|
|
2473
2197
|
this.handleClose();
|
|
2474
2198
|
}
|
|
2475
2199
|
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Drive the sheet to an arbitrary px size, bypassing snap matching.
|
|
2478
|
-
* Useful for preview scrubs, animation chaining, or programmatic gestures.
|
|
2479
|
-
* Animates with the configured spring/tween. Resolves when the animation
|
|
2480
|
-
* completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
|
|
2481
|
-
*
|
|
2482
|
-
* Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
|
|
2483
|
-
* nor `snap` (the sheet isn't settling to a known snap). `progress` events
|
|
2484
|
-
* still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
|
|
2485
|
-
*/
|
|
2486
2200
|
async dragTo(targetSize, velocityOrOpts = 0) {
|
|
2487
2201
|
if (this.destroyed) return;
|
|
2488
2202
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2520,20 +2234,10 @@ var BottomSheetEngine = class {
|
|
|
2520
2234
|
this.snaps.setAllowedIds(ids);
|
|
2521
2235
|
this.updateAriaSlider();
|
|
2522
2236
|
if (snap && ids.includes(snap)) {
|
|
2523
|
-
void this.snapTo(
|
|
2524
|
-
snap,
|
|
2525
|
-
0,
|
|
2526
|
-
/* internal */
|
|
2527
|
-
true
|
|
2528
|
-
);
|
|
2237
|
+
void this.snapTo(snap, 0, true);
|
|
2529
2238
|
} else if (!ids.includes(this.activeId)) {
|
|
2530
2239
|
const fallback = ids[0];
|
|
2531
|
-
if (fallback) void this.snapTo(
|
|
2532
|
-
fallback,
|
|
2533
|
-
0,
|
|
2534
|
-
/* internal */
|
|
2535
|
-
true
|
|
2536
|
-
);
|
|
2240
|
+
if (fallback) void this.snapTo(fallback, 0, true);
|
|
2537
2241
|
}
|
|
2538
2242
|
}
|
|
2539
2243
|
setBackdropRange(range) {
|
|
@@ -2551,21 +2255,6 @@ var BottomSheetEngine = class {
|
|
|
2551
2255
|
setScrimInteractive(interactive) {
|
|
2552
2256
|
this.scrim.setScrimInteractive(interactive);
|
|
2553
2257
|
}
|
|
2554
|
-
/**
|
|
2555
|
-
* Batch-update multiple scrim fields in one call. Apply order is:
|
|
2556
|
-
* 1. `preset` — fills baseline color/blur/range/interactive.
|
|
2557
|
-
* 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
|
|
2558
|
-
* 3. `enabled` — flips the opacity contribution on/off.
|
|
2559
|
-
* 4. `tapToClose` — installs/tears the click listener.
|
|
2560
|
-
* 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
|
|
2561
|
-
* 6. ONE `applySize` so the per-frame opacity stays consistent.
|
|
2562
|
-
*
|
|
2563
|
-
* Performance: collecting changes here (vs calling individual setters)
|
|
2564
|
-
* avoids repeated `applySize` calls during a multi-field swap. Setters
|
|
2565
|
-
* called here suppress their own internal `applySize` either by passing
|
|
2566
|
-
* through fast-paths (mode/enabled no-op when value unchanged) or by
|
|
2567
|
-
* being visual-only (color/blur/interactive don't influence opacity).
|
|
2568
|
-
*/
|
|
2569
2258
|
setScrim(opts) {
|
|
2570
2259
|
this.scrim.setScrim(opts, () => this.applySize(this.size));
|
|
2571
2260
|
}
|
|
@@ -2581,11 +2270,6 @@ var BottomSheetEngine = class {
|
|
|
2581
2270
|
setScrimOverlay(opts) {
|
|
2582
2271
|
return this.scrim.setScrimOverlay(opts);
|
|
2583
2272
|
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Read-only snapshot of scrim state. Use the `setScrim*` setters to mutate.
|
|
2586
|
-
* Stable public API — internals (e.g. cached opacity sentinels) are NOT
|
|
2587
|
-
* exposed so consumers can't accidentally couple to ScrimController shape.
|
|
2588
|
-
*/
|
|
2589
2273
|
getScrimState() {
|
|
2590
2274
|
return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
|
|
2591
2275
|
}
|
|
@@ -2631,24 +2315,11 @@ var BottomSheetEngine = class {
|
|
|
2631
2315
|
emit(event, payload) {
|
|
2632
2316
|
this.bus.emit(event, payload);
|
|
2633
2317
|
}
|
|
2634
|
-
/**
|
|
2635
|
-
* Abort the current cycle's signal and replace the controller. Returns the
|
|
2636
|
-
* fresh signal so the caller can capture it locally before any await — when
|
|
2637
|
-
* a future newCycle() / destroy aborts our controller, the captured signal
|
|
2638
|
-
* flips to aborted and post-await guards short-circuit.
|
|
2639
|
-
*/
|
|
2640
2318
|
newCycle() {
|
|
2641
2319
|
this.currentAbort.abort();
|
|
2642
2320
|
this.currentAbort = new AbortController();
|
|
2643
2321
|
return this.currentAbort.signal;
|
|
2644
2322
|
}
|
|
2645
|
-
/**
|
|
2646
|
-
* Fire `before-snap` and report whether a listener cancelled it. The
|
|
2647
|
-
* `cancel()` callback exposed to listeners is frozen after the synchronous
|
|
2648
|
-
* emit phase so async callers can't silently miss — they get a console
|
|
2649
|
-
* warning. Returns `true` when cancelled so callers can early-return /
|
|
2650
|
-
* spring-back without re-implementing the cancel/frozen pattern.
|
|
2651
|
-
*/
|
|
2652
2323
|
emitBeforeSnap(target, previousId) {
|
|
2653
2324
|
let cancelled = false;
|
|
2654
2325
|
let frozen = false;
|
|
@@ -2682,30 +2353,9 @@ var BottomSheetEngine = class {
|
|
|
2682
2353
|
this.snaps.setRaw(this.snapPointsRaw);
|
|
2683
2354
|
this.scrim.invalidateOpacityCache();
|
|
2684
2355
|
}
|
|
2685
|
-
/**
|
|
2686
|
-
* Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
|
|
2687
|
-
* Engine wrapper kept so existing call sites don't all need updating;
|
|
2688
|
-
* resolver-level memoization survives across method invocations.
|
|
2689
|
-
*/
|
|
2690
2356
|
getAllowedRange() {
|
|
2691
2357
|
return this.snaps.getAllowedRange();
|
|
2692
2358
|
}
|
|
2693
|
-
/**
|
|
2694
|
-
* Write ARIA slider value attributes onto the handle so the engine itself
|
|
2695
|
-
* is the source of truth for screen-reader announcements. Called from the
|
|
2696
|
-
* constructor and from every code path that mutates `activeId` /
|
|
2697
|
-
* `allowedIds`. WAI-ARIA 1.2 requires `aria-valuenow` for `role="slider"` —
|
|
2698
|
-
* without it screen readers can't announce drag/snap progress to keyboard
|
|
2699
|
-
* + AT users (WCAG 4.1.2 Name, Role, Value).
|
|
2700
|
-
*
|
|
2701
|
-
* Engine does NOT set `role="slider"` itself — that's the adapter's or
|
|
2702
|
-
* consumer markup's responsibility. We only decorate with dynamic values.
|
|
2703
|
-
*
|
|
2704
|
-
* Empty allowed-list is malformed input (settable via `setAllowed([])`);
|
|
2705
|
-
* short-circuit to remove ARIA value attributes so screen readers see a
|
|
2706
|
-
* no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
|
|
2707
|
-
* requires `valuemax > valuemin` for `role="slider"`).
|
|
2708
|
-
*/
|
|
2709
2359
|
updateAriaSlider() {
|
|
2710
2360
|
this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
|
|
2711
2361
|
}
|
|
@@ -2730,11 +2380,6 @@ var BottomSheetEngine = class {
|
|
|
2730
2380
|
element: this.element,
|
|
2731
2381
|
mode: this.mode,
|
|
2732
2382
|
getRoot: () => this.rootEl,
|
|
2733
|
-
// Single coalesced reader — narrows the DI surface and removes 3 getter
|
|
2734
|
-
// dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
|
|
2735
|
-
// (this.dragContextBuf) — see field JSDoc for the retention contract.
|
|
2736
|
-
// Inner `range` object is mutated in place too so callers caching its
|
|
2737
|
-
// identity between frames stay valid.
|
|
2738
2383
|
getDragContext: () => {
|
|
2739
2384
|
const buf = this.dragContextBuf;
|
|
2740
2385
|
const r = this.getAllowedRange();
|
|
@@ -2900,7 +2545,6 @@ var BottomSheetEngine = class {
|
|
|
2900
2545
|
}
|
|
2901
2546
|
});
|
|
2902
2547
|
}
|
|
2903
|
-
/** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
|
|
2904
2548
|
haptic() {
|
|
2905
2549
|
if (typeof navigator !== "undefined" && "vibrate" in navigator) {
|
|
2906
2550
|
try {
|
|
@@ -2916,6 +2560,9 @@ var BottomSheetEngine = class {
|
|
|
2916
2560
|
style.transform = this.transformTemplate(offset);
|
|
2917
2561
|
if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
|
|
2918
2562
|
style.setProperty("--bs-size", `${size}px`);
|
|
2563
|
+
if (this.scrimParent) {
|
|
2564
|
+
this.scrimParent.style.setProperty("--bs-size", `${size}px`);
|
|
2565
|
+
}
|
|
2919
2566
|
}
|
|
2920
2567
|
const progress = this.computeProgress(size);
|
|
2921
2568
|
const progressChanged = this.progressWriteSentinel.shouldWrite(
|
|
@@ -3047,8 +2694,6 @@ var OverlayEngine = class {
|
|
|
3047
2694
|
__publicField(this, "exitEasing");
|
|
3048
2695
|
__publicField(this, "backdropOpacity");
|
|
3049
2696
|
__publicField(this, "backdropFilter");
|
|
3050
|
-
// Definite-assignment: written via assignSwipeFromConfig in the
|
|
3051
|
-
// constructor — TS can't follow the helper, mark the assertion explicit.
|
|
3052
2697
|
__publicField(this, "swipeToCloseEnabled");
|
|
3053
2698
|
__publicField(this, "swipeThreshold");
|
|
3054
2699
|
__publicField(this, "swipeVelocityThreshold");
|
|
@@ -3068,13 +2713,7 @@ var OverlayEngine = class {
|
|
|
3068
2713
|
__publicField(this, "isOpen_", false);
|
|
3069
2714
|
__publicField(this, "isAnimating_", false);
|
|
3070
2715
|
__publicField(this, "destroyed", false);
|
|
3071
|
-
// Tracks whether setOverlayChildren ever populated the panel — lets
|
|
3072
|
-
// clearOverlayChildren skip the DOM mutation when nothing was injected
|
|
3073
|
-
// (so the field also acts as a zero-allocation feature flag).
|
|
3074
2716
|
__publicField(this, "hasInjectedChildren", false);
|
|
3075
|
-
// Bumped on every open/close. awaitTransition captures it and ignores
|
|
3076
|
-
// transitionend/timeout fired by older cycles — prevents cross-cycle
|
|
3077
|
-
// listener bleed during rapid toggling.
|
|
3078
2717
|
__publicField(this, "cycleNonce", 0);
|
|
3079
2718
|
__publicField(this, "bus", createEventBus());
|
|
3080
2719
|
__publicField(this, "detachEscape", null);
|
|
@@ -3127,9 +2766,6 @@ var OverlayEngine = class {
|
|
|
3127
2766
|
closeOnEscape: this.closeOnEscape,
|
|
3128
2767
|
lockBodyScroll: this.bodyScrollLockEnabled,
|
|
3129
2768
|
inertSiblings: this.inertSiblingsEnabled,
|
|
3130
|
-
// Overlay-specific guard: only paint body siblings inert when the
|
|
3131
|
-
// overlay element is actually a body descendant. Detached / shadow-
|
|
3132
|
-
// DOM / portal overlays would otherwise lock unrelated trees.
|
|
3133
2769
|
shouldApplyInertSiblings: () => {
|
|
3134
2770
|
const bodyChildren = Array.from(document.body.children);
|
|
3135
2771
|
return bodyChildren.some(
|
|
@@ -3283,12 +2919,6 @@ var OverlayEngine = class {
|
|
|
3283
2919
|
this.releaseStackEntry = null;
|
|
3284
2920
|
this.restoreMount();
|
|
3285
2921
|
}
|
|
3286
|
-
/**
|
|
3287
|
-
* Runtime visual setter — opacity is applied immediately when open so a
|
|
3288
|
-
* live-controls panel can preview the change without re-opening. When
|
|
3289
|
-
* closed we only stash the field; next open() picks it up so we don't
|
|
3290
|
-
* mutate hidden DOM (avoids paint thrash on detached panels).
|
|
3291
|
-
*/
|
|
3292
2922
|
setBackdropOpacity(opacity) {
|
|
3293
2923
|
if (this.destroyed) return;
|
|
3294
2924
|
this.backdropOpacity = opacity;
|
|
@@ -3296,11 +2926,6 @@ var OverlayEngine = class {
|
|
|
3296
2926
|
this.backdrop.style.opacity = String(opacity);
|
|
3297
2927
|
}
|
|
3298
2928
|
}
|
|
3299
|
-
/**
|
|
3300
|
-
* Runtime visual setter for `backdrop-filter`. `undefined` clears the
|
|
3301
|
-
* filter (mirrors the close() behaviour). Defers to next open() when the
|
|
3302
|
-
* overlay is currently closed.
|
|
3303
|
-
*/
|
|
3304
2929
|
setBackdropFilter(filter) {
|
|
3305
2930
|
if (this.destroyed) return;
|
|
3306
2931
|
this.backdropFilter = filter;
|
|
@@ -3314,12 +2939,6 @@ var OverlayEngine = class {
|
|
|
3314
2939
|
style.webkitBackdropFilter = "";
|
|
3315
2940
|
}
|
|
3316
2941
|
}
|
|
3317
|
-
/**
|
|
3318
|
-
* Toggle / reconfigure swipe-to-close at runtime. Re-resolves the flat
|
|
3319
|
-
* threshold fields, then installs or tears down the pointer listener so
|
|
3320
|
-
* the change applies to the CURRENT open cycle. Idempotent: safe to call
|
|
3321
|
-
* with the same config repeatedly.
|
|
3322
|
-
*/
|
|
3323
2942
|
setSwipeToClose(config) {
|
|
3324
2943
|
if (this.destroyed) return;
|
|
3325
2944
|
const wasEnabled = this.swipeToCloseEnabled;
|
|
@@ -3332,7 +2951,6 @@ var OverlayEngine = class {
|
|
|
3332
2951
|
this.detachSwipe = null;
|
|
3333
2952
|
}
|
|
3334
2953
|
}
|
|
3335
|
-
/** Animation setters only stage the field — applied on next open/close. */
|
|
3336
2954
|
setEnterAnimation(animation) {
|
|
3337
2955
|
if (this.destroyed) return;
|
|
3338
2956
|
this.enterAnimation = animation;
|
|
@@ -3341,23 +2959,15 @@ var OverlayEngine = class {
|
|
|
3341
2959
|
if (this.destroyed) return;
|
|
3342
2960
|
this.exitAnimation = animation;
|
|
3343
2961
|
}
|
|
3344
|
-
/** Updates the field used at next close() — no immediate side effect. */
|
|
3345
2962
|
setReturnFocus(target) {
|
|
3346
2963
|
if (this.destroyed) return;
|
|
3347
2964
|
this.lifecycle.setReturnFocus(target);
|
|
3348
2965
|
}
|
|
3349
|
-
/**
|
|
3350
|
-
* Batch update — preset first, then individual overrides. Applied as a
|
|
3351
|
-
* single DOM-write phase when open so live-controls don't trigger
|
|
3352
|
-
* cascading reflows. Safe to call when closed (defers to next open).
|
|
3353
|
-
*/
|
|
3354
2966
|
setOverlay(opts) {
|
|
3355
2967
|
if (this.destroyed) return;
|
|
3356
2968
|
const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
|
|
3357
2969
|
const merged = preset ? {
|
|
3358
2970
|
backdropOpacity: preset.backdropOpacity,
|
|
3359
|
-
// Preset's `undefined` filter means "no filter" — surface as null
|
|
3360
|
-
// so the merged step clears the field instead of inheriting old.
|
|
3361
2971
|
backdropFilter: preset.backdropFilter === void 0 ? null : preset.backdropFilter,
|
|
3362
2972
|
swipeToClose: preset.swipeToClose,
|
|
3363
2973
|
enterAnimation: preset.enterAnimation,
|
|
@@ -3416,30 +3026,17 @@ var OverlayEngine = class {
|
|
|
3416
3026
|
}
|
|
3417
3027
|
}
|
|
3418
3028
|
}
|
|
3419
|
-
/**
|
|
3420
|
-
* Runtime children injection — replace the panel's contents without
|
|
3421
|
-
* unmounting the engine. `replaceChildren()` is a single atomic DOM op,
|
|
3422
|
-
* so callers can swap content rapidly without intermediate flicker.
|
|
3423
|
-
* Function form is resolved exactly ONCE per call (cheap lazy content).
|
|
3424
|
-
*/
|
|
3425
3029
|
setOverlayChildren(children) {
|
|
3426
3030
|
if (this.destroyed) return;
|
|
3427
3031
|
const node = typeof children === "function" ? children() : children;
|
|
3428
3032
|
this.element.replaceChildren(node);
|
|
3429
3033
|
this.hasInjectedChildren = true;
|
|
3430
3034
|
}
|
|
3431
|
-
/**
|
|
3432
|
-
* Clear injected children. No-op when nothing was injected so consumers
|
|
3433
|
-
* who never used setOverlayChildren can call this defensively without
|
|
3434
|
-
* paying for a DOM mutation.
|
|
3435
|
-
*/
|
|
3436
3035
|
clearOverlayChildren() {
|
|
3437
3036
|
if (this.destroyed || !this.hasInjectedChildren) return;
|
|
3438
3037
|
this.element.replaceChildren();
|
|
3439
3038
|
this.hasInjectedChildren = false;
|
|
3440
3039
|
}
|
|
3441
|
-
// Shared swipe-config normalizer — used by constructor, setSwipeToClose,
|
|
3442
|
-
// and setOverlay so the union/object shape is decoded in exactly one place.
|
|
3443
3040
|
assignSwipeFromConfig(swipe) {
|
|
3444
3041
|
if (typeof swipe === "object" && swipe !== null) {
|
|
3445
3042
|
this.swipeToCloseEnabled = swipe.enabled ?? true;
|
|
@@ -3583,13 +3180,6 @@ var OverlayEngine = class {
|
|
|
3583
3180
|
this.detachOutsidePointer = null;
|
|
3584
3181
|
this.lifecycle.release();
|
|
3585
3182
|
}
|
|
3586
|
-
/**
|
|
3587
|
-
* Light pointer-driven dismiss. Tracks finger along the edge axis, follows
|
|
3588
|
-
* with translate, dismisses if past `swipeThreshold` (fraction of panel
|
|
3589
|
-
* size) or release velocity exceeds `swipeVelocityThreshold` (px/ms).
|
|
3590
|
-
* Pure CSS-transform during drag — no rAF or physics required since the
|
|
3591
|
-
* panel only moves one direction.
|
|
3592
|
-
*/
|
|
3593
3183
|
installSwipeToClose() {
|
|
3594
3184
|
const el = this.element;
|
|
3595
3185
|
const edge = this.edge;
|