@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.cjs
CHANGED
|
@@ -239,11 +239,6 @@ var SnapResolver = class {
|
|
|
239
239
|
this.allowedIds = allowed ?? raw.map((p) => p.id);
|
|
240
240
|
this.recompute();
|
|
241
241
|
}
|
|
242
|
-
/**
|
|
243
|
-
* Re-resolve raw → resolved against the current viewport / fit-measure,
|
|
244
|
-
* recompute maxAxisSize, invalidate range cache, notify engine. Called on
|
|
245
|
-
* resize, orientationchange, visualViewport changes, and snap-list swaps.
|
|
246
|
-
*/
|
|
247
242
|
recompute() {
|
|
248
243
|
this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
|
|
249
244
|
this.maxAxisSize = this.resolved.reduce(
|
|
@@ -253,15 +248,10 @@ var SnapResolver = class {
|
|
|
253
248
|
this.rangeCache = null;
|
|
254
249
|
this.onMaxAxisSizeChange?.(this.maxAxisSize);
|
|
255
250
|
}
|
|
256
|
-
/** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
|
|
257
251
|
setRaw(raw) {
|
|
258
252
|
this.raw = raw;
|
|
259
253
|
this.recompute();
|
|
260
254
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Replace the allow-list. Range cache depends on `(resolvedSnaps ×
|
|
263
|
-
* allowedIds)` so flipping ids alone invalidates without a full recompute.
|
|
264
|
-
*/
|
|
265
255
|
setAllowedIds(ids) {
|
|
266
256
|
this.allowedIds = ids;
|
|
267
257
|
this.rangeCache = null;
|
|
@@ -269,11 +259,6 @@ var SnapResolver = class {
|
|
|
269
259
|
findById(id) {
|
|
270
260
|
return findById(id, this.resolved);
|
|
271
261
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Pure drag-settle target resolution. Engine passes the gesture-specific
|
|
274
|
-
* fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
|
|
275
|
-
* `size`/`activeId` since those are still engine-owned.
|
|
276
|
-
*/
|
|
277
262
|
findDragSettleTarget(args) {
|
|
278
263
|
return findDragSettleTarget({
|
|
279
264
|
...args,
|
|
@@ -282,18 +267,9 @@ var SnapResolver = class {
|
|
|
282
267
|
maxAxisSize: this.maxAxisSize
|
|
283
268
|
});
|
|
284
269
|
}
|
|
285
|
-
/**
|
|
286
|
-
* Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
|
|
287
|
-
* applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
|
|
288
|
-
* to one recompute per geometry/allow-list change.
|
|
289
|
-
*/
|
|
290
270
|
getAllowedRange() {
|
|
291
271
|
return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
|
|
292
272
|
}
|
|
293
|
-
/**
|
|
294
|
-
* Read-only view of resolved snaps. Returned as `readonly` to discourage
|
|
295
|
-
* accidental mutation by feature install sites that close over this list.
|
|
296
|
-
*/
|
|
297
273
|
getResolvedSnaps() {
|
|
298
274
|
return this.resolved;
|
|
299
275
|
}
|
|
@@ -303,18 +279,6 @@ var SnapResolver = class {
|
|
|
303
279
|
getMaxAxisSize() {
|
|
304
280
|
return this.maxAxisSize;
|
|
305
281
|
}
|
|
306
|
-
/**
|
|
307
|
-
* External clamp override. The resize-observer feature compares
|
|
308
|
-
* `getMaxAxisSize()` against the live viewport and shrinks if the largest
|
|
309
|
-
* resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
|
|
310
|
-
* window resize). The next `recompute()` overwrites this clamp from the
|
|
311
|
-
* resolved-snap reduction.
|
|
312
|
-
*
|
|
313
|
-
* Does NOT invalidate the range cache — viewport clamping doesn't change
|
|
314
|
-
* `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
|
|
315
|
-
* so the resolver owns the layout-axis style write end-to-end (symmetric
|
|
316
|
-
* with `recompute()` — feature call sites no longer duplicate the write).
|
|
317
|
-
*/
|
|
318
282
|
setMaxAxisSize(n) {
|
|
319
283
|
this.maxAxisSize = n;
|
|
320
284
|
this.onMaxAxisSizeChange?.(n);
|
|
@@ -689,73 +653,49 @@ var WriteSentinel = class {
|
|
|
689
653
|
constructor() {
|
|
690
654
|
__publicField(this, "last", -1);
|
|
691
655
|
}
|
|
692
|
-
/**
|
|
693
|
-
* Returns `true` (and updates the cached value) if `next` differs from the
|
|
694
|
-
* cached `last` by more than `epsilon`. Returns `false` (and leaves the
|
|
695
|
-
* cache untouched) when the delta is within epsilon — caller skips the
|
|
696
|
-
* CSSOM write.
|
|
697
|
-
*/
|
|
698
656
|
shouldWrite(next, epsilon) {
|
|
699
657
|
if (Math.abs(next - this.last) <= epsilon) return false;
|
|
700
658
|
this.last = next;
|
|
701
659
|
return true;
|
|
702
660
|
}
|
|
703
|
-
/**
|
|
704
|
-
* Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
|
|
705
|
-
* non-negative `next` exceeds the epsilon comparison. Called from setters
|
|
706
|
-
* that mutate state under the cache (range/mode/enabled flips that change
|
|
707
|
-
* the progress→opacity mapping but don't move `progress` itself).
|
|
708
|
-
*/
|
|
709
661
|
invalidate() {
|
|
710
662
|
this.last = -1;
|
|
711
663
|
}
|
|
712
|
-
/**
|
|
713
|
-
* Record `value` as the last-written without performing a write. Used
|
|
714
|
-
* when a setter writes a literal value through a different path (e.g.
|
|
715
|
-
* `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
|
|
716
|
-
* `shouldWrite` calls to short-circuit against that known cached state.
|
|
717
|
-
*/
|
|
718
664
|
setLastWritten(value) {
|
|
719
665
|
this.last = value;
|
|
720
666
|
}
|
|
721
|
-
/** Read-only accessor for diagnostic / dedup-aware peek. */
|
|
722
667
|
get value() {
|
|
723
668
|
return this.last;
|
|
724
669
|
}
|
|
725
670
|
};
|
|
726
671
|
|
|
672
|
+
// src/core/primitives/devWarn.ts
|
|
673
|
+
function devWarn(...args) {
|
|
674
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
|
|
675
|
+
console.warn(...args);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
727
679
|
// src/core/controllers/scrim-controller.ts
|
|
680
|
+
var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
|
|
728
681
|
var ScrimController = class {
|
|
729
682
|
constructor(deps, opts) {
|
|
730
|
-
// ── DOM refs (mirror engine's; cached for hot path) ─────────────────
|
|
731
683
|
__publicField(this, "screenComponent");
|
|
732
684
|
__publicField(this, "backdrop");
|
|
733
685
|
__publicField(this, "mode");
|
|
734
|
-
// ── Engine callbacks ────────────────────────────────────────────────
|
|
735
686
|
__publicField(this, "isDestroyed");
|
|
736
687
|
__publicField(this, "isTopSheet");
|
|
737
688
|
__publicField(this, "getAllowedIds");
|
|
738
689
|
__publicField(this, "getResolvedSnaps");
|
|
739
690
|
__publicField(this, "snapToFn");
|
|
740
|
-
// ── Mode + state ────────────────────────────────────────────────────
|
|
741
691
|
__publicField(this, "scrimMode");
|
|
742
692
|
__publicField(this, "backdropRange");
|
|
743
693
|
__publicField(this, "screenRange");
|
|
744
|
-
// setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
|
|
745
|
-
// ranges so re-enable can restore prior values without a remount. Public
|
|
746
|
-
// so tests / introspection can read it without an `as any` cast.
|
|
747
694
|
__publicField(this, "scrimEnabled", true);
|
|
748
695
|
__publicField(this, "savedScrimRanges", null);
|
|
749
|
-
// ── Tap-to-close ────────────────────────────────────────────────────
|
|
750
696
|
__publicField(this, "scrimTapToCloseEnabled", false);
|
|
751
697
|
__publicField(this, "detachScrimTap", null);
|
|
752
|
-
// ── Scrim overlay slot (positioned floating element) ────────────────
|
|
753
698
|
__publicField(this, "scrimOverlayTeardown", null);
|
|
754
|
-
// ── Hot-path opacity dedup caches (per-frame writes) ────────────────
|
|
755
|
-
// Numeric sentinels migrated to WriteSentinel primitive — single
|
|
756
|
-
// invalidation surface, shared with the engine's size/progress sentinels.
|
|
757
|
-
// The two string-typed sentinels (`Pointer`, `Display`) keep their inline
|
|
758
|
-
// pattern: equality on string union, no epsilon, different reset semantics.
|
|
759
699
|
__publicField(this, "backdropOpacitySentinel", new WriteSentinel());
|
|
760
700
|
__publicField(this, "screenOpacitySentinel", new WriteSentinel());
|
|
761
701
|
__publicField(this, "lastBackdropPointer", "");
|
|
@@ -790,41 +730,15 @@ var ScrimController = class {
|
|
|
790
730
|
}
|
|
791
731
|
}
|
|
792
732
|
}
|
|
793
|
-
/**
|
|
794
|
-
* Invalidate the opacity dedup caches. Called from any setter that mutates
|
|
795
|
-
* range/mode/enabled — opacity is a pure function of (progress, range,
|
|
796
|
-
* enabled), so a config change makes the cached values stale even if
|
|
797
|
-
* `progress` itself didn't move. Next `applyOpacity(progress, false)` call
|
|
798
|
-
* sees the -1 sentinel and re-runs the math instead of short-circuiting.
|
|
799
|
-
*
|
|
800
|
-
* Public for the engine: `setSnapPoints` mutates resolved geometry which
|
|
801
|
-
* shifts `progress→opacity` mapping under the cache; engine calls this to
|
|
802
|
-
* keep the next render frame correct on a static sheet.
|
|
803
|
-
* @internal
|
|
804
|
-
*/
|
|
805
733
|
invalidateOpacityCache() {
|
|
806
734
|
this.backdropOpacitySentinel.invalidate();
|
|
807
735
|
this.screenOpacitySentinel.invalidate();
|
|
808
736
|
}
|
|
809
|
-
/** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
|
|
810
737
|
attach() {
|
|
811
738
|
if (this.scrimTapToCloseEnabled) {
|
|
812
739
|
this.setScrimTapToClose(true);
|
|
813
740
|
}
|
|
814
741
|
}
|
|
815
|
-
// ── Hot-path: opacity computation called per-frame from engine.applySize ──
|
|
816
|
-
/**
|
|
817
|
-
* Per-frame opacity writer. Computes backdrop + screen opacity from the
|
|
818
|
-
* sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
|
|
819
|
-
* caches so steady-state frames don't trigger style recalc.
|
|
820
|
-
*
|
|
821
|
-
* `progressChanged` is the engine's already-computed dedup gate — when
|
|
822
|
-
* `false`, opacity is by construction a pure function of `progress` so
|
|
823
|
-
* cached values are still valid; we short-circuit the Math.min/max +
|
|
824
|
-
* division work entirely. Setters that mutate ranges/mode/enabled
|
|
825
|
-
* invalidate the cache by writing -1 sentinels so the next call re-runs
|
|
826
|
-
* the math.
|
|
827
|
-
*/
|
|
828
742
|
applyOpacity(progress, progressChanged) {
|
|
829
743
|
if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
|
|
830
744
|
return;
|
|
@@ -870,80 +784,30 @@ var ScrimController = class {
|
|
|
870
784
|
}
|
|
871
785
|
}
|
|
872
786
|
}
|
|
873
|
-
// ── Public setters (engine forwards these 1:1) ─────────────────────
|
|
874
|
-
/**
|
|
875
|
-
* Replace the [start, end] progress window in which the backdrop fades 0→1.
|
|
876
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
877
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
878
|
-
* @example
|
|
879
|
-
* // Backdrop only starts darkening after the sheet crosses 30% open.
|
|
880
|
-
* engine.setBackdropRange([0.3, 1]);
|
|
881
|
-
*/
|
|
882
787
|
setBackdropRange(range, applySize) {
|
|
883
788
|
if (this.isDestroyed()) return;
|
|
884
789
|
this.backdropRange = range;
|
|
885
790
|
this.invalidateOpacityCache();
|
|
886
791
|
applySize();
|
|
887
792
|
}
|
|
888
|
-
/**
|
|
889
|
-
* Replace the [start, end] progress window in which the screen scrim fades 0→1.
|
|
890
|
-
* @param range - Tuple of two progress values in 0..1 (start <= end).
|
|
891
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
892
|
-
* @example
|
|
893
|
-
* // Scrim is fully opaque only when the sheet is past 50% — softer feel.
|
|
894
|
-
* engine.setScreenRange([0, 0.5]);
|
|
895
|
-
*/
|
|
896
793
|
setScreenRange(range, applySize) {
|
|
897
794
|
if (this.isDestroyed()) return;
|
|
898
795
|
this.screenRange = range;
|
|
899
796
|
this.invalidateOpacityCache();
|
|
900
797
|
applySize();
|
|
901
798
|
}
|
|
902
|
-
/**
|
|
903
|
-
* Set the scrim background color. Pass `null`/`undefined` to clear the
|
|
904
|
-
* inline write and let consumer CSS take over.
|
|
905
|
-
* @param color - Any valid CSS color string, or `null`/`undefined` to clear.
|
|
906
|
-
* @example
|
|
907
|
-
* engine.setScrimColor("rgba(0, 0, 0, 0.6)");
|
|
908
|
-
* engine.setScrimColor(null); // hand back to consumer CSS
|
|
909
|
-
*/
|
|
910
799
|
setScrimColor(color) {
|
|
911
800
|
if (this.isDestroyed()) return;
|
|
912
801
|
this.applyScrimStyles({ color: color === void 0 ? null : color });
|
|
913
802
|
}
|
|
914
|
-
/**
|
|
915
|
-
* Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
|
|
916
|
-
* @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
|
|
917
|
-
* `null`/`undefined` to remove the filter.
|
|
918
|
-
* @example
|
|
919
|
-
* engine.setScrimBlur("12px"); // frosted-glass effect
|
|
920
|
-
* engine.setScrimBlur(null); // disable blur
|
|
921
|
-
*/
|
|
922
803
|
setScrimBlur(blur) {
|
|
923
804
|
if (this.isDestroyed()) return;
|
|
924
805
|
this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
|
|
925
806
|
}
|
|
926
|
-
/**
|
|
927
|
-
* Toggle whether the scrim layer captures pointer events.
|
|
928
|
-
* @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
|
|
929
|
-
* @example
|
|
930
|
-
* engine.setScrimInteractive(true); // scrim swallows clicks
|
|
931
|
-
* engine.setScrimInteractive(false); // clicks pass through to content below
|
|
932
|
-
*/
|
|
933
807
|
setScrimInteractive(interactive) {
|
|
934
808
|
if (this.isDestroyed()) return;
|
|
935
809
|
this.applyScrimStyles({ interactive });
|
|
936
810
|
}
|
|
937
|
-
/**
|
|
938
|
-
* Switch scrim positioning at runtime.
|
|
939
|
-
* @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
|
|
940
|
-
* so only the area NOT covered by the sheet is dimmed), or
|
|
941
|
-
* `"off"` (display:none + opacity:0 — no remount needed).
|
|
942
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
943
|
-
* @example
|
|
944
|
-
* engine.setScrimMode("above-sheet"); // dim only the area above the sheet
|
|
945
|
-
* engine.setScrimMode("off"); // disable scrim layer
|
|
946
|
-
*/
|
|
947
811
|
setScrimMode(mode, applySize) {
|
|
948
812
|
if (this.isDestroyed()) return;
|
|
949
813
|
if (mode === this.scrimMode) return;
|
|
@@ -973,15 +837,6 @@ var ScrimController = class {
|
|
|
973
837
|
}
|
|
974
838
|
applySize();
|
|
975
839
|
}
|
|
976
|
-
/**
|
|
977
|
-
* Enable or disable click-on-scrim → snap to first non-zero allowed snap.
|
|
978
|
-
* Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
|
|
979
|
-
* click listener actually fires.
|
|
980
|
-
* @param enabled - `true` installs the click listener; `false` removes it.
|
|
981
|
-
* @example
|
|
982
|
-
* engine.setScrimTapToClose(true); // tap outside sheet collapses it
|
|
983
|
-
* engine.setScrimTapToClose(false); // require explicit close button
|
|
984
|
-
*/
|
|
985
840
|
setScrimTapToClose(enabled) {
|
|
986
841
|
if (this.isDestroyed()) return;
|
|
987
842
|
if (!this.screenComponent) return;
|
|
@@ -1011,16 +866,6 @@ var ScrimController = class {
|
|
|
1011
866
|
this.scrimTapToCloseEnabled = false;
|
|
1012
867
|
}
|
|
1013
868
|
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Toggle the scrim layer without remounting. Disabling stashes current
|
|
1016
|
-
* `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
|
|
1017
|
-
* formula → 0 across the whole range); re-enabling restores the saved values.
|
|
1018
|
-
* @param enabled - `false` hides scrim, `true` restores prior ranges.
|
|
1019
|
-
* @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
|
|
1020
|
-
* @example
|
|
1021
|
-
* engine.setScrimEnabled(false); // hide scrim, ranges remembered
|
|
1022
|
-
* engine.setScrimEnabled(true); // restore prior ranges (no remount)
|
|
1023
|
-
*/
|
|
1024
869
|
setScrimEnabled(enabled, applySize) {
|
|
1025
870
|
if (this.isDestroyed()) return;
|
|
1026
871
|
if (enabled === this.scrimEnabled) return;
|
|
@@ -1039,20 +884,6 @@ var ScrimController = class {
|
|
|
1039
884
|
}
|
|
1040
885
|
applySize();
|
|
1041
886
|
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Batch-update multiple scrim fields with a single applySize() at the end.
|
|
1044
|
-
* If `preset` is set, it seeds color/blur/interactive/range first; explicit
|
|
1045
|
-
* fields override.
|
|
1046
|
-
* @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
|
|
1047
|
-
* color, blur, interactive, range.
|
|
1048
|
-
* @param applySize - Engine-supplied recompute hook; runs ONCE per call.
|
|
1049
|
-
* @example
|
|
1050
|
-
* engine.setScrim({
|
|
1051
|
-
* preset: "frosted",
|
|
1052
|
-
* mode: "above-sheet",
|
|
1053
|
-
* tapToClose: true,
|
|
1054
|
-
* });
|
|
1055
|
-
*/
|
|
1056
887
|
setScrim(opts, applySize) {
|
|
1057
888
|
if (this.isDestroyed()) return;
|
|
1058
889
|
if (opts.preset) {
|
|
@@ -1075,27 +906,18 @@ var ScrimController = class {
|
|
|
1075
906
|
interactive: opts.interactive
|
|
1076
907
|
});
|
|
1077
908
|
if (opts.range !== void 0) {
|
|
1078
|
-
this.
|
|
909
|
+
if (!this.scrimEnabled && this.savedScrimRanges) {
|
|
910
|
+
this.savedScrimRanges = {
|
|
911
|
+
screen: opts.range,
|
|
912
|
+
backdrop: this.savedScrimRanges.backdrop
|
|
913
|
+
};
|
|
914
|
+
} else {
|
|
915
|
+
this.screenRange = opts.range;
|
|
916
|
+
}
|
|
1079
917
|
}
|
|
1080
918
|
this.invalidateOpacityCache();
|
|
1081
919
|
applySize();
|
|
1082
920
|
}
|
|
1083
|
-
/**
|
|
1084
|
-
* Inject a positioned floating element into the scrim layer (e.g. a close
|
|
1085
|
-
* button anchored top-right). Replaces any prior overlay so repeated calls
|
|
1086
|
-
* (reactive effects re-running) don't accumulate wrappers.
|
|
1087
|
-
* @param opts - Children element, optional `inset` (default `"16px"`),
|
|
1088
|
-
* `position` (9-way grid, default `"top-right"`), and
|
|
1089
|
-
* `interactive` flag (default `true`).
|
|
1090
|
-
* @returns Teardown function that removes the wrapper from the DOM.
|
|
1091
|
-
* @example
|
|
1092
|
-
* const teardown = engine.setScrimOverlay({
|
|
1093
|
-
* children: closeBtn,
|
|
1094
|
-
* position: "top-right",
|
|
1095
|
-
* inset: "20px",
|
|
1096
|
-
* });
|
|
1097
|
-
* // later: teardown(); // remove the floating button
|
|
1098
|
-
*/
|
|
1099
921
|
setScrimOverlay(opts) {
|
|
1100
922
|
if (this.isDestroyed()) return () => {
|
|
1101
923
|
};
|
|
@@ -1103,7 +925,18 @@ var ScrimController = class {
|
|
|
1103
925
|
};
|
|
1104
926
|
this.scrimOverlayTeardown?.();
|
|
1105
927
|
const screen = this.screenComponent;
|
|
1106
|
-
const
|
|
928
|
+
const insetRaw = opts.inset ?? "16px";
|
|
929
|
+
const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
|
|
930
|
+
if (inset !== insetRaw) {
|
|
931
|
+
devWarn(
|
|
932
|
+
`[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
|
|
936
|
+
devWarn(
|
|
937
|
+
"[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."
|
|
938
|
+
);
|
|
939
|
+
}
|
|
1107
940
|
const position = opts.position ?? "top-right";
|
|
1108
941
|
const interactive = opts.interactive ?? true;
|
|
1109
942
|
const wrapper = screen.ownerDocument.createElement("div");
|
|
@@ -1152,12 +985,30 @@ var ScrimController = class {
|
|
|
1152
985
|
ws.bottom = inset;
|
|
1153
986
|
ws.right = inset;
|
|
1154
987
|
break;
|
|
988
|
+
case "sheet-top-left":
|
|
989
|
+
case "sheet-top-center":
|
|
990
|
+
case "sheet-top-right":
|
|
991
|
+
applySheetAnchoredPosition(
|
|
992
|
+
ws,
|
|
993
|
+
this.mode,
|
|
994
|
+
position,
|
|
995
|
+
inset
|
|
996
|
+
);
|
|
997
|
+
break;
|
|
1155
998
|
}
|
|
1156
999
|
if (interactive) {
|
|
1157
1000
|
ws.pointerEvents = "auto";
|
|
1158
1001
|
}
|
|
1159
1002
|
wrapper.appendChild(opts.children);
|
|
1160
|
-
screen.
|
|
1003
|
+
const host = screen.parentElement;
|
|
1004
|
+
if (!host) {
|
|
1005
|
+
devWarn(
|
|
1006
|
+
"[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
|
|
1007
|
+
);
|
|
1008
|
+
return () => {
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
host.appendChild(wrapper);
|
|
1161
1012
|
const teardown = () => {
|
|
1162
1013
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
1163
1014
|
if (this.scrimOverlayTeardown === teardown) {
|
|
@@ -1167,7 +1018,6 @@ var ScrimController = class {
|
|
|
1167
1018
|
this.scrimOverlayTeardown = teardown;
|
|
1168
1019
|
return teardown;
|
|
1169
1020
|
}
|
|
1170
|
-
/** Tear down toggle-state listeners + remove any injected overlay wrapper. */
|
|
1171
1021
|
destroy() {
|
|
1172
1022
|
this.detachScrimTap?.();
|
|
1173
1023
|
this.detachScrimTap = null;
|
|
@@ -1186,8 +1036,6 @@ var ScrimController = class {
|
|
|
1186
1036
|
this.lastBackdropPointer = "";
|
|
1187
1037
|
this.lastScreenDisplay = "";
|
|
1188
1038
|
}
|
|
1189
|
-
// ── Private helpers ────────────────────────────────────────────────
|
|
1190
|
-
/** Centralized scrim style writer — atomic 3-property write across all setters. */
|
|
1191
1039
|
applyScrimStyles(opts) {
|
|
1192
1040
|
if (!this.screenComponent) return;
|
|
1193
1041
|
const s = this.screenComponent.style;
|
|
@@ -1209,7 +1057,6 @@ var ScrimController = class {
|
|
|
1209
1057
|
s.pointerEvents = opts.interactive ? "auto" : "none";
|
|
1210
1058
|
}
|
|
1211
1059
|
}
|
|
1212
|
-
/** Pin the screen overlay to the area NOT occupied by the sheet. */
|
|
1213
1060
|
applyAboveSheetInset() {
|
|
1214
1061
|
if (!this.screenComponent) return;
|
|
1215
1062
|
const insetByMode = {
|
|
@@ -1224,6 +1071,35 @@ var ScrimController = class {
|
|
|
1224
1071
|
s.pointerEvents = "none";
|
|
1225
1072
|
}
|
|
1226
1073
|
};
|
|
1074
|
+
function resolveSheetAnchoredStyle(mode, position, inset) {
|
|
1075
|
+
const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
|
|
1076
|
+
switch (mode) {
|
|
1077
|
+
case "bottom":
|
|
1078
|
+
if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
|
|
1079
|
+
if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
|
|
1080
|
+
return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
1081
|
+
case "top":
|
|
1082
|
+
if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
|
|
1083
|
+
if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
|
|
1084
|
+
return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
|
|
1085
|
+
case "left":
|
|
1086
|
+
if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
|
|
1087
|
+
if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
|
|
1088
|
+
return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
1089
|
+
case "right":
|
|
1090
|
+
if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
|
|
1091
|
+
if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
|
|
1092
|
+
return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function applySheetAnchoredPosition(ws, mode, position, inset) {
|
|
1096
|
+
const style = resolveSheetAnchoredStyle(mode, position, inset);
|
|
1097
|
+
if (style.top !== void 0) ws.top = style.top;
|
|
1098
|
+
if (style.bottom !== void 0) ws.bottom = style.bottom;
|
|
1099
|
+
if (style.left !== void 0) ws.left = style.left;
|
|
1100
|
+
if (style.right !== void 0) ws.right = style.right;
|
|
1101
|
+
if (style.transform !== void 0) ws.transform = style.transform;
|
|
1102
|
+
}
|
|
1227
1103
|
|
|
1228
1104
|
// src/core/animation/animation.ts
|
|
1229
1105
|
var easeOutBack = (t) => {
|
|
@@ -1376,13 +1252,9 @@ var AnimationRunner = class {
|
|
|
1376
1252
|
__publicField(this, "duration");
|
|
1377
1253
|
__publicField(this, "easing");
|
|
1378
1254
|
__publicField(this, "respectReducedMotion");
|
|
1379
|
-
/** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
|
|
1380
1255
|
__publicField(this, "reducedMotion", false);
|
|
1381
|
-
/** Disposes the matchMedia change listener — drained from destroy(). */
|
|
1382
1256
|
__publicField(this, "detachReducedMotion", null);
|
|
1383
|
-
/** Computed once — both inputs (option + API presence) stable for life. */
|
|
1384
1257
|
__publicField(this, "viewTransitionsAvailable");
|
|
1385
|
-
/** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
|
|
1386
1258
|
__publicField(this, "currentTween", null);
|
|
1387
1259
|
__publicField(this, "currentSpring", null);
|
|
1388
1260
|
this.element = deps.element;
|
|
@@ -1413,26 +1285,13 @@ var AnimationRunner = class {
|
|
|
1413
1285
|
}
|
|
1414
1286
|
}
|
|
1415
1287
|
}
|
|
1416
|
-
/**
|
|
1417
|
-
* Live derived flag — `true` whenever a spring/tween handle is in flight.
|
|
1418
|
-
* Reading the canonical handles (instead of mirroring into a separate
|
|
1419
|
-
* boolean) eliminates the reduced-motion early-return latent bug where
|
|
1420
|
-
* the flag stayed true forever, and removes the cross-controller
|
|
1421
|
-
* setIsAnimating callback.
|
|
1422
|
-
*/
|
|
1423
1288
|
get isAnimating() {
|
|
1424
1289
|
return this.currentTween !== null || this.currentSpring !== null;
|
|
1425
1290
|
}
|
|
1426
|
-
/** Cancel any in-flight tween/spring without touching engine state. */
|
|
1427
1291
|
cancel() {
|
|
1428
1292
|
this.currentTween?.cancel();
|
|
1429
1293
|
this.currentSpring?.cancel();
|
|
1430
1294
|
}
|
|
1431
|
-
/**
|
|
1432
|
-
* Animate from current size to `target`. Resolves when the animation
|
|
1433
|
-
* completes; resolves early if cancelled mid-flight (caller checks an
|
|
1434
|
-
* abort signal post-await for cycle-invariant correctness).
|
|
1435
|
-
*/
|
|
1436
1295
|
async animateTo(target, velocityPxPerMs) {
|
|
1437
1296
|
if (target === this.getSize()) return;
|
|
1438
1297
|
this.currentTween?.cancel();
|
|
@@ -1473,7 +1332,6 @@ var AnimationRunner = class {
|
|
|
1473
1332
|
if (!this.isDragging()) this.element.style.willChange = "auto";
|
|
1474
1333
|
}
|
|
1475
1334
|
}
|
|
1476
|
-
/** Tear down the matchMedia listener. Engine calls from destroy(). */
|
|
1477
1335
|
destroy() {
|
|
1478
1336
|
this.detachReducedMotion?.();
|
|
1479
1337
|
this.detachReducedMotion = null;
|
|
@@ -1633,12 +1491,8 @@ var LifecycleController = class {
|
|
|
1633
1491
|
__publicField(this, "inertSiblings");
|
|
1634
1492
|
__publicField(this, "shouldApplyInertSiblings");
|
|
1635
1493
|
__publicField(this, "returnFocus");
|
|
1636
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
1637
1494
|
__publicField(this, "releaseFocusTrap", null);
|
|
1638
1495
|
__publicField(this, "releaseScrollLock", null);
|
|
1639
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
1640
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
1641
|
-
* released or never-installed instances. */
|
|
1642
1496
|
__publicField(this, "installed", false);
|
|
1643
1497
|
__publicField(this, "destroyed", false);
|
|
1644
1498
|
this.element = deps.element;
|
|
@@ -1652,14 +1506,6 @@ var LifecycleController = class {
|
|
|
1652
1506
|
this.returnFocus = opts.returnFocus;
|
|
1653
1507
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
1654
1508
|
}
|
|
1655
|
-
/**
|
|
1656
|
-
* Run the open-time install steps. Idempotent: each step checks its
|
|
1657
|
-
* existing teardown ref so a re-open during a half-cancelled close
|
|
1658
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
1659
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
1660
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
1661
|
-
* No-op when destroyed.
|
|
1662
|
-
*/
|
|
1663
1509
|
install() {
|
|
1664
1510
|
if (this.destroyed) return;
|
|
1665
1511
|
this.installed = true;
|
|
@@ -1676,22 +1522,10 @@ var LifecycleController = class {
|
|
|
1676
1522
|
this.inertSiblings.apply();
|
|
1677
1523
|
}
|
|
1678
1524
|
}
|
|
1679
|
-
/**
|
|
1680
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
1681
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
1682
|
-
* on the next `release()`. No-op when destroyed.
|
|
1683
|
-
*/
|
|
1684
1525
|
setReturnFocus(target) {
|
|
1685
1526
|
if (this.destroyed) return;
|
|
1686
1527
|
this.returnFocus = target;
|
|
1687
1528
|
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
1690
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
1691
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
1692
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
1693
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
1694
|
-
*/
|
|
1695
1529
|
release() {
|
|
1696
1530
|
if (!this.installed) return;
|
|
1697
1531
|
this.installed = false;
|
|
@@ -1705,12 +1539,6 @@ var LifecycleController = class {
|
|
|
1705
1539
|
target?.focus?.();
|
|
1706
1540
|
}
|
|
1707
1541
|
}
|
|
1708
|
-
/**
|
|
1709
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
1710
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
1711
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
1712
|
-
* restore.
|
|
1713
|
-
*/
|
|
1714
1542
|
destroy() {
|
|
1715
1543
|
this.release();
|
|
1716
1544
|
this.destroyed = true;
|
|
@@ -1720,12 +1548,10 @@ var LifecycleController = class {
|
|
|
1720
1548
|
// src/core/gestures.ts
|
|
1721
1549
|
var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
|
|
1722
1550
|
var SampleRing = class {
|
|
1723
|
-
// current count, ≤ capacity
|
|
1724
1551
|
constructor(capacity) {
|
|
1725
1552
|
__publicField(this, "slots");
|
|
1726
1553
|
__publicField(this, "capacity");
|
|
1727
1554
|
__publicField(this, "head", 0);
|
|
1728
|
-
// next write index
|
|
1729
1555
|
__publicField(this, "size", 0);
|
|
1730
1556
|
this.capacity = capacity;
|
|
1731
1557
|
this.slots = new Array(capacity);
|
|
@@ -1738,12 +1564,6 @@ var SampleRing = class {
|
|
|
1738
1564
|
this.head = (this.head + 1) % this.capacity;
|
|
1739
1565
|
if (this.size < this.capacity) this.size++;
|
|
1740
1566
|
}
|
|
1741
|
-
/**
|
|
1742
|
-
* Drop oldest entries whose timestamp is below `cutoff`, but never shrink
|
|
1743
|
-
* below `minKeep`. Mirrors the original
|
|
1744
|
-
* `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
|
|
1745
|
-
* but without copying — just advances the logical tail.
|
|
1746
|
-
*/
|
|
1747
1567
|
pruneBefore(cutoff, minKeep) {
|
|
1748
1568
|
while (this.size > minKeep) {
|
|
1749
1569
|
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(
|
|
@@ -2984,7 +2631,7 @@ function useBottomSheet(opts) {
|
|
|
2984
2631
|
handle: handleRef.current ?? void 0,
|
|
2985
2632
|
scrollContainer: contentRef.current ?? void 0,
|
|
2986
2633
|
backdrop: backdropRef.current ?? void 0,
|
|
2987
|
-
|
|
2634
|
+
scrim: screenRef.current ?? void 0
|
|
2988
2635
|
});
|
|
2989
2636
|
engineRef.current = engine;
|
|
2990
2637
|
cachedSnapshotRef.current = { ...engine.state };
|
|
@@ -3118,6 +2765,11 @@ var BottomSheet = react.forwardRef(
|
|
|
3118
2765
|
open,
|
|
3119
2766
|
close,
|
|
3120
2767
|
setAllowed,
|
|
2768
|
+
setSnapPoints: (points, allowed) => getEngine()?.setSnapPoints(points, allowed),
|
|
2769
|
+
setScrim: (opts) => getEngine()?.setScrim(opts),
|
|
2770
|
+
setScrimOverlay: (opts) => getEngine()?.setScrimOverlay(opts) ?? (() => {
|
|
2771
|
+
}),
|
|
2772
|
+
getEngine,
|
|
3121
2773
|
get state() {
|
|
3122
2774
|
return getEngine()?.state ?? state;
|
|
3123
2775
|
}
|
|
@@ -3137,7 +2789,7 @@ var BottomSheet = react.forwardRef(
|
|
|
3137
2789
|
onClick: closeOnBackdrop ? () => void close() : void 0
|
|
3138
2790
|
}
|
|
3139
2791
|
),
|
|
3140
|
-
|
|
2792
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3141
2793
|
"div",
|
|
3142
2794
|
{
|
|
3143
2795
|
ref: screenRef,
|
|
@@ -3260,8 +2912,6 @@ var OverlayEngine = class {
|
|
|
3260
2912
|
__publicField(this, "exitEasing");
|
|
3261
2913
|
__publicField(this, "backdropOpacity");
|
|
3262
2914
|
__publicField(this, "backdropFilter");
|
|
3263
|
-
// Definite-assignment: written via assignSwipeFromConfig in the
|
|
3264
|
-
// constructor — TS can't follow the helper, mark the assertion explicit.
|
|
3265
2915
|
__publicField(this, "swipeToCloseEnabled");
|
|
3266
2916
|
__publicField(this, "swipeThreshold");
|
|
3267
2917
|
__publicField(this, "swipeVelocityThreshold");
|
|
@@ -3281,13 +2931,7 @@ var OverlayEngine = class {
|
|
|
3281
2931
|
__publicField(this, "isOpen_", false);
|
|
3282
2932
|
__publicField(this, "isAnimating_", false);
|
|
3283
2933
|
__publicField(this, "destroyed", false);
|
|
3284
|
-
// Tracks whether setOverlayChildren ever populated the panel — lets
|
|
3285
|
-
// clearOverlayChildren skip the DOM mutation when nothing was injected
|
|
3286
|
-
// (so the field also acts as a zero-allocation feature flag).
|
|
3287
2934
|
__publicField(this, "hasInjectedChildren", false);
|
|
3288
|
-
// Bumped on every open/close. awaitTransition captures it and ignores
|
|
3289
|
-
// transitionend/timeout fired by older cycles — prevents cross-cycle
|
|
3290
|
-
// listener bleed during rapid toggling.
|
|
3291
2935
|
__publicField(this, "cycleNonce", 0);
|
|
3292
2936
|
__publicField(this, "bus", createEventBus());
|
|
3293
2937
|
__publicField(this, "detachEscape", null);
|
|
@@ -3340,9 +2984,6 @@ var OverlayEngine = class {
|
|
|
3340
2984
|
closeOnEscape: this.closeOnEscape,
|
|
3341
2985
|
lockBodyScroll: this.bodyScrollLockEnabled,
|
|
3342
2986
|
inertSiblings: this.inertSiblingsEnabled,
|
|
3343
|
-
// Overlay-specific guard: only paint body siblings inert when the
|
|
3344
|
-
// overlay element is actually a body descendant. Detached / shadow-
|
|
3345
|
-
// DOM / portal overlays would otherwise lock unrelated trees.
|
|
3346
2987
|
shouldApplyInertSiblings: () => {
|
|
3347
2988
|
const bodyChildren = Array.from(document.body.children);
|
|
3348
2989
|
return bodyChildren.some(
|
|
@@ -3496,12 +3137,6 @@ var OverlayEngine = class {
|
|
|
3496
3137
|
this.releaseStackEntry = null;
|
|
3497
3138
|
this.restoreMount();
|
|
3498
3139
|
}
|
|
3499
|
-
/**
|
|
3500
|
-
* Runtime visual setter — opacity is applied immediately when open so a
|
|
3501
|
-
* live-controls panel can preview the change without re-opening. When
|
|
3502
|
-
* closed we only stash the field; next open() picks it up so we don't
|
|
3503
|
-
* mutate hidden DOM (avoids paint thrash on detached panels).
|
|
3504
|
-
*/
|
|
3505
3140
|
setBackdropOpacity(opacity) {
|
|
3506
3141
|
if (this.destroyed) return;
|
|
3507
3142
|
this.backdropOpacity = opacity;
|
|
@@ -3509,11 +3144,6 @@ var OverlayEngine = class {
|
|
|
3509
3144
|
this.backdrop.style.opacity = String(opacity);
|
|
3510
3145
|
}
|
|
3511
3146
|
}
|
|
3512
|
-
/**
|
|
3513
|
-
* Runtime visual setter for `backdrop-filter`. `undefined` clears the
|
|
3514
|
-
* filter (mirrors the close() behaviour). Defers to next open() when the
|
|
3515
|
-
* overlay is currently closed.
|
|
3516
|
-
*/
|
|
3517
3147
|
setBackdropFilter(filter) {
|
|
3518
3148
|
if (this.destroyed) return;
|
|
3519
3149
|
this.backdropFilter = filter;
|
|
@@ -3527,12 +3157,6 @@ var OverlayEngine = class {
|
|
|
3527
3157
|
style.webkitBackdropFilter = "";
|
|
3528
3158
|
}
|
|
3529
3159
|
}
|
|
3530
|
-
/**
|
|
3531
|
-
* Toggle / reconfigure swipe-to-close at runtime. Re-resolves the flat
|
|
3532
|
-
* threshold fields, then installs or tears down the pointer listener so
|
|
3533
|
-
* the change applies to the CURRENT open cycle. Idempotent: safe to call
|
|
3534
|
-
* with the same config repeatedly.
|
|
3535
|
-
*/
|
|
3536
3160
|
setSwipeToClose(config) {
|
|
3537
3161
|
if (this.destroyed) return;
|
|
3538
3162
|
const wasEnabled = this.swipeToCloseEnabled;
|
|
@@ -3545,7 +3169,6 @@ var OverlayEngine = class {
|
|
|
3545
3169
|
this.detachSwipe = null;
|
|
3546
3170
|
}
|
|
3547
3171
|
}
|
|
3548
|
-
/** Animation setters only stage the field — applied on next open/close. */
|
|
3549
3172
|
setEnterAnimation(animation) {
|
|
3550
3173
|
if (this.destroyed) return;
|
|
3551
3174
|
this.enterAnimation = animation;
|
|
@@ -3554,23 +3177,15 @@ var OverlayEngine = class {
|
|
|
3554
3177
|
if (this.destroyed) return;
|
|
3555
3178
|
this.exitAnimation = animation;
|
|
3556
3179
|
}
|
|
3557
|
-
/** Updates the field used at next close() — no immediate side effect. */
|
|
3558
3180
|
setReturnFocus(target) {
|
|
3559
3181
|
if (this.destroyed) return;
|
|
3560
3182
|
this.lifecycle.setReturnFocus(target);
|
|
3561
3183
|
}
|
|
3562
|
-
/**
|
|
3563
|
-
* Batch update — preset first, then individual overrides. Applied as a
|
|
3564
|
-
* single DOM-write phase when open so live-controls don't trigger
|
|
3565
|
-
* cascading reflows. Safe to call when closed (defers to next open).
|
|
3566
|
-
*/
|
|
3567
3184
|
setOverlay(opts) {
|
|
3568
3185
|
if (this.destroyed) return;
|
|
3569
3186
|
const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
|
|
3570
3187
|
const merged = preset ? {
|
|
3571
3188
|
backdropOpacity: preset.backdropOpacity,
|
|
3572
|
-
// Preset's `undefined` filter means "no filter" — surface as null
|
|
3573
|
-
// so the merged step clears the field instead of inheriting old.
|
|
3574
3189
|
backdropFilter: preset.backdropFilter === void 0 ? null : preset.backdropFilter,
|
|
3575
3190
|
swipeToClose: preset.swipeToClose,
|
|
3576
3191
|
enterAnimation: preset.enterAnimation,
|
|
@@ -3629,30 +3244,17 @@ var OverlayEngine = class {
|
|
|
3629
3244
|
}
|
|
3630
3245
|
}
|
|
3631
3246
|
}
|
|
3632
|
-
/**
|
|
3633
|
-
* Runtime children injection — replace the panel's contents without
|
|
3634
|
-
* unmounting the engine. `replaceChildren()` is a single atomic DOM op,
|
|
3635
|
-
* so callers can swap content rapidly without intermediate flicker.
|
|
3636
|
-
* Function form is resolved exactly ONCE per call (cheap lazy content).
|
|
3637
|
-
*/
|
|
3638
3247
|
setOverlayChildren(children) {
|
|
3639
3248
|
if (this.destroyed) return;
|
|
3640
3249
|
const node = typeof children === "function" ? children() : children;
|
|
3641
3250
|
this.element.replaceChildren(node);
|
|
3642
3251
|
this.hasInjectedChildren = true;
|
|
3643
3252
|
}
|
|
3644
|
-
/**
|
|
3645
|
-
* Clear injected children. No-op when nothing was injected so consumers
|
|
3646
|
-
* who never used setOverlayChildren can call this defensively without
|
|
3647
|
-
* paying for a DOM mutation.
|
|
3648
|
-
*/
|
|
3649
3253
|
clearOverlayChildren() {
|
|
3650
3254
|
if (this.destroyed || !this.hasInjectedChildren) return;
|
|
3651
3255
|
this.element.replaceChildren();
|
|
3652
3256
|
this.hasInjectedChildren = false;
|
|
3653
3257
|
}
|
|
3654
|
-
// Shared swipe-config normalizer — used by constructor, setSwipeToClose,
|
|
3655
|
-
// and setOverlay so the union/object shape is decoded in exactly one place.
|
|
3656
3258
|
assignSwipeFromConfig(swipe) {
|
|
3657
3259
|
if (typeof swipe === "object" && swipe !== null) {
|
|
3658
3260
|
this.swipeToCloseEnabled = swipe.enabled ?? true;
|
|
@@ -3796,13 +3398,6 @@ var OverlayEngine = class {
|
|
|
3796
3398
|
this.detachOutsidePointer = null;
|
|
3797
3399
|
this.lifecycle.release();
|
|
3798
3400
|
}
|
|
3799
|
-
/**
|
|
3800
|
-
* Light pointer-driven dismiss. Tracks finger along the edge axis, follows
|
|
3801
|
-
* with translate, dismisses if past `swipeThreshold` (fraction of panel
|
|
3802
|
-
* size) or release velocity exceeds `swipeVelocityThreshold` (px/ms).
|
|
3803
|
-
* Pure CSS-transform during drag — no rAF or physics required since the
|
|
3804
|
-
* panel only moves one direction.
|
|
3805
|
-
*/
|
|
3806
3401
|
installSwipeToClose() {
|
|
3807
3402
|
const el = this.element;
|
|
3808
3403
|
const edge = this.edge;
|