@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/react.js
CHANGED
|
@@ -237,11 +237,6 @@ var SnapResolver = class {
|
|
|
237
237
|
this.allowedIds = allowed ?? raw.map((p) => p.id);
|
|
238
238
|
this.recompute();
|
|
239
239
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Re-resolve raw → resolved against the current viewport / fit-measure,
|
|
242
|
-
* recompute maxAxisSize, invalidate range cache, notify engine. Called on
|
|
243
|
-
* resize, orientationchange, visualViewport changes, and snap-list swaps.
|
|
244
|
-
*/
|
|
245
240
|
recompute() {
|
|
246
241
|
this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
|
|
247
242
|
this.maxAxisSize = this.resolved.reduce(
|
|
@@ -251,15 +246,10 @@ var SnapResolver = class {
|
|
|
251
246
|
this.rangeCache = null;
|
|
252
247
|
this.onMaxAxisSizeChange?.(this.maxAxisSize);
|
|
253
248
|
}
|
|
254
|
-
/** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
|
|
255
249
|
setRaw(raw) {
|
|
256
250
|
this.raw = raw;
|
|
257
251
|
this.recompute();
|
|
258
252
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Replace the allow-list. Range cache depends on `(resolvedSnaps ×
|
|
261
|
-
* allowedIds)` so flipping ids alone invalidates without a full recompute.
|
|
262
|
-
*/
|
|
263
253
|
setAllowedIds(ids) {
|
|
264
254
|
this.allowedIds = ids;
|
|
265
255
|
this.rangeCache = null;
|
|
@@ -267,11 +257,6 @@ var SnapResolver = class {
|
|
|
267
257
|
findById(id) {
|
|
268
258
|
return findById(id, this.resolved);
|
|
269
259
|
}
|
|
270
|
-
/**
|
|
271
|
-
* Pure drag-settle target resolution. Engine passes the gesture-specific
|
|
272
|
-
* fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
|
|
273
|
-
* `size`/`activeId` since those are still engine-owned.
|
|
274
|
-
*/
|
|
275
260
|
findDragSettleTarget(args) {
|
|
276
261
|
return findDragSettleTarget({
|
|
277
262
|
...args,
|
|
@@ -280,18 +265,9 @@ var SnapResolver = class {
|
|
|
280
265
|
maxAxisSize: this.maxAxisSize
|
|
281
266
|
});
|
|
282
267
|
}
|
|
283
|
-
/**
|
|
284
|
-
* Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
|
|
285
|
-
* applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
|
|
286
|
-
* to one recompute per geometry/allow-list change.
|
|
287
|
-
*/
|
|
288
268
|
getAllowedRange() {
|
|
289
269
|
return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
|
|
290
270
|
}
|
|
291
|
-
/**
|
|
292
|
-
* Read-only view of resolved snaps. Returned as `readonly` to discourage
|
|
293
|
-
* accidental mutation by feature install sites that close over this list.
|
|
294
|
-
*/
|
|
295
271
|
getResolvedSnaps() {
|
|
296
272
|
return this.resolved;
|
|
297
273
|
}
|
|
@@ -301,18 +277,6 @@ var SnapResolver = class {
|
|
|
301
277
|
getMaxAxisSize() {
|
|
302
278
|
return this.maxAxisSize;
|
|
303
279
|
}
|
|
304
|
-
/**
|
|
305
|
-
* External clamp override. The resize-observer feature compares
|
|
306
|
-
* `getMaxAxisSize()` against the live viewport and shrinks if the largest
|
|
307
|
-
* resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
|
|
308
|
-
* window resize). The next `recompute()` overwrites this clamp from the
|
|
309
|
-
* resolved-snap reduction.
|
|
310
|
-
*
|
|
311
|
-
* Does NOT invalidate the range cache — viewport clamping doesn't change
|
|
312
|
-
* `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
|
|
313
|
-
* so the resolver owns the layout-axis style write end-to-end (symmetric
|
|
314
|
-
* with `recompute()` — feature call sites no longer duplicate the write).
|
|
315
|
-
*/
|
|
316
280
|
setMaxAxisSize(n) {
|
|
317
281
|
this.maxAxisSize = n;
|
|
318
282
|
this.onMaxAxisSizeChange?.(n);
|
|
@@ -687,73 +651,49 @@ var WriteSentinel = class {
|
|
|
687
651
|
constructor() {
|
|
688
652
|
__publicField(this, "last", -1);
|
|
689
653
|
}
|
|
690
|
-
/**
|
|
691
|
-
* Returns `true` (and updates the cached value) if `next` differs from the
|
|
692
|
-
* cached `last` by more than `epsilon`. Returns `false` (and leaves the
|
|
693
|
-
* cache untouched) when the delta is within epsilon — caller skips the
|
|
694
|
-
* CSSOM write.
|
|
695
|
-
*/
|
|
696
654
|
shouldWrite(next, epsilon) {
|
|
697
655
|
if (Math.abs(next - this.last) <= epsilon) return false;
|
|
698
656
|
this.last = next;
|
|
699
657
|
return true;
|
|
700
658
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
|
|
703
|
-
* non-negative `next` exceeds the epsilon comparison. Called from setters
|
|
704
|
-
* that mutate state under the cache (range/mode/enabled flips that change
|
|
705
|
-
* the progress→opacity mapping but don't move `progress` itself).
|
|
706
|
-
*/
|
|
707
659
|
invalidate() {
|
|
708
660
|
this.last = -1;
|
|
709
661
|
}
|
|
710
|
-
/**
|
|
711
|
-
* Record `value` as the last-written without performing a write. Used
|
|
712
|
-
* when a setter writes a literal value through a different path (e.g.
|
|
713
|
-
* `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
|
|
714
|
-
* `shouldWrite` calls to short-circuit against that known cached state.
|
|
715
|
-
*/
|
|
716
662
|
setLastWritten(value) {
|
|
717
663
|
this.last = value;
|
|
718
664
|
}
|
|
719
|
-
/** Read-only accessor for diagnostic / dedup-aware peek. */
|
|
720
665
|
get value() {
|
|
721
666
|
return this.last;
|
|
722
667
|
}
|
|
723
668
|
};
|
|
724
669
|
|
|
670
|
+
// src/core/primitives/devWarn.ts
|
|
671
|
+
function devWarn(...args) {
|
|
672
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
|
|
673
|
+
console.warn(...args);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
725
677
|
// src/core/controllers/scrim-controller.ts
|
|
678
|
+
var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
|
|
726
679
|
var ScrimController = class {
|
|
727
680
|
constructor(deps, opts) {
|
|
728
|
-
// ── DOM refs (mirror engine's; cached for hot path) ─────────────────
|
|
729
681
|
__publicField(this, "screenComponent");
|
|
730
682
|
__publicField(this, "backdrop");
|
|
731
683
|
__publicField(this, "mode");
|
|
732
|
-
// ── Engine callbacks ────────────────────────────────────────────────
|
|
733
684
|
__publicField(this, "isDestroyed");
|
|
734
685
|
__publicField(this, "isTopSheet");
|
|
735
686
|
__publicField(this, "getAllowedIds");
|
|
736
687
|
__publicField(this, "getResolvedSnaps");
|
|
737
688
|
__publicField(this, "snapToFn");
|
|
738
|
-
// ── Mode + state ────────────────────────────────────────────────────
|
|
739
689
|
__publicField(this, "scrimMode");
|
|
740
690
|
__publicField(this, "backdropRange");
|
|
741
691
|
__publicField(this, "screenRange");
|
|
742
|
-
// setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
|
|
743
|
-
// ranges so re-enable can restore prior values without a remount. Public
|
|
744
|
-
// so tests / introspection can read it without an `as any` cast.
|
|
745
692
|
__publicField(this, "scrimEnabled", true);
|
|
746
693
|
__publicField(this, "savedScrimRanges", null);
|
|
747
|
-
// ── Tap-to-close ────────────────────────────────────────────────────
|
|
748
694
|
__publicField(this, "scrimTapToCloseEnabled", false);
|
|
749
695
|
__publicField(this, "detachScrimTap", null);
|
|
750
|
-
// ── Scrim overlay slot (positioned floating element) ────────────────
|
|
751
696
|
__publicField(this, "scrimOverlayTeardown", null);
|
|
752
|
-
// ── Hot-path opacity dedup caches (per-frame writes) ────────────────
|
|
753
|
-
// Numeric sentinels migrated to WriteSentinel primitive — single
|
|
754
|
-
// invalidation surface, shared with the engine's size/progress sentinels.
|
|
755
|
-
// The two string-typed sentinels (`Pointer`, `Display`) keep their inline
|
|
756
|
-
// pattern: equality on string union, no epsilon, different reset semantics.
|
|
757
697
|
__publicField(this, "backdropOpacitySentinel", new WriteSentinel());
|
|
758
698
|
__publicField(this, "screenOpacitySentinel", new WriteSentinel());
|
|
759
699
|
__publicField(this, "lastBackdropPointer", "");
|
|
@@ -788,41 +728,15 @@ var ScrimController = class {
|
|
|
788
728
|
}
|
|
789
729
|
}
|
|
790
730
|
}
|
|
791
|
-
/**
|
|
792
|
-
* Invalidate the opacity dedup caches. Called from any setter that mutates
|
|
793
|
-
* range/mode/enabled — opacity is a pure function of (progress, range,
|
|
794
|
-
* enabled), so a config change makes the cached values stale even if
|
|
795
|
-
* `progress` itself didn't move. Next `applyOpacity(progress, false)` call
|
|
796
|
-
* sees the -1 sentinel and re-runs the math instead of short-circuiting.
|
|
797
|
-
*
|
|
798
|
-
* Public for the engine: `setSnapPoints` mutates resolved geometry which
|
|
799
|
-
* shifts `progress→opacity` mapping under the cache; engine calls this to
|
|
800
|
-
* keep the next render frame correct on a static sheet.
|
|
801
|
-
* @internal
|
|
802
|
-
*/
|
|
803
731
|
invalidateOpacityCache() {
|
|
804
732
|
this.backdropOpacitySentinel.invalidate();
|
|
805
733
|
this.screenOpacitySentinel.invalidate();
|
|
806
734
|
}
|
|
807
|
-
/** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
|
|
808
735
|
attach() {
|
|
809
736
|
if (this.scrimTapToCloseEnabled) {
|
|
810
737
|
this.setScrimTapToClose(true);
|
|
811
738
|
}
|
|
812
739
|
}
|
|
813
|
-
// ── Hot-path: opacity computation called per-frame from engine.applySize ──
|
|
814
|
-
/**
|
|
815
|
-
* Per-frame opacity writer. Computes backdrop + screen opacity from the
|
|
816
|
-
* sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
|
|
817
|
-
* caches so steady-state frames don't trigger style recalc.
|
|
818
|
-
*
|
|
819
|
-
* `progressChanged` is the engine's already-computed dedup gate — when
|
|
820
|
-
* `false`, opacity is by construction a pure function of `progress` so
|
|
821
|
-
* cached values are still valid; we short-circuit the Math.min/max +
|
|
822
|
-
* division work entirely. Setters that mutate ranges/mode/enabled
|
|
823
|
-
* invalidate the cache by writing -1 sentinels so the next call re-runs
|
|
824
|
-
* the math.
|
|
825
|
-
*/
|
|
826
740
|
applyOpacity(progress, progressChanged) {
|
|
827
741
|
if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
|
|
828
742
|
return;
|
|
@@ -868,80 +782,30 @@ var ScrimController = class {
|
|
|
868
782
|
}
|
|
869
783
|
}
|
|
870
784
|
}
|
|
871
|
-
// ── Public setters (engine forwards these 1:1) ─────────────────────
|
|
872
|
-
/**
|
|
873
|
-
* Replace the [start, end] progress window in which the backdrop fades 0→1.
|
|
874
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
875
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
876
|
-
* @example
|
|
877
|
-
* // Backdrop only starts darkening after the sheet crosses 30% open.
|
|
878
|
-
* engine.setBackdropRange([0.3, 1]);
|
|
879
|
-
*/
|
|
880
785
|
setBackdropRange(range, applySize) {
|
|
881
786
|
if (this.isDestroyed()) return;
|
|
882
787
|
this.backdropRange = range;
|
|
883
788
|
this.invalidateOpacityCache();
|
|
884
789
|
applySize();
|
|
885
790
|
}
|
|
886
|
-
/**
|
|
887
|
-
* Replace the [start, end] progress window in which the screen scrim fades 0→1.
|
|
888
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
889
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
890
|
-
* @example
|
|
891
|
-
* // Scrim is fully opaque only when the sheet is past 50% — softer feel.
|
|
892
|
-
* engine.setScreenRange([0, 0.5]);
|
|
893
|
-
*/
|
|
894
791
|
setScreenRange(range, applySize) {
|
|
895
792
|
if (this.isDestroyed()) return;
|
|
896
793
|
this.screenRange = range;
|
|
897
794
|
this.invalidateOpacityCache();
|
|
898
795
|
applySize();
|
|
899
796
|
}
|
|
900
|
-
/**
|
|
901
|
-
* Set the scrim background color. Pass `null`/`undefined` to clear the
|
|
902
|
-
* inline write and let consumer CSS take over.
|
|
903
|
-
* @param color - Any valid CSS color string, or `null`/`undefined` to clear.
|
|
904
|
-
* @example
|
|
905
|
-
* engine.setScrimColor("rgba(0, 0, 0, 0.6)");
|
|
906
|
-
* engine.setScrimColor(null); // hand back to consumer CSS
|
|
907
|
-
*/
|
|
908
797
|
setScrimColor(color) {
|
|
909
798
|
if (this.isDestroyed()) return;
|
|
910
799
|
this.applyScrimStyles({ color: color === void 0 ? null : color });
|
|
911
800
|
}
|
|
912
|
-
/**
|
|
913
|
-
* Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
|
|
914
|
-
* @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
|
|
915
|
-
* `null`/`undefined` to remove the filter.
|
|
916
|
-
* @example
|
|
917
|
-
* engine.setScrimBlur("12px"); // frosted-glass effect
|
|
918
|
-
* engine.setScrimBlur(null); // disable blur
|
|
919
|
-
*/
|
|
920
801
|
setScrimBlur(blur) {
|
|
921
802
|
if (this.isDestroyed()) return;
|
|
922
803
|
this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
|
|
923
804
|
}
|
|
924
|
-
/**
|
|
925
|
-
* Toggle whether the scrim layer captures pointer events.
|
|
926
|
-
* @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
|
|
927
|
-
* @example
|
|
928
|
-
* engine.setScrimInteractive(true); // scrim swallows clicks
|
|
929
|
-
* engine.setScrimInteractive(false); // clicks pass through to content below
|
|
930
|
-
*/
|
|
931
805
|
setScrimInteractive(interactive) {
|
|
932
806
|
if (this.isDestroyed()) return;
|
|
933
807
|
this.applyScrimStyles({ interactive });
|
|
934
808
|
}
|
|
935
|
-
/**
|
|
936
|
-
* Switch scrim positioning at runtime.
|
|
937
|
-
* @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
|
|
938
|
-
* so only the area NOT covered by the sheet is dimmed), or
|
|
939
|
-
* `"off"` (display:none + opacity:0 — no remount needed).
|
|
940
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
941
|
-
* @example
|
|
942
|
-
* engine.setScrimMode("above-sheet"); // dim only the area above the sheet
|
|
943
|
-
* engine.setScrimMode("off"); // disable scrim layer
|
|
944
|
-
*/
|
|
945
809
|
setScrimMode(mode, applySize) {
|
|
946
810
|
if (this.isDestroyed()) return;
|
|
947
811
|
if (mode === this.scrimMode) return;
|
|
@@ -971,15 +835,6 @@ var ScrimController = class {
|
|
|
971
835
|
}
|
|
972
836
|
applySize();
|
|
973
837
|
}
|
|
974
|
-
/**
|
|
975
|
-
* Enable or disable click-on-scrim → snap to first non-zero allowed snap.
|
|
976
|
-
* Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
|
|
977
|
-
* click listener actually fires.
|
|
978
|
-
* @param enabled - `true` installs the click listener; `false` removes it.
|
|
979
|
-
* @example
|
|
980
|
-
* engine.setScrimTapToClose(true); // tap outside sheet collapses it
|
|
981
|
-
* engine.setScrimTapToClose(false); // require explicit close button
|
|
982
|
-
*/
|
|
983
838
|
setScrimTapToClose(enabled) {
|
|
984
839
|
if (this.isDestroyed()) return;
|
|
985
840
|
if (!this.screenComponent) return;
|
|
@@ -1009,16 +864,6 @@ var ScrimController = class {
|
|
|
1009
864
|
this.scrimTapToCloseEnabled = false;
|
|
1010
865
|
}
|
|
1011
866
|
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Toggle the scrim layer without remounting. Disabling stashes current
|
|
1014
|
-
* `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
|
|
1015
|
-
* formula → 0 across the whole range); re-enabling restores the saved values.
|
|
1016
|
-
* @param enabled - `false` hides scrim, `true` restores prior ranges.
|
|
1017
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
1018
|
-
* @example
|
|
1019
|
-
* engine.setScrimEnabled(false); // hide scrim, ranges remembered
|
|
1020
|
-
* engine.setScrimEnabled(true); // restore prior ranges (no remount)
|
|
1021
|
-
*/
|
|
1022
867
|
setScrimEnabled(enabled, applySize) {
|
|
1023
868
|
if (this.isDestroyed()) return;
|
|
1024
869
|
if (enabled === this.scrimEnabled) return;
|
|
@@ -1037,20 +882,6 @@ var ScrimController = class {
|
|
|
1037
882
|
}
|
|
1038
883
|
applySize();
|
|
1039
884
|
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Batch-update multiple scrim fields with a single applySize() at the end.
|
|
1042
|
-
* If `preset` is set, it seeds color/blur/interactive/range first; explicit
|
|
1043
|
-
* fields override.
|
|
1044
|
-
* @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
|
|
1045
|
-
* color, blur, interactive, range.
|
|
1046
|
-
* @param applySize - Engine-supplied recompute hook; runs ONCE per call.
|
|
1047
|
-
* @example
|
|
1048
|
-
* engine.setScrim({
|
|
1049
|
-
* preset: "frosted",
|
|
1050
|
-
* mode: "above-sheet",
|
|
1051
|
-
* tapToClose: true,
|
|
1052
|
-
* });
|
|
1053
|
-
*/
|
|
1054
885
|
setScrim(opts, applySize) {
|
|
1055
886
|
if (this.isDestroyed()) return;
|
|
1056
887
|
if (opts.preset) {
|
|
@@ -1073,27 +904,18 @@ var ScrimController = class {
|
|
|
1073
904
|
interactive: opts.interactive
|
|
1074
905
|
});
|
|
1075
906
|
if (opts.range !== void 0) {
|
|
1076
|
-
this.
|
|
907
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
908
|
+
this.savedScrimRanges = {
|
|
909
|
+
screen: opts.range,
|
|
910
|
+
backdrop: this.savedScrimRanges.backdrop
|
|
911
|
+
};
|
|
912
|
+
} else {
|
|
913
|
+
this.screenRange = opts.range;
|
|
914
|
+
}
|
|
1077
915
|
}
|
|
1078
916
|
this.invalidateOpacityCache();
|
|
1079
917
|
applySize();
|
|
1080
918
|
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Inject a positioned floating element into the scrim layer (e.g. a close
|
|
1083
|
-
* button anchored top-right). Replaces any prior overlay so repeated calls
|
|
1084
|
-
* (reactive effects re-running) don't accumulate wrappers.
|
|
1085
|
-
* @param opts - Children element, optional `inset` (default `"16px"`),
|
|
1086
|
-
* `position` (9-way grid, default `"top-right"`), and
|
|
1087
|
-
* `interactive` flag (default `true`).
|
|
1088
|
-
* @returns Teardown function that removes the wrapper from the DOM.
|
|
1089
|
-
* @example
|
|
1090
|
-
* const teardown = engine.setScrimOverlay({
|
|
1091
|
-
* children: closeBtn,
|
|
1092
|
-
* position: "top-right",
|
|
1093
|
-
* inset: "20px",
|
|
1094
|
-
* });
|
|
1095
|
-
* // later: teardown(); // remove the floating button
|
|
1096
|
-
*/
|
|
1097
919
|
setScrimOverlay(opts) {
|
|
1098
920
|
if (this.isDestroyed()) return () => {
|
|
1099
921
|
};
|
|
@@ -1101,7 +923,18 @@ var ScrimController = class {
|
|
|
1101
923
|
};
|
|
1102
924
|
this.scrimOverlayTeardown?.();
|
|
1103
925
|
const screen = this.screenComponent;
|
|
1104
|
-
const
|
|
926
|
+
const insetRaw = opts.inset ?? "16px";
|
|
927
|
+
const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
|
|
928
|
+
if (inset !== insetRaw) {
|
|
929
|
+
devWarn(
|
|
930
|
+
`[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
|
|
934
|
+
devWarn(
|
|
935
|
+
"[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."
|
|
936
|
+
);
|
|
937
|
+
}
|
|
1105
938
|
const position = opts.position ?? "top-right";
|
|
1106
939
|
const interactive = opts.interactive ?? true;
|
|
1107
940
|
const wrapper = screen.ownerDocument.createElement("div");
|
|
@@ -1150,12 +983,30 @@ var ScrimController = class {
|
|
|
1150
983
|
ws.bottom = inset;
|
|
1151
984
|
ws.right = inset;
|
|
1152
985
|
break;
|
|
986
|
+
case "sheet-top-left":
|
|
987
|
+
case "sheet-top-center":
|
|
988
|
+
case "sheet-top-right":
|
|
989
|
+
applySheetAnchoredPosition(
|
|
990
|
+
ws,
|
|
991
|
+
this.mode,
|
|
992
|
+
position,
|
|
993
|
+
inset
|
|
994
|
+
);
|
|
995
|
+
break;
|
|
1153
996
|
}
|
|
1154
997
|
if (interactive) {
|
|
1155
998
|
ws.pointerEvents = "auto";
|
|
1156
999
|
}
|
|
1157
1000
|
wrapper.appendChild(opts.children);
|
|
1158
|
-
screen.
|
|
1001
|
+
const host = screen.parentElement;
|
|
1002
|
+
if (!host) {
|
|
1003
|
+
devWarn(
|
|
1004
|
+
"[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
|
|
1005
|
+
);
|
|
1006
|
+
return () => {
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
host.appendChild(wrapper);
|
|
1159
1010
|
const teardown = () => {
|
|
1160
1011
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
1161
1012
|
if (this.scrimOverlayTeardown === teardown) {
|
|
@@ -1165,7 +1016,6 @@ var ScrimController = class {
|
|
|
1165
1016
|
this.scrimOverlayTeardown = teardown;
|
|
1166
1017
|
return teardown;
|
|
1167
1018
|
}
|
|
1168
|
-
/** Tear down toggle-state listeners + remove any injected overlay wrapper. */
|
|
1169
1019
|
destroy() {
|
|
1170
1020
|
this.detachScrimTap?.();
|
|
1171
1021
|
this.detachScrimTap = null;
|
|
@@ -1184,8 +1034,6 @@ var ScrimController = class {
|
|
|
1184
1034
|
this.lastBackdropPointer = "";
|
|
1185
1035
|
this.lastScreenDisplay = "";
|
|
1186
1036
|
}
|
|
1187
|
-
// ── Private helpers ────────────────────────────────────────────────
|
|
1188
|
-
/** Centralized scrim style writer — atomic 3-property write across all setters. */
|
|
1189
1037
|
applyScrimStyles(opts) {
|
|
1190
1038
|
if (!this.screenComponent) return;
|
|
1191
1039
|
const s = this.screenComponent.style;
|
|
@@ -1207,7 +1055,6 @@ var ScrimController = class {
|
|
|
1207
1055
|
s.pointerEvents = opts.interactive ? "auto" : "none";
|
|
1208
1056
|
}
|
|
1209
1057
|
}
|
|
1210
|
-
/** Pin the screen overlay to the area NOT occupied by the sheet. */
|
|
1211
1058
|
applyAboveSheetInset() {
|
|
1212
1059
|
if (!this.screenComponent) return;
|
|
1213
1060
|
const insetByMode = {
|
|
@@ -1222,6 +1069,35 @@ var ScrimController = class {
|
|
|
1222
1069
|
s.pointerEvents = "none";
|
|
1223
1070
|
}
|
|
1224
1071
|
};
|
|
1072
|
+
function resolveSheetAnchoredStyle(mode, position, inset) {
|
|
1073
|
+
const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
|
|
1074
|
+
switch (mode) {
|
|
1075
|
+
case "bottom":
|
|
1076
|
+
if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
|
|
1077
|
+
if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
|
|
1078
|
+
return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
1079
|
+
case "top":
|
|
1080
|
+
if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
|
|
1081
|
+
if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
|
|
1082
|
+
return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
1083
|
+
case "left":
|
|
1084
|
+
if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
|
|
1085
|
+
if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
|
|
1086
|
+
return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
1087
|
+
case "right":
|
|
1088
|
+
if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
|
|
1089
|
+
if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
|
|
1090
|
+
return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function applySheetAnchoredPosition(ws, mode, position, inset) {
|
|
1094
|
+
const style = resolveSheetAnchoredStyle(mode, position, inset);
|
|
1095
|
+
if (style.top !== void 0) ws.top = style.top;
|
|
1096
|
+
if (style.bottom !== void 0) ws.bottom = style.bottom;
|
|
1097
|
+
if (style.left !== void 0) ws.left = style.left;
|
|
1098
|
+
if (style.right !== void 0) ws.right = style.right;
|
|
1099
|
+
if (style.transform !== void 0) ws.transform = style.transform;
|
|
1100
|
+
}
|
|
1225
1101
|
|
|
1226
1102
|
// src/core/animation/animation.ts
|
|
1227
1103
|
var easeOutBack = (t) => {
|
|
@@ -1374,13 +1250,9 @@ var AnimationRunner = class {
|
|
|
1374
1250
|
__publicField(this, "duration");
|
|
1375
1251
|
__publicField(this, "easing");
|
|
1376
1252
|
__publicField(this, "respectReducedMotion");
|
|
1377
|
-
/** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
|
|
1378
1253
|
__publicField(this, "reducedMotion", false);
|
|
1379
|
-
/** Disposes the matchMedia change listener — drained from destroy(). */
|
|
1380
1254
|
__publicField(this, "detachReducedMotion", null);
|
|
1381
|
-
/** Computed once — both inputs (option + API presence) stable for life. */
|
|
1382
1255
|
__publicField(this, "viewTransitionsAvailable");
|
|
1383
|
-
/** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
|
|
1384
1256
|
__publicField(this, "currentTween", null);
|
|
1385
1257
|
__publicField(this, "currentSpring", null);
|
|
1386
1258
|
this.element = deps.element;
|
|
@@ -1411,26 +1283,13 @@ var AnimationRunner = class {
|
|
|
1411
1283
|
}
|
|
1412
1284
|
}
|
|
1413
1285
|
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Live derived flag — `true` whenever a spring/tween handle is in flight.
|
|
1416
|
-
* Reading the canonical handles (instead of mirroring into a separate
|
|
1417
|
-
* boolean) eliminates the reduced-motion early-return latent bug where
|
|
1418
|
-
* the flag stayed true forever, and removes the cross-controller
|
|
1419
|
-
* setIsAnimating callback.
|
|
1420
|
-
*/
|
|
1421
1286
|
get isAnimating() {
|
|
1422
1287
|
return this.currentTween !== null || this.currentSpring !== null;
|
|
1423
1288
|
}
|
|
1424
|
-
/** Cancel any in-flight tween/spring without touching engine state. */
|
|
1425
1289
|
cancel() {
|
|
1426
1290
|
this.currentTween?.cancel();
|
|
1427
1291
|
this.currentSpring?.cancel();
|
|
1428
1292
|
}
|
|
1429
|
-
/**
|
|
1430
|
-
* Animate from current size to `target`. Resolves when the animation
|
|
1431
|
-
* completes; resolves early if cancelled mid-flight (caller checks an
|
|
1432
|
-
* abort signal post-await for cycle-invariant correctness).
|
|
1433
|
-
*/
|
|
1434
1293
|
async animateTo(target, velocityPxPerMs) {
|
|
1435
1294
|
if (target === this.getSize()) return;
|
|
1436
1295
|
this.currentTween?.cancel();
|
|
@@ -1471,7 +1330,6 @@ var AnimationRunner = class {
|
|
|
1471
1330
|
if (!this.isDragging()) this.element.style.willChange = "auto";
|
|
1472
1331
|
}
|
|
1473
1332
|
}
|
|
1474
|
-
/** Tear down the matchMedia listener. Engine calls from destroy(). */
|
|
1475
1333
|
destroy() {
|
|
1476
1334
|
this.detachReducedMotion?.();
|
|
1477
1335
|
this.detachReducedMotion = null;
|
|
@@ -1631,12 +1489,8 @@ var LifecycleController = class {
|
|
|
1631
1489
|
__publicField(this, "inertSiblings");
|
|
1632
1490
|
__publicField(this, "shouldApplyInertSiblings");
|
|
1633
1491
|
__publicField(this, "returnFocus");
|
|
1634
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
1635
1492
|
__publicField(this, "releaseFocusTrap", null);
|
|
1636
1493
|
__publicField(this, "releaseScrollLock", null);
|
|
1637
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
1638
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
1639
|
-
* released or never-installed instances. */
|
|
1640
1494
|
__publicField(this, "installed", false);
|
|
1641
1495
|
__publicField(this, "destroyed", false);
|
|
1642
1496
|
this.element = deps.element;
|
|
@@ -1650,14 +1504,6 @@ var LifecycleController = class {
|
|
|
1650
1504
|
this.returnFocus = opts.returnFocus;
|
|
1651
1505
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
1652
1506
|
}
|
|
1653
|
-
/**
|
|
1654
|
-
* Run the open-time install steps. Idempotent: each step checks its
|
|
1655
|
-
* existing teardown ref so a re-open during a half-cancelled close
|
|
1656
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
1657
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
1658
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
1659
|
-
* No-op when destroyed.
|
|
1660
|
-
*/
|
|
1661
1507
|
install() {
|
|
1662
1508
|
if (this.destroyed) return;
|
|
1663
1509
|
this.installed = true;
|
|
@@ -1674,22 +1520,10 @@ var LifecycleController = class {
|
|
|
1674
1520
|
this.inertSiblings.apply();
|
|
1675
1521
|
}
|
|
1676
1522
|
}
|
|
1677
|
-
/**
|
|
1678
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
1679
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
1680
|
-
* on the next `release()`. No-op when destroyed.
|
|
1681
|
-
*/
|
|
1682
1523
|
setReturnFocus(target) {
|
|
1683
1524
|
if (this.destroyed) return;
|
|
1684
1525
|
this.returnFocus = target;
|
|
1685
1526
|
}
|
|
1686
|
-
/**
|
|
1687
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
1688
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
1689
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
1690
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
1691
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
1692
|
-
*/
|
|
1693
1527
|
release() {
|
|
1694
1528
|
if (!this.installed) return;
|
|
1695
1529
|
this.installed = false;
|
|
@@ -1703,12 +1537,6 @@ var LifecycleController = class {
|
|
|
1703
1537
|
target?.focus?.();
|
|
1704
1538
|
}
|
|
1705
1539
|
}
|
|
1706
|
-
/**
|
|
1707
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
1708
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
1709
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
1710
|
-
* restore.
|
|
1711
|
-
*/
|
|
1712
1540
|
destroy() {
|
|
1713
1541
|
this.release();
|
|
1714
1542
|
this.destroyed = true;
|
|
@@ -1718,12 +1546,10 @@ var LifecycleController = class {
|
|
|
1718
1546
|
// src/core/gestures.ts
|
|
1719
1547
|
var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
|
|
1720
1548
|
var SampleRing = class {
|
|
1721
|
-
// current count, ≤ capacity
|
|
1722
1549
|
constructor(capacity) {
|
|
1723
1550
|
__publicField(this, "slots");
|
|
1724
1551
|
__publicField(this, "capacity");
|
|
1725
1552
|
__publicField(this, "head", 0);
|
|
1726
|
-
// next write index
|
|
1727
1553
|
__publicField(this, "size", 0);
|
|
1728
1554
|
this.capacity = capacity;
|
|
1729
1555
|
this.slots = new Array(capacity);
|
|
@@ -1736,12 +1562,6 @@ var SampleRing = class {
|
|
|
1736
1562
|
this.head = (this.head + 1) % this.capacity;
|
|
1737
1563
|
if (this.size < this.capacity) this.size++;
|
|
1738
1564
|
}
|
|
1739
|
-
/**
|
|
1740
|
-
* Drop oldest entries whose timestamp is below `cutoff`, but never shrink
|
|
1741
|
-
* below `minKeep`. Mirrors the original
|
|
1742
|
-
* `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
|
|
1743
|
-
* but without copying — just advances the logical tail.
|
|
1744
|
-
*/
|
|
1745
1565
|
pruneBefore(cutoff, minKeep) {
|
|
1746
1566
|
while (this.size > minKeep) {
|
|
1747
1567
|
const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
|
|
@@ -1877,28 +1697,14 @@ var GestureController = class {
|
|
|
1877
1697
|
__publicField(this, "detach", null);
|
|
1878
1698
|
__publicField(this, "isDragging_", false);
|
|
1879
1699
|
__publicField(this, "dragStartSize", 0);
|
|
1880
|
-
// Only "touch" gestures are eligible for soft-keyboard dismissal —
|
|
1881
|
-
// mouse/trackpad users don't have a keyboard to dismiss.
|
|
1882
1700
|
__publicField(this, "currentPointerKind", "touch");
|
|
1883
|
-
// One blur per drag — flipping focus every move event races the keyboard's
|
|
1884
|
-
// own animations on iOS and makes the page jitter.
|
|
1885
1701
|
__publicField(this, "keyboardDismissed", false);
|
|
1886
|
-
// Reused payload object for the "drag" event — mutated each onMove instead
|
|
1887
|
-
// of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
|
|
1888
|
-
// would otherwise churn tens of KB through the GC. Mirrors browser
|
|
1889
|
-
// PointerEvent semantics: consumers must NOT retain the reference across
|
|
1890
|
-
// frames (clone if needed). See SheetEventMap.drag JSDoc.
|
|
1891
1702
|
__publicField(this, "dragPayload", { size: 0, delta: 0 });
|
|
1892
1703
|
this.deps = deps;
|
|
1893
1704
|
}
|
|
1894
|
-
/** True between `dragstart` and `dragend`/`onCancel`. */
|
|
1895
1705
|
get isDragging() {
|
|
1896
1706
|
return this.isDragging_;
|
|
1897
1707
|
}
|
|
1898
|
-
/**
|
|
1899
|
-
* Bind pointer listeners on the handle and return a teardown. Engine pushes
|
|
1900
|
-
* the teardown into its TeardownStack so destroy() drains it once.
|
|
1901
|
-
*/
|
|
1902
1708
|
install() {
|
|
1903
1709
|
if (this.detach) return this.detach;
|
|
1904
1710
|
this.detach = installGestures(this.deps.handle, this.deps.mode, {
|
|
@@ -1955,11 +1761,6 @@ var GestureController = class {
|
|
|
1955
1761
|
});
|
|
1956
1762
|
return this.detach;
|
|
1957
1763
|
}
|
|
1958
|
-
/**
|
|
1959
|
-
* Force-clear drag visual state. Engine calls from destroy() when destroyed
|
|
1960
|
-
* mid-drag — gesture detach won't synthesize an onCancel, so we have to
|
|
1961
|
-
* roll back the data-attribute + will-change ourselves.
|
|
1962
|
-
*/
|
|
1963
1764
|
forceClearDragState() {
|
|
1964
1765
|
if (!this.isDragging_) return;
|
|
1965
1766
|
this.isDragging_ = false;
|
|
@@ -1978,11 +1779,6 @@ var AriaSliderWriter = class {
|
|
|
1978
1779
|
mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
|
|
1979
1780
|
);
|
|
1980
1781
|
}
|
|
1981
|
-
/**
|
|
1982
|
-
* Update slider position. `allowedIds` length determines `aria-valuemax`;
|
|
1983
|
-
* `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
|
|
1984
|
-
* Empty list → strip every dynamic attr so AT skips the handle entirely.
|
|
1985
|
-
*/
|
|
1986
1782
|
setValue(allowedIds, activeId) {
|
|
1987
1783
|
if (allowedIds.length === 0) {
|
|
1988
1784
|
this.handle.removeAttribute("aria-valuemin");
|
|
@@ -2056,9 +1852,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2056
1852
|
scrimMode: opts.scrimMode,
|
|
2057
1853
|
scrimColor: opts.scrimColor,
|
|
2058
1854
|
scrimBlur: opts.scrimBlur,
|
|
2059
|
-
// Auto-promote pointer-events when tap-to-close is on so the click
|
|
2060
|
-
// listener actually receives events. Caller can still force `false`
|
|
2061
|
-
// explicitly to opt out — `??` only kicks in for `undefined`.
|
|
2062
1855
|
scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
|
|
2063
1856
|
scrimTapToClose: opts.scrimTapToClose,
|
|
2064
1857
|
scrimPreset: opts.scrimPreset,
|
|
@@ -2079,10 +1872,6 @@ function resolveEngineOptions(opts, extras) {
|
|
|
2079
1872
|
closeOnEscape: opts.closeOnEscape,
|
|
2080
1873
|
lockBodyScroll: opts.lockBodyScroll,
|
|
2081
1874
|
inertSiblings: opts.inertSiblings,
|
|
2082
|
-
// Folded in from `extras` so the lifecycle bag is fully assembled at
|
|
2083
|
-
// the call site — engine no longer needs a spread+override at
|
|
2084
|
-
// controller construction. When `extras` is omitted, the field stays
|
|
2085
|
-
// undefined and the controller uses its own default predicate.
|
|
2086
1875
|
shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
|
|
2087
1876
|
}
|
|
2088
1877
|
};
|
|
@@ -2117,6 +1906,7 @@ var BottomSheetEngine = class {
|
|
|
2117
1906
|
__publicField(this, "scrollContainer");
|
|
2118
1907
|
__publicField(this, "backdrop");
|
|
2119
1908
|
__publicField(this, "screenComponent");
|
|
1909
|
+
__publicField(this, "scrimParent", null);
|
|
2120
1910
|
__publicField(this, "mode");
|
|
2121
1911
|
__publicField(this, "flickVelocity");
|
|
2122
1912
|
__publicField(this, "dragThreshold");
|
|
@@ -2127,40 +1917,22 @@ var BottomSheetEngine = class {
|
|
|
2127
1917
|
__publicField(this, "lifecycle");
|
|
2128
1918
|
__publicField(this, "closeOnBack");
|
|
2129
1919
|
__publicField(this, "routedTo");
|
|
2130
|
-
// Engine retains `snapPointsRaw` for the "find a closed-id" query in
|
|
2131
|
-
// close() (raw input, not resolved geometry). Resolver owns geometry.
|
|
2132
1920
|
__publicField(this, "snapPointsRaw");
|
|
2133
1921
|
__publicField(this, "snaps");
|
|
2134
1922
|
__publicField(this, "activeId");
|
|
2135
1923
|
__publicField(this, "size", 0);
|
|
2136
|
-
// Constructed in attach(); a plugin's install() could read `state` before
|
|
2137
|
-
// attach completes if the constructor throws partway through. State getter
|
|
2138
|
-
// null-guards via `this.gesture?.isDragging ?? false`.
|
|
2139
1924
|
__publicField(this, "gesture");
|
|
2140
1925
|
__publicField(this, "rootEl", null);
|
|
2141
1926
|
__publicField(this, "destroyed", false);
|
|
2142
|
-
// In-flight ViewTransition handle. A second snapTo while a VT is in flight
|
|
2143
|
-
// would otherwise let Chromium reject the second's promise and flicker —
|
|
2144
|
-
// newCycle() calls skipTransition() to abort cleanly.
|
|
2145
1927
|
__publicField(this, "currentViewTransition", null);
|
|
2146
|
-
// Each cycle captures the signal at entry; when a newer cycle aborts the
|
|
2147
|
-
// controller, the captured signal flips to `aborted=true` and post-await
|
|
2148
|
-
// guards short-circuit (prevents stale snap/open/close emits).
|
|
2149
1928
|
__publicField(this, "currentAbort", new AbortController());
|
|
2150
|
-
// Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
|
|
2151
|
-
// visually distinguishable; gating CSSOM writes drops 30-50% of writes
|
|
2152
|
-
// during the spring's settle tail.
|
|
2153
1929
|
__publicField(this, "sizeWriteSentinel", new WriteSentinel());
|
|
2154
1930
|
__publicField(this, "progressWriteSentinel", new WriteSentinel());
|
|
2155
1931
|
__publicField(this, "isTopSheet", true);
|
|
2156
|
-
// Pooled payload — identity documented in SheetEventMap.progress JSDoc;
|
|
2157
|
-
// consumers must not retain across frames.
|
|
2158
1932
|
__publicField(this, "progressPayload", {
|
|
2159
1933
|
value: 0,
|
|
2160
1934
|
size: 0
|
|
2161
1935
|
});
|
|
2162
|
-
// Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
|
|
2163
|
-
// Inner `range` object identity is also stable (mutated in place).
|
|
2164
1936
|
__publicField(this, "dragContextBuf", {
|
|
2165
1937
|
size: 0,
|
|
2166
1938
|
maxAxisSize: 0,
|
|
@@ -2171,13 +1943,7 @@ var BottomSheetEngine = class {
|
|
|
2171
1943
|
__publicField(this, "persistKey");
|
|
2172
1944
|
__publicField(this, "linkedSheets", []);
|
|
2173
1945
|
__publicField(this, "scrollCache");
|
|
2174
|
-
// Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
|
|
2175
|
-
// tear down first before the infrastructure they depend on. Toggle-semantic
|
|
2176
|
-
// listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
|
|
2177
|
-
// fields on their controllers.
|
|
2178
1946
|
__publicField(this, "teardowns", new TeardownStack());
|
|
2179
|
-
// Pre-computed transform template — built once per engine since `mode` is
|
|
2180
|
-
// fixed for its lifetime. Avoids the 4-arm switch at 60-120 Hz.
|
|
2181
1947
|
__publicField(this, "transformTemplate");
|
|
2182
1948
|
const resolved = resolveEngineOptions(opts, {
|
|
2183
1949
|
shouldApplyInertSiblings: () => {
|
|
@@ -2192,7 +1958,8 @@ var BottomSheetEngine = class {
|
|
|
2192
1958
|
this.handle = opts.handle ?? opts.element;
|
|
2193
1959
|
this.scrollContainer = opts.scrollContainer;
|
|
2194
1960
|
this.backdrop = opts.backdrop;
|
|
2195
|
-
this.screenComponent = opts.screenComponent;
|
|
1961
|
+
this.screenComponent = opts.scrim ?? opts.screenComponent;
|
|
1962
|
+
this.scrimParent = this.screenComponent?.parentElement ?? null;
|
|
2196
1963
|
this.mode = resolved.mode;
|
|
2197
1964
|
this.transformTemplate = buildTransformTemplate(this.mode);
|
|
2198
1965
|
this.aria = new AriaSliderWriter(this.handle, this.mode);
|
|
@@ -2220,8 +1987,6 @@ var BottomSheetEngine = class {
|
|
|
2220
1987
|
getRootEl: () => this.rootEl,
|
|
2221
1988
|
applySize: (size) => this.applySize(size),
|
|
2222
1989
|
getSize: () => this.size,
|
|
2223
|
-
// Optional chain protects synthetic test paths that drive animateTo
|
|
2224
|
-
// before attach() (gesture is constructed there).
|
|
2225
1990
|
isDragging: () => this.gesture?.isDragging ?? false
|
|
2226
1991
|
},
|
|
2227
1992
|
resolved.animation
|
|
@@ -2326,21 +2091,6 @@ var BottomSheetEngine = class {
|
|
|
2326
2091
|
on(event, fn) {
|
|
2327
2092
|
return this.bus.on(event, fn);
|
|
2328
2093
|
}
|
|
2329
|
-
/**
|
|
2330
|
-
* Register a plugin. `install` runs synchronously; if it returns a function,
|
|
2331
|
-
* it's captured for teardown during `destroy()`. No-op if already destroyed
|
|
2332
|
-
* — installing into a torn-down engine would leak listeners.
|
|
2333
|
-
*
|
|
2334
|
-
* Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
|
|
2335
|
-
* so a buggy plugin can't take the engine down with it: the engine stays
|
|
2336
|
-
* usable, sibling plugins still install, and the error surfaces in dev tools
|
|
2337
|
-
* on the next tick instead of being swallowed. Mirrors the per-fn isolation
|
|
2338
|
-
* model used for teardowns.
|
|
2339
|
-
*
|
|
2340
|
-
* Plugins that accept the second `scope` argument get transactional cleanup:
|
|
2341
|
-
* push partial-install cleanups via `scope.add(fn)` and the engine drains
|
|
2342
|
-
* them on throw, OR merges them into destroy-time teardowns on success.
|
|
2343
|
-
*/
|
|
2344
2094
|
use(plugin) {
|
|
2345
2095
|
if (this.destroyed) {
|
|
2346
2096
|
console.warn(
|
|
@@ -2350,9 +2100,6 @@ var BottomSheetEngine = class {
|
|
|
2350
2100
|
}
|
|
2351
2101
|
const scoped = [];
|
|
2352
2102
|
const scope = {
|
|
2353
|
-
// typeof check — not just truthiness — so a JS-only plugin pushing a
|
|
2354
|
-
// bogus value is silently rejected instead of queueing a confusing
|
|
2355
|
-
// TypeError on destroy() drain.
|
|
2356
2103
|
add: (fn) => {
|
|
2357
2104
|
if (typeof fn === "function") scoped.push(fn);
|
|
2358
2105
|
}
|
|
@@ -2381,36 +2128,13 @@ var BottomSheetEngine = class {
|
|
|
2381
2128
|
}
|
|
2382
2129
|
return this;
|
|
2383
2130
|
}
|
|
2384
|
-
/**
|
|
2385
|
-
* Replace the linked-sheets list at runtime. Use when peer engines aren't
|
|
2386
|
-
* available at construction time (mounted later in the tree). The wiring
|
|
2387
|
-
* to the `open` event is independent of this list — it reads `linkedSheets`
|
|
2388
|
-
* lazily on emit, so this setter is a simple field swap.
|
|
2389
|
-
*/
|
|
2390
2131
|
setLinkedSheets(sheets) {
|
|
2391
2132
|
if (this.destroyed) return;
|
|
2392
2133
|
this.linkedSheets = sheets;
|
|
2393
2134
|
}
|
|
2394
|
-
/** Read-only view of the current allow-list, for cross-sheet target negotiation. */
|
|
2395
2135
|
getAllowedIds() {
|
|
2396
2136
|
return this.snaps.getAllowedIds().slice();
|
|
2397
2137
|
}
|
|
2398
|
-
/**
|
|
2399
|
-
* Animate to a snap point by id. Resolves when the animation completes.
|
|
2400
|
-
*
|
|
2401
|
-
* Two call shapes:
|
|
2402
|
-
* ```ts
|
|
2403
|
-
* engine.snapTo("full"); // default velocity
|
|
2404
|
-
* engine.snapTo("full", 0.5); // px/ms initial velocity
|
|
2405
|
-
* engine.snapTo("full", { velocity: 0.5, signal }); // abortable
|
|
2406
|
-
* ```
|
|
2407
|
-
*
|
|
2408
|
-
* When `opts.signal` is provided, calling `controller.abort()` cancels the
|
|
2409
|
-
* in-flight tween/spring and the returned promise resolves without firing
|
|
2410
|
-
* `snap`. The captured `AbortSignal` is checked after each await so external
|
|
2411
|
-
* aborts ride the same cycle invariant as internal ones (resize, destroy,
|
|
2412
|
-
* concurrent snapTo).
|
|
2413
|
-
*/
|
|
2414
2138
|
async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
|
|
2415
2139
|
if (this.destroyed) return;
|
|
2416
2140
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2469,16 +2193,6 @@ var BottomSheetEngine = class {
|
|
|
2469
2193
|
this.handleClose();
|
|
2470
2194
|
}
|
|
2471
2195
|
}
|
|
2472
|
-
/**
|
|
2473
|
-
* Drive the sheet to an arbitrary px size, bypassing snap matching.
|
|
2474
|
-
* Useful for preview scrubs, animation chaining, or programmatic gestures.
|
|
2475
|
-
* Animates with the configured spring/tween. Resolves when the animation
|
|
2476
|
-
* completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
|
|
2477
|
-
*
|
|
2478
|
-
* Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
|
|
2479
|
-
* nor `snap` (the sheet isn't settling to a known snap). `progress` events
|
|
2480
|
-
* still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
|
|
2481
|
-
*/
|
|
2482
2196
|
async dragTo(targetSize, velocityOrOpts = 0) {
|
|
2483
2197
|
if (this.destroyed) return;
|
|
2484
2198
|
const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
|
|
@@ -2516,20 +2230,10 @@ var BottomSheetEngine = class {
|
|
|
2516
2230
|
this.snaps.setAllowedIds(ids);
|
|
2517
2231
|
this.updateAriaSlider();
|
|
2518
2232
|
if (snap && ids.includes(snap)) {
|
|
2519
|
-
void this.snapTo(
|
|
2520
|
-
snap,
|
|
2521
|
-
0,
|
|
2522
|
-
/* internal */
|
|
2523
|
-
true
|
|
2524
|
-
);
|
|
2233
|
+
void this.snapTo(snap, 0, true);
|
|
2525
2234
|
} else if (!ids.includes(this.activeId)) {
|
|
2526
2235
|
const fallback = ids[0];
|
|
2527
|
-
if (fallback) void this.snapTo(
|
|
2528
|
-
fallback,
|
|
2529
|
-
0,
|
|
2530
|
-
/* internal */
|
|
2531
|
-
true
|
|
2532
|
-
);
|
|
2236
|
+
if (fallback) void this.snapTo(fallback, 0, true);
|
|
2533
2237
|
}
|
|
2534
2238
|
}
|
|
2535
2239
|
setBackdropRange(range) {
|
|
@@ -2547,21 +2251,6 @@ var BottomSheetEngine = class {
|
|
|
2547
2251
|
setScrimInteractive(interactive) {
|
|
2548
2252
|
this.scrim.setScrimInteractive(interactive);
|
|
2549
2253
|
}
|
|
2550
|
-
/**
|
|
2551
|
-
* Batch-update multiple scrim fields in one call. Apply order is:
|
|
2552
|
-
* 1. `preset` — fills baseline color/blur/range/interactive.
|
|
2553
|
-
* 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
|
|
2554
|
-
* 3. `enabled` — flips the opacity contribution on/off.
|
|
2555
|
-
* 4. `tapToClose` — installs/tears the click listener.
|
|
2556
|
-
* 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
|
|
2557
|
-
* 6. ONE `applySize` so the per-frame opacity stays consistent.
|
|
2558
|
-
*
|
|
2559
|
-
* Performance: collecting changes here (vs calling individual setters)
|
|
2560
|
-
* avoids repeated `applySize` calls during a multi-field swap. Setters
|
|
2561
|
-
* called here suppress their own internal `applySize` either by passing
|
|
2562
|
-
* through fast-paths (mode/enabled no-op when value unchanged) or by
|
|
2563
|
-
* being visual-only (color/blur/interactive don't influence opacity).
|
|
2564
|
-
*/
|
|
2565
2254
|
setScrim(opts) {
|
|
2566
2255
|
this.scrim.setScrim(opts, () => this.applySize(this.size));
|
|
2567
2256
|
}
|
|
@@ -2577,11 +2266,6 @@ var BottomSheetEngine = class {
|
|
|
2577
2266
|
setScrimOverlay(opts) {
|
|
2578
2267
|
return this.scrim.setScrimOverlay(opts);
|
|
2579
2268
|
}
|
|
2580
|
-
/**
|
|
2581
|
-
* Read-only snapshot of scrim state. Use the `setScrim*` setters to mutate.
|
|
2582
|
-
* Stable public API — internals (e.g. cached opacity sentinels) are NOT
|
|
2583
|
-
* exposed so consumers can't accidentally couple to ScrimController shape.
|
|
2584
|
-
*/
|
|
2585
2269
|
getScrimState() {
|
|
2586
2270
|
return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
|
|
2587
2271
|
}
|
|
@@ -2627,24 +2311,11 @@ var BottomSheetEngine = class {
|
|
|
2627
2311
|
emit(event, payload) {
|
|
2628
2312
|
this.bus.emit(event, payload);
|
|
2629
2313
|
}
|
|
2630
|
-
/**
|
|
2631
|
-
* Abort the current cycle's signal and replace the controller. Returns the
|
|
2632
|
-
* fresh signal so the caller can capture it locally before any await — when
|
|
2633
|
-
* a future newCycle() / destroy aborts our controller, the captured signal
|
|
2634
|
-
* flips to aborted and post-await guards short-circuit.
|
|
2635
|
-
*/
|
|
2636
2314
|
newCycle() {
|
|
2637
2315
|
this.currentAbort.abort();
|
|
2638
2316
|
this.currentAbort = new AbortController();
|
|
2639
2317
|
return this.currentAbort.signal;
|
|
2640
2318
|
}
|
|
2641
|
-
/**
|
|
2642
|
-
* Fire `before-snap` and report whether a listener cancelled it. The
|
|
2643
|
-
* `cancel()` callback exposed to listeners is frozen after the synchronous
|
|
2644
|
-
* emit phase so async callers can't silently miss — they get a console
|
|
2645
|
-
* warning. Returns `true` when cancelled so callers can early-return /
|
|
2646
|
-
* spring-back without re-implementing the cancel/frozen pattern.
|
|
2647
|
-
*/
|
|
2648
2319
|
emitBeforeSnap(target, previousId) {
|
|
2649
2320
|
let cancelled = false;
|
|
2650
2321
|
let frozen = false;
|
|
@@ -2678,30 +2349,9 @@ var BottomSheetEngine = class {
|
|
|
2678
2349
|
this.snaps.setRaw(this.snapPointsRaw);
|
|
2679
2350
|
this.scrim.invalidateOpacityCache();
|
|
2680
2351
|
}
|
|
2681
|
-
/**
|
|
2682
|
-
* Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
|
|
2683
|
-
* Engine wrapper kept so existing call sites don't all need updating;
|
|
2684
|
-
* resolver-level memoization survives across method invocations.
|
|
2685
|
-
*/
|
|
2686
2352
|
getAllowedRange() {
|
|
2687
2353
|
return this.snaps.getAllowedRange();
|
|
2688
2354
|
}
|
|
2689
|
-
/**
|
|
2690
|
-
* Write ARIA slider value attributes onto the handle so the engine itself
|
|
2691
|
-
* is the source of truth for screen-reader announcements. Called from the
|
|
2692
|
-
* constructor and from every code path that mutates `activeId` /
|
|
2693
|
-
* `allowedIds`. WAI-ARIA 1.2 requires `aria-valuenow` for `role="slider"` —
|
|
2694
|
-
* without it screen readers can't announce drag/snap progress to keyboard
|
|
2695
|
-
* + AT users (WCAG 4.1.2 Name, Role, Value).
|
|
2696
|
-
*
|
|
2697
|
-
* Engine does NOT set `role="slider"` itself — that's the adapter's or
|
|
2698
|
-
* consumer markup's responsibility. We only decorate with dynamic values.
|
|
2699
|
-
*
|
|
2700
|
-
* Empty allowed-list is malformed input (settable via `setAllowed([])`);
|
|
2701
|
-
* short-circuit to remove ARIA value attributes so screen readers see a
|
|
2702
|
-
* no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
|
|
2703
|
-
* requires `valuemax > valuemin` for `role="slider"`).
|
|
2704
|
-
*/
|
|
2705
2355
|
updateAriaSlider() {
|
|
2706
2356
|
this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
|
|
2707
2357
|
}
|
|
@@ -2726,11 +2376,6 @@ var BottomSheetEngine = class {
|
|
|
2726
2376
|
element: this.element,
|
|
2727
2377
|
mode: this.mode,
|
|
2728
2378
|
getRoot: () => this.rootEl,
|
|
2729
|
-
// Single coalesced reader — narrows the DI surface and removes 3 getter
|
|
2730
|
-
// dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
|
|
2731
|
-
// (this.dragContextBuf) — see field JSDoc for the retention contract.
|
|
2732
|
-
// Inner `range` object is mutated in place too so callers caching its
|
|
2733
|
-
// identity between frames stay valid.
|
|
2734
2379
|
getDragContext: () => {
|
|
2735
2380
|
const buf = this.dragContextBuf;
|
|
2736
2381
|
const r = this.getAllowedRange();
|
|
@@ -2896,7 +2541,6 @@ var BottomSheetEngine = class {
|
|
|
2896
2541
|
}
|
|
2897
2542
|
});
|
|
2898
2543
|
}
|
|
2899
|
-
/** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
|
|
2900
2544
|
haptic() {
|
|
2901
2545
|
if (typeof navigator !== "undefined" && "vibrate" in navigator) {
|
|
2902
2546
|
try {
|
|
@@ -2912,6 +2556,9 @@ var BottomSheetEngine = class {
|
|
|
2912
2556
|
style.transform = this.transformTemplate(offset);
|
|
2913
2557
|
if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
|
|
2914
2558
|
style.setProperty("--bs-size", `${size}px`);
|
|
2559
|
+
if (this.scrimParent) {
|
|
2560
|
+
this.scrimParent.style.setProperty("--bs-size", `${size}px`);
|
|
2561
|
+
}
|
|
2915
2562
|
}
|
|
2916
2563
|
const progress = this.computeProgress(size);
|
|
2917
2564
|
const progressChanged = this.progressWriteSentinel.shouldWrite(
|
|
@@ -2982,7 +2629,7 @@ function useBottomSheet(opts) {
|
|
|
2982
2629
|
handle: handleRef.current ?? void 0,
|
|
2983
2630
|
scrollContainer: contentRef.current ?? void 0,
|
|
2984
2631
|
backdrop: backdropRef.current ?? void 0,
|
|
2985
|
-
|
|
2632
|
+
scrim: screenRef.current ?? void 0
|
|
2986
2633
|
});
|
|
2987
2634
|
engineRef.current = engine;
|
|
2988
2635
|
cachedSnapshotRef.current = { ...engine.state };
|
|
@@ -3116,6 +2763,11 @@ var BottomSheet = forwardRef(
|
|
|
3116
2763
|
open,
|
|
3117
2764
|
close,
|
|
3118
2765
|
setAllowed,
|
|
2766
|
+
setSnapPoints: (points, allowed) => getEngine()?.setSnapPoints(points, allowed),
|
|
2767
|
+
setScrim: (opts) => getEngine()?.setScrim(opts),
|
|
2768
|
+
setScrimOverlay: (opts) => getEngine()?.setScrimOverlay(opts) ?? (() => {
|
|
2769
|
+
}),
|
|
2770
|
+
getEngine,
|
|
3119
2771
|
get state() {
|
|
3120
2772
|
return getEngine()?.state ?? state;
|
|
3121
2773
|
}
|
|
@@ -3135,7 +2787,7 @@ var BottomSheet = forwardRef(
|
|
|
3135
2787
|
onClick: closeOnBackdrop ? () => void close() : void 0
|
|
3136
2788
|
}
|
|
3137
2789
|
),
|
|
3138
|
-
|
|
2790
|
+
/* @__PURE__ */ jsx(
|
|
3139
2791
|
"div",
|
|
3140
2792
|
{
|
|
3141
2793
|
ref: screenRef,
|
|
@@ -3258,8 +2910,6 @@ var OverlayEngine = class {
|
|
|
3258
2910
|
__publicField(this, "exitEasing");
|
|
3259
2911
|
__publicField(this, "backdropOpacity");
|
|
3260
2912
|
__publicField(this, "backdropFilter");
|
|
3261
|
-
// Definite-assignment: written via assignSwipeFromConfig in the
|
|
3262
|
-
// constructor — TS can't follow the helper, mark the assertion explicit.
|
|
3263
2913
|
__publicField(this, "swipeToCloseEnabled");
|
|
3264
2914
|
__publicField(this, "swipeThreshold");
|
|
3265
2915
|
__publicField(this, "swipeVelocityThreshold");
|
|
@@ -3279,13 +2929,7 @@ var OverlayEngine = class {
|
|
|
3279
2929
|
__publicField(this, "isOpen_", false);
|
|
3280
2930
|
__publicField(this, "isAnimating_", false);
|
|
3281
2931
|
__publicField(this, "destroyed", false);
|
|
3282
|
-
// Tracks whether setOverlayChildren ever populated the panel — lets
|
|
3283
|
-
// clearOverlayChildren skip the DOM mutation when nothing was injected
|
|
3284
|
-
// (so the field also acts as a zero-allocation feature flag).
|
|
3285
2932
|
__publicField(this, "hasInjectedChildren", false);
|
|
3286
|
-
// Bumped on every open/close. awaitTransition captures it and ignores
|
|
3287
|
-
// transitionend/timeout fired by older cycles — prevents cross-cycle
|
|
3288
|
-
// listener bleed during rapid toggling.
|
|
3289
2933
|
__publicField(this, "cycleNonce", 0);
|
|
3290
2934
|
__publicField(this, "bus", createEventBus());
|
|
3291
2935
|
__publicField(this, "detachEscape", null);
|
|
@@ -3338,9 +2982,6 @@ var OverlayEngine = class {
|
|
|
3338
2982
|
closeOnEscape: this.closeOnEscape,
|
|
3339
2983
|
lockBodyScroll: this.bodyScrollLockEnabled,
|
|
3340
2984
|
inertSiblings: this.inertSiblingsEnabled,
|
|
3341
|
-
// Overlay-specific guard: only paint body siblings inert when the
|
|
3342
|
-
// overlay element is actually a body descendant. Detached / shadow-
|
|
3343
|
-
// DOM / portal overlays would otherwise lock unrelated trees.
|
|
3344
2985
|
shouldApplyInertSiblings: () => {
|
|
3345
2986
|
const bodyChildren = Array.from(document.body.children);
|
|
3346
2987
|
return bodyChildren.some(
|
|
@@ -3494,12 +3135,6 @@ var OverlayEngine = class {
|
|
|
3494
3135
|
this.releaseStackEntry = null;
|
|
3495
3136
|
this.restoreMount();
|
|
3496
3137
|
}
|
|
3497
|
-
/**
|
|
3498
|
-
* Runtime visual setter — opacity is applied immediately when open so a
|
|
3499
|
-
* live-controls panel can preview the change without re-opening. When
|
|
3500
|
-
* closed we only stash the field; next open() picks it up so we don't
|
|
3501
|
-
* mutate hidden DOM (avoids paint thrash on detached panels).
|
|
3502
|
-
*/
|
|
3503
3138
|
setBackdropOpacity(opacity) {
|
|
3504
3139
|
if (this.destroyed) return;
|
|
3505
3140
|
this.backdropOpacity = opacity;
|
|
@@ -3507,11 +3142,6 @@ var OverlayEngine = class {
|
|
|
3507
3142
|
this.backdrop.style.opacity = String(opacity);
|
|
3508
3143
|
}
|
|
3509
3144
|
}
|
|
3510
|
-
/**
|
|
3511
|
-
* Runtime visual setter for `backdrop-filter`. `undefined` clears the
|
|
3512
|
-
* filter (mirrors the close() behaviour). Defers to next open() when the
|
|
3513
|
-
* overlay is currently closed.
|
|
3514
|
-
*/
|
|
3515
3145
|
setBackdropFilter(filter) {
|
|
3516
3146
|
if (this.destroyed) return;
|
|
3517
3147
|
this.backdropFilter = filter;
|
|
@@ -3525,12 +3155,6 @@ var OverlayEngine = class {
|
|
|
3525
3155
|
style.webkitBackdropFilter = "";
|
|
3526
3156
|
}
|
|
3527
3157
|
}
|
|
3528
|
-
/**
|
|
3529
|
-
* Toggle / reconfigure swipe-to-close at runtime. Re-resolves the flat
|
|
3530
|
-
* threshold fields, then installs or tears down the pointer listener so
|
|
3531
|
-
* the change applies to the CURRENT open cycle. Idempotent: safe to call
|
|
3532
|
-
* with the same config repeatedly.
|
|
3533
|
-
*/
|
|
3534
3158
|
setSwipeToClose(config) {
|
|
3535
3159
|
if (this.destroyed) return;
|
|
3536
3160
|
const wasEnabled = this.swipeToCloseEnabled;
|
|
@@ -3543,7 +3167,6 @@ var OverlayEngine = class {
|
|
|
3543
3167
|
this.detachSwipe = null;
|
|
3544
3168
|
}
|
|
3545
3169
|
}
|
|
3546
|
-
/** Animation setters only stage the field — applied on next open/close. */
|
|
3547
3170
|
setEnterAnimation(animation) {
|
|
3548
3171
|
if (this.destroyed) return;
|
|
3549
3172
|
this.enterAnimation = animation;
|
|
@@ -3552,23 +3175,15 @@ var OverlayEngine = class {
|
|
|
3552
3175
|
if (this.destroyed) return;
|
|
3553
3176
|
this.exitAnimation = animation;
|
|
3554
3177
|
}
|
|
3555
|
-
/** Updates the field used at next close() — no immediate side effect. */
|
|
3556
3178
|
setReturnFocus(target) {
|
|
3557
3179
|
if (this.destroyed) return;
|
|
3558
3180
|
this.lifecycle.setReturnFocus(target);
|
|
3559
3181
|
}
|
|
3560
|
-
/**
|
|
3561
|
-
* Batch update — preset first, then individual overrides. Applied as a
|
|
3562
|
-
* single DOM-write phase when open so live-controls don't trigger
|
|
3563
|
-
* cascading reflows. Safe to call when closed (defers to next open).
|
|
3564
|
-
*/
|
|
3565
3182
|
setOverlay(opts) {
|
|
3566
3183
|
if (this.destroyed) return;
|
|
3567
3184
|
const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
|
|
3568
3185
|
const merged = preset ? {
|
|
3569
3186
|
backdropOpacity: preset.backdropOpacity,
|
|
3570
|
-
// Preset's `undefined` filter means "no filter" — surface as null
|
|
3571
|
-
// so the merged step clears the field instead of inheriting old.
|
|
3572
3187
|
backdropFilter: preset.backdropFilter === void 0 ? null : preset.backdropFilter,
|
|
3573
3188
|
swipeToClose: preset.swipeToClose,
|
|
3574
3189
|
enterAnimation: preset.enterAnimation,
|
|
@@ -3627,30 +3242,17 @@ var OverlayEngine = class {
|
|
|
3627
3242
|
}
|
|
3628
3243
|
}
|
|
3629
3244
|
}
|
|
3630
|
-
/**
|
|
3631
|
-
* Runtime children injection — replace the panel's contents without
|
|
3632
|
-
* unmounting the engine. `replaceChildren()` is a single atomic DOM op,
|
|
3633
|
-
* so callers can swap content rapidly without intermediate flicker.
|
|
3634
|
-
* Function form is resolved exactly ONCE per call (cheap lazy content).
|
|
3635
|
-
*/
|
|
3636
3245
|
setOverlayChildren(children) {
|
|
3637
3246
|
if (this.destroyed) return;
|
|
3638
3247
|
const node = typeof children === "function" ? children() : children;
|
|
3639
3248
|
this.element.replaceChildren(node);
|
|
3640
3249
|
this.hasInjectedChildren = true;
|
|
3641
3250
|
}
|
|
3642
|
-
/**
|
|
3643
|
-
* Clear injected children. No-op when nothing was injected so consumers
|
|
3644
|
-
* who never used setOverlayChildren can call this defensively without
|
|
3645
|
-
* paying for a DOM mutation.
|
|
3646
|
-
*/
|
|
3647
3251
|
clearOverlayChildren() {
|
|
3648
3252
|
if (this.destroyed || !this.hasInjectedChildren) return;
|
|
3649
3253
|
this.element.replaceChildren();
|
|
3650
3254
|
this.hasInjectedChildren = false;
|
|
3651
3255
|
}
|
|
3652
|
-
// Shared swipe-config normalizer — used by constructor, setSwipeToClose,
|
|
3653
|
-
// and setOverlay so the union/object shape is decoded in exactly one place.
|
|
3654
3256
|
assignSwipeFromConfig(swipe) {
|
|
3655
3257
|
if (typeof swipe === "object" && swipe !== null) {
|
|
3656
3258
|
this.swipeToCloseEnabled = swipe.enabled ?? true;
|
|
@@ -3794,13 +3396,6 @@ var OverlayEngine = class {
|
|
|
3794
3396
|
this.detachOutsidePointer = null;
|
|
3795
3397
|
this.lifecycle.release();
|
|
3796
3398
|
}
|
|
3797
|
-
/**
|
|
3798
|
-
* Light pointer-driven dismiss. Tracks finger along the edge axis, follows
|
|
3799
|
-
* with translate, dismisses if past `swipeThreshold` (fraction of panel
|
|
3800
|
-
* size) or release velocity exceeds `swipeVelocityThreshold` (px/ms).
|
|
3801
|
-
* Pure CSS-transform during drag — no rAF or physics required since the
|
|
3802
|
-
* panel only moves one direction.
|
|
3803
|
-
*/
|
|
3804
3399
|
installSwipeToClose() {
|
|
3805
3400
|
const el = this.element;
|
|
3806
3401
|
const edge = this.edge;
|