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