@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.
Files changed (50) hide show
  1. package/README.md +6 -0
  2. package/dist/BottomSheetEngine-CIT9Htti.d.cts +219 -0
  3. package/dist/BottomSheetEngine-Df9MwBbm.d.ts +219 -0
  4. package/dist/element.cjs +95 -440
  5. package/dist/element.d.cts +3 -56
  6. package/dist/element.d.ts +3 -56
  7. package/dist/element.iife.global.js +2 -2
  8. package/dist/element.js +95 -440
  9. package/dist/index.cjs +84 -494
  10. package/dist/index.d.cts +3 -84
  11. package/dist/index.d.ts +3 -84
  12. package/dist/index.js +84 -494
  13. package/dist/integrations/formik.d.cts +2 -8
  14. package/dist/integrations/formik.d.ts +2 -8
  15. package/dist/integrations/react-hook-form.d.cts +2 -11
  16. package/dist/integrations/react-hook-form.d.ts +2 -11
  17. package/dist/overlay.cjs +0 -87
  18. package/dist/overlay.d.cts +1 -137
  19. package/dist/overlay.d.ts +1 -137
  20. package/dist/overlay.js +0 -87
  21. package/dist/preact.d.cts +2 -2
  22. package/dist/preact.d.ts +2 -2
  23. package/dist/preact.js +0 -1
  24. package/dist/qwik.cjs +87 -439
  25. package/dist/qwik.d.cts +3 -24
  26. package/dist/qwik.d.ts +3 -24
  27. package/dist/qwik.js +87 -439
  28. package/dist/react.cjs +91 -496
  29. package/dist/react.d.cts +16 -158
  30. package/dist/react.d.ts +16 -158
  31. package/dist/react.js +91 -496
  32. package/dist/solid.cjs +87 -439
  33. package/dist/solid.d.cts +4 -31
  34. package/dist/solid.d.ts +4 -31
  35. package/dist/solid.js +87 -439
  36. package/dist/styles.css +27 -102
  37. package/dist/svelte.cjs +139 -462
  38. package/dist/svelte.d.ts +9 -2
  39. package/dist/svelte.js +139 -462
  40. package/dist/themes/ios.css +0 -3
  41. package/dist/themes/material.css +0 -3
  42. package/dist/themes/vercel.css +0 -3
  43. package/dist/{transform-Brkf-wzo.d.cts → transform-jVfAikE9.d.cts} +0 -1
  44. package/dist/{transform-Brkf-wzo.d.ts → transform-jVfAikE9.d.ts} +0 -1
  45. package/dist/vue.cjs +104 -446
  46. package/dist/vue.d.ts +7 -1
  47. package/dist/vue.js +105 -447
  48. package/package.json +3 -3
  49. package/dist/BottomSheetEngine-CvSVbo6c.d.cts +0 -704
  50. package/dist/BottomSheetEngine-DbO8rW_3.d.ts +0 -704
package/dist/element.cjs CHANGED
@@ -272,11 +272,6 @@ var init_snap_resolver = __esm({
272
272
  this.allowedIds = allowed ?? raw.map((p) => p.id);
273
273
  this.recompute();
274
274
  }
275
- /**
276
- * Re-resolve raw → resolved against the current viewport / fit-measure,
277
- * recompute maxAxisSize, invalidate range cache, notify engine. Called on
278
- * resize, orientationchange, visualViewport changes, and snap-list swaps.
279
- */
280
275
  recompute() {
281
276
  this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
282
277
  this.maxAxisSize = this.resolved.reduce(
@@ -286,15 +281,10 @@ var init_snap_resolver = __esm({
286
281
  this.rangeCache = null;
287
282
  this.onMaxAxisSizeChange?.(this.maxAxisSize);
288
283
  }
289
- /** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
290
284
  setRaw(raw) {
291
285
  this.raw = raw;
292
286
  this.recompute();
293
287
  }
294
- /**
295
- * Replace the allow-list. Range cache depends on `(resolvedSnaps ×
296
- * allowedIds)` so flipping ids alone invalidates without a full recompute.
297
- */
298
288
  setAllowedIds(ids) {
299
289
  this.allowedIds = ids;
300
290
  this.rangeCache = null;
@@ -302,11 +292,6 @@ var init_snap_resolver = __esm({
302
292
  findById(id) {
303
293
  return findById(id, this.resolved);
304
294
  }
305
- /**
306
- * Pure drag-settle target resolution. Engine passes the gesture-specific
307
- * fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
308
- * `size`/`activeId` since those are still engine-owned.
309
- */
310
295
  findDragSettleTarget(args) {
311
296
  return findDragSettleTarget({
312
297
  ...args,
@@ -315,18 +300,9 @@ var init_snap_resolver = __esm({
315
300
  maxAxisSize: this.maxAxisSize
316
301
  });
317
302
  }
318
- /**
319
- * Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
320
- * applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
321
- * to one recompute per geometry/allow-list change.
322
- */
323
303
  getAllowedRange() {
324
304
  return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
325
305
  }
326
- /**
327
- * Read-only view of resolved snaps. Returned as `readonly` to discourage
328
- * accidental mutation by feature install sites that close over this list.
329
- */
330
306
  getResolvedSnaps() {
331
307
  return this.resolved;
332
308
  }
@@ -336,18 +312,6 @@ var init_snap_resolver = __esm({
336
312
  getMaxAxisSize() {
337
313
  return this.maxAxisSize;
338
314
  }
339
- /**
340
- * External clamp override. The resize-observer feature compares
341
- * `getMaxAxisSize()` against the live viewport and shrinks if the largest
342
- * resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
343
- * window resize). The next `recompute()` overwrites this clamp from the
344
- * resolved-snap reduction.
345
- *
346
- * Does NOT invalidate the range cache — viewport clamping doesn't change
347
- * `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
348
- * so the resolver owns the layout-axis style write end-to-end (symmetric
349
- * with `recompute()` — feature call sites no longer duplicate the write).
350
- */
351
315
  setMaxAxisSize(n) {
352
316
  this.maxAxisSize = n;
353
317
  this.onMaxAxisSizeChange?.(n);
@@ -771,36 +735,17 @@ var init_opacity_dedup = __esm({
771
735
  constructor() {
772
736
  __publicField(this, "last", -1);
773
737
  }
774
- /**
775
- * Returns `true` (and updates the cached value) if `next` differs from the
776
- * cached `last` by more than `epsilon`. Returns `false` (and leaves the
777
- * cache untouched) when the delta is within epsilon — caller skips the
778
- * CSSOM write.
779
- */
780
738
  shouldWrite(next, epsilon) {
781
739
  if (Math.abs(next - this.last) <= epsilon) return false;
782
740
  this.last = next;
783
741
  return true;
784
742
  }
785
- /**
786
- * Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
787
- * non-negative `next` exceeds the epsilon comparison. Called from setters
788
- * that mutate state under the cache (range/mode/enabled flips that change
789
- * the progress→opacity mapping but don't move `progress` itself).
790
- */
791
743
  invalidate() {
792
744
  this.last = -1;
793
745
  }
794
- /**
795
- * Record `value` as the last-written without performing a write. Used
796
- * when a setter writes a literal value through a different path (e.g.
797
- * `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
798
- * `shouldWrite` calls to short-circuit against that known cached state.
799
- */
800
746
  setLastWritten(value) {
801
747
  this.last = value;
802
748
  }
803
- /** Read-only accessor for diagnostic / dedup-aware peek. */
804
749
  get value() {
805
750
  return this.last;
806
751
  }
@@ -808,45 +753,74 @@ var init_opacity_dedup = __esm({
808
753
  }
809
754
  });
810
755
 
756
+ // src/core/primitives/devWarn.ts
757
+ function devWarn(...args) {
758
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
759
+ console.warn(...args);
760
+ }
761
+ }
762
+ var init_devWarn = __esm({
763
+ "src/core/primitives/devWarn.ts"() {
764
+ }
765
+ });
766
+
811
767
  // src/core/controllers/scrim-controller.ts
812
- var ScrimController;
768
+ function resolveSheetAnchoredStyle(mode, position, inset) {
769
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
770
+ switch (mode) {
771
+ case "bottom":
772
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
773
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
774
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
775
+ case "top":
776
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
777
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
778
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
779
+ case "left":
780
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
781
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
782
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
783
+ case "right":
784
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
785
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
786
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
787
+ }
788
+ }
789
+ function applySheetAnchoredPosition(ws, mode, position, inset) {
790
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
791
+ if (style.top !== void 0) ws.top = style.top;
792
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
793
+ if (style.left !== void 0) ws.left = style.left;
794
+ if (style.right !== void 0) ws.right = style.right;
795
+ if (style.transform !== void 0) ws.transform = style.transform;
796
+ }
797
+ var VALID_CSS_LENGTH, ScrimController;
813
798
  var init_scrim_controller = __esm({
814
799
  "src/core/controllers/scrim-controller.ts"() {
815
800
  init_types();
816
801
  init_snap_points();
817
802
  init_hot_path_thresholds();
818
803
  init_opacity_dedup();
804
+ init_devWarn();
805
+ VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
819
806
  ScrimController = class {
820
807
  constructor(deps, opts) {
821
- // ── DOM refs (mirror engine's; cached for hot path) ─────────────────
822
808
  __publicField(this, "screenComponent");
823
809
  __publicField(this, "backdrop");
824
810
  __publicField(this, "mode");
825
- // ── Engine callbacks ────────────────────────────────────────────────
826
811
  __publicField(this, "isDestroyed");
827
812
  __publicField(this, "isTopSheet");
828
813
  __publicField(this, "getAllowedIds");
829
814
  __publicField(this, "getResolvedSnaps");
830
815
  __publicField(this, "snapToFn");
831
- // ── Mode + state ────────────────────────────────────────────────────
832
816
  __publicField(this, "scrimMode");
833
817
  __publicField(this, "backdropRange");
834
818
  __publicField(this, "screenRange");
835
- // setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
836
- // ranges so re-enable can restore prior values without a remount. Public
837
- // so tests / introspection can read it without an `as any` cast.
838
819
  __publicField(this, "scrimEnabled", true);
839
820
  __publicField(this, "savedScrimRanges", null);
840
- // ── Tap-to-close ────────────────────────────────────────────────────
841
821
  __publicField(this, "scrimTapToCloseEnabled", false);
842
822
  __publicField(this, "detachScrimTap", null);
843
- // ── Scrim overlay slot (positioned floating element) ────────────────
844
823
  __publicField(this, "scrimOverlayTeardown", null);
845
- // ── Hot-path opacity dedup caches (per-frame writes) ────────────────
846
- // Numeric sentinels migrated to WriteSentinel primitive — single
847
- // invalidation surface, shared with the engine's size/progress sentinels.
848
- // The two string-typed sentinels (`Pointer`, `Display`) keep their inline
849
- // pattern: equality on string union, no epsilon, different reset semantics.
850
824
  __publicField(this, "backdropOpacitySentinel", new WriteSentinel());
851
825
  __publicField(this, "screenOpacitySentinel", new WriteSentinel());
852
826
  __publicField(this, "lastBackdropPointer", "");
@@ -881,41 +855,15 @@ var init_scrim_controller = __esm({
881
855
  }
882
856
  }
883
857
  }
884
- /**
885
- * Invalidate the opacity dedup caches. Called from any setter that mutates
886
- * range/mode/enabled — opacity is a pure function of (progress, range,
887
- * enabled), so a config change makes the cached values stale even if
888
- * `progress` itself didn't move. Next `applyOpacity(progress, false)` call
889
- * sees the -1 sentinel and re-runs the math instead of short-circuiting.
890
- *
891
- * Public for the engine: `setSnapPoints` mutates resolved geometry which
892
- * shifts `progress→opacity` mapping under the cache; engine calls this to
893
- * keep the next render frame correct on a static sheet.
894
- * @internal
895
- */
896
858
  invalidateOpacityCache() {
897
859
  this.backdropOpacitySentinel.invalidate();
898
860
  this.screenOpacitySentinel.invalidate();
899
861
  }
900
- /** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
901
862
  attach() {
902
863
  if (this.scrimTapToCloseEnabled) {
903
864
  this.setScrimTapToClose(true);
904
865
  }
905
866
  }
906
- // ── Hot-path: opacity computation called per-frame from engine.applySize ──
907
- /**
908
- * Per-frame opacity writer. Computes backdrop + screen opacity from the
909
- * sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
910
- * caches so steady-state frames don't trigger style recalc.
911
- *
912
- * `progressChanged` is the engine's already-computed dedup gate — when
913
- * `false`, opacity is by construction a pure function of `progress` so
914
- * cached values are still valid; we short-circuit the Math.min/max +
915
- * division work entirely. Setters that mutate ranges/mode/enabled
916
- * invalidate the cache by writing -1 sentinels so the next call re-runs
917
- * the math.
918
- */
919
867
  applyOpacity(progress, progressChanged) {
920
868
  if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
921
869
  return;
@@ -961,80 +909,30 @@ var init_scrim_controller = __esm({
961
909
  }
962
910
  }
963
911
  }
964
- // ── Public setters (engine forwards these 1:1) ─────────────────────
965
- /**
966
- * Replace the [start, end] progress window in which the backdrop fades 0→1.
967
- * @param range - Tuple of two progress values in 0..1 (start <= end).
968
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
969
- * @example
970
- * // Backdrop only starts darkening after the sheet crosses 30% open.
971
- * engine.setBackdropRange([0.3, 1]);
972
- */
973
912
  setBackdropRange(range, applySize) {
974
913
  if (this.isDestroyed()) return;
975
914
  this.backdropRange = range;
976
915
  this.invalidateOpacityCache();
977
916
  applySize();
978
917
  }
979
- /**
980
- * Replace the [start, end] progress window in which the screen scrim fades 0→1.
981
- * @param range - Tuple of two progress values in 0..1 (start <= end).
982
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
983
- * @example
984
- * // Scrim is fully opaque only when the sheet is past 50% — softer feel.
985
- * engine.setScreenRange([0, 0.5]);
986
- */
987
918
  setScreenRange(range, applySize) {
988
919
  if (this.isDestroyed()) return;
989
920
  this.screenRange = range;
990
921
  this.invalidateOpacityCache();
991
922
  applySize();
992
923
  }
993
- /**
994
- * Set the scrim background color. Pass `null`/`undefined` to clear the
995
- * inline write and let consumer CSS take over.
996
- * @param color - Any valid CSS color string, or `null`/`undefined` to clear.
997
- * @example
998
- * engine.setScrimColor("rgba(0, 0, 0, 0.6)");
999
- * engine.setScrimColor(null); // hand back to consumer CSS
1000
- */
1001
924
  setScrimColor(color) {
1002
925
  if (this.isDestroyed()) return;
1003
926
  this.applyScrimStyles({ color: color === void 0 ? null : color });
1004
927
  }
1005
- /**
1006
- * Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
1007
- * @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
1008
- * `null`/`undefined` to remove the filter.
1009
- * @example
1010
- * engine.setScrimBlur("12px"); // frosted-glass effect
1011
- * engine.setScrimBlur(null); // disable blur
1012
- */
1013
928
  setScrimBlur(blur) {
1014
929
  if (this.isDestroyed()) return;
1015
930
  this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
1016
931
  }
1017
- /**
1018
- * Toggle whether the scrim layer captures pointer events.
1019
- * @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
1020
- * @example
1021
- * engine.setScrimInteractive(true); // scrim swallows clicks
1022
- * engine.setScrimInteractive(false); // clicks pass through to content below
1023
- */
1024
932
  setScrimInteractive(interactive) {
1025
933
  if (this.isDestroyed()) return;
1026
934
  this.applyScrimStyles({ interactive });
1027
935
  }
1028
- /**
1029
- * Switch scrim positioning at runtime.
1030
- * @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
1031
- * so only the area NOT covered by the sheet is dimmed), or
1032
- * `"off"` (display:none + opacity:0 — no remount needed).
1033
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
1034
- * @example
1035
- * engine.setScrimMode("above-sheet"); // dim only the area above the sheet
1036
- * engine.setScrimMode("off"); // disable scrim layer
1037
- */
1038
936
  setScrimMode(mode, applySize) {
1039
937
  if (this.isDestroyed()) return;
1040
938
  if (mode === this.scrimMode) return;
@@ -1064,15 +962,6 @@ var init_scrim_controller = __esm({
1064
962
  }
1065
963
  applySize();
1066
964
  }
1067
- /**
1068
- * Enable or disable click-on-scrim → snap to first non-zero allowed snap.
1069
- * Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
1070
- * click listener actually fires.
1071
- * @param enabled - `true` installs the click listener; `false` removes it.
1072
- * @example
1073
- * engine.setScrimTapToClose(true); // tap outside sheet collapses it
1074
- * engine.setScrimTapToClose(false); // require explicit close button
1075
- */
1076
965
  setScrimTapToClose(enabled) {
1077
966
  if (this.isDestroyed()) return;
1078
967
  if (!this.screenComponent) return;
@@ -1102,16 +991,6 @@ var init_scrim_controller = __esm({
1102
991
  this.scrimTapToCloseEnabled = false;
1103
992
  }
1104
993
  }
1105
- /**
1106
- * Toggle the scrim layer without remounting. Disabling stashes current
1107
- * `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
1108
- * formula → 0 across the whole range); re-enabling restores the saved values.
1109
- * @param enabled - `false` hides scrim, `true` restores prior ranges.
1110
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
1111
- * @example
1112
- * engine.setScrimEnabled(false); // hide scrim, ranges remembered
1113
- * engine.setScrimEnabled(true); // restore prior ranges (no remount)
1114
- */
1115
994
  setScrimEnabled(enabled, applySize) {
1116
995
  if (this.isDestroyed()) return;
1117
996
  if (enabled === this.scrimEnabled) return;
@@ -1130,20 +1009,6 @@ var init_scrim_controller = __esm({
1130
1009
  }
1131
1010
  applySize();
1132
1011
  }
1133
- /**
1134
- * Batch-update multiple scrim fields with a single applySize() at the end.
1135
- * If `preset` is set, it seeds color/blur/interactive/range first; explicit
1136
- * fields override.
1137
- * @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
1138
- * color, blur, interactive, range.
1139
- * @param applySize - Engine-supplied recompute hook; runs ONCE per call.
1140
- * @example
1141
- * engine.setScrim({
1142
- * preset: "frosted",
1143
- * mode: "above-sheet",
1144
- * tapToClose: true,
1145
- * });
1146
- */
1147
1012
  setScrim(opts, applySize) {
1148
1013
  if (this.isDestroyed()) return;
1149
1014
  if (opts.preset) {
@@ -1166,27 +1031,18 @@ var init_scrim_controller = __esm({
1166
1031
  interactive: opts.interactive
1167
1032
  });
1168
1033
  if (opts.range !== void 0) {
1169
- this.screenRange = opts.range;
1034
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1035
+ this.savedScrimRanges = {
1036
+ screen: opts.range,
1037
+ backdrop: this.savedScrimRanges.backdrop
1038
+ };
1039
+ } else {
1040
+ this.screenRange = opts.range;
1041
+ }
1170
1042
  }
1171
1043
  this.invalidateOpacityCache();
1172
1044
  applySize();
1173
1045
  }
1174
- /**
1175
- * Inject a positioned floating element into the scrim layer (e.g. a close
1176
- * button anchored top-right). Replaces any prior overlay so repeated calls
1177
- * (reactive effects re-running) don't accumulate wrappers.
1178
- * @param opts - Children element, optional `inset` (default `"16px"`),
1179
- * `position` (9-way grid, default `"top-right"`), and
1180
- * `interactive` flag (default `true`).
1181
- * @returns Teardown function that removes the wrapper from the DOM.
1182
- * @example
1183
- * const teardown = engine.setScrimOverlay({
1184
- * children: closeBtn,
1185
- * position: "top-right",
1186
- * inset: "20px",
1187
- * });
1188
- * // later: teardown(); // remove the floating button
1189
- */
1190
1046
  setScrimOverlay(opts) {
1191
1047
  if (this.isDestroyed()) return () => {
1192
1048
  };
@@ -1194,7 +1050,18 @@ var init_scrim_controller = __esm({
1194
1050
  };
1195
1051
  this.scrimOverlayTeardown?.();
1196
1052
  const screen = this.screenComponent;
1197
- const inset = opts.inset ?? "16px";
1053
+ const insetRaw = opts.inset ?? "16px";
1054
+ const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
1055
+ if (inset !== insetRaw) {
1056
+ devWarn(
1057
+ `[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
1058
+ );
1059
+ }
1060
+ if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
1061
+ devWarn(
1062
+ "[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."
1063
+ );
1064
+ }
1198
1065
  const position = opts.position ?? "top-right";
1199
1066
  const interactive = opts.interactive ?? true;
1200
1067
  const wrapper = screen.ownerDocument.createElement("div");
@@ -1243,12 +1110,30 @@ var init_scrim_controller = __esm({
1243
1110
  ws.bottom = inset;
1244
1111
  ws.right = inset;
1245
1112
  break;
1113
+ case "sheet-top-left":
1114
+ case "sheet-top-center":
1115
+ case "sheet-top-right":
1116
+ applySheetAnchoredPosition(
1117
+ ws,
1118
+ this.mode,
1119
+ position,
1120
+ inset
1121
+ );
1122
+ break;
1246
1123
  }
1247
1124
  if (interactive) {
1248
1125
  ws.pointerEvents = "auto";
1249
1126
  }
1250
1127
  wrapper.appendChild(opts.children);
1251
- screen.appendChild(wrapper);
1128
+ const host = screen.parentElement;
1129
+ if (!host) {
1130
+ devWarn(
1131
+ "[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
1132
+ );
1133
+ return () => {
1134
+ };
1135
+ }
1136
+ host.appendChild(wrapper);
1252
1137
  const teardown = () => {
1253
1138
  if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
1254
1139
  if (this.scrimOverlayTeardown === teardown) {
@@ -1258,7 +1143,6 @@ var init_scrim_controller = __esm({
1258
1143
  this.scrimOverlayTeardown = teardown;
1259
1144
  return teardown;
1260
1145
  }
1261
- /** Tear down toggle-state listeners + remove any injected overlay wrapper. */
1262
1146
  destroy() {
1263
1147
  this.detachScrimTap?.();
1264
1148
  this.detachScrimTap = null;
@@ -1277,8 +1161,6 @@ var init_scrim_controller = __esm({
1277
1161
  this.lastBackdropPointer = "";
1278
1162
  this.lastScreenDisplay = "";
1279
1163
  }
1280
- // ── Private helpers ────────────────────────────────────────────────
1281
- /** Centralized scrim style writer — atomic 3-property write across all setters. */
1282
1164
  applyScrimStyles(opts) {
1283
1165
  if (!this.screenComponent) return;
1284
1166
  const s = this.screenComponent.style;
@@ -1300,7 +1182,6 @@ var init_scrim_controller = __esm({
1300
1182
  s.pointerEvents = opts.interactive ? "auto" : "none";
1301
1183
  }
1302
1184
  }
1303
- /** Pin the screen overlay to the area NOT occupied by the sheet. */
1304
1185
  applyAboveSheetInset() {
1305
1186
  if (!this.screenComponent) return;
1306
1187
  const insetByMode = {
@@ -1491,13 +1372,9 @@ var init_animation_runner = __esm({
1491
1372
  __publicField(this, "duration");
1492
1373
  __publicField(this, "easing");
1493
1374
  __publicField(this, "respectReducedMotion");
1494
- /** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
1495
1375
  __publicField(this, "reducedMotion", false);
1496
- /** Disposes the matchMedia change listener — drained from destroy(). */
1497
1376
  __publicField(this, "detachReducedMotion", null);
1498
- /** Computed once — both inputs (option + API presence) stable for life. */
1499
1377
  __publicField(this, "viewTransitionsAvailable");
1500
- /** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
1501
1378
  __publicField(this, "currentTween", null);
1502
1379
  __publicField(this, "currentSpring", null);
1503
1380
  this.element = deps.element;
@@ -1528,26 +1405,13 @@ var init_animation_runner = __esm({
1528
1405
  }
1529
1406
  }
1530
1407
  }
1531
- /**
1532
- * Live derived flag — `true` whenever a spring/tween handle is in flight.
1533
- * Reading the canonical handles (instead of mirroring into a separate
1534
- * boolean) eliminates the reduced-motion early-return latent bug where
1535
- * the flag stayed true forever, and removes the cross-controller
1536
- * setIsAnimating callback.
1537
- */
1538
1408
  get isAnimating() {
1539
1409
  return this.currentTween !== null || this.currentSpring !== null;
1540
1410
  }
1541
- /** Cancel any in-flight tween/spring without touching engine state. */
1542
1411
  cancel() {
1543
1412
  this.currentTween?.cancel();
1544
1413
  this.currentSpring?.cancel();
1545
1414
  }
1546
- /**
1547
- * Animate from current size to `target`. Resolves when the animation
1548
- * completes; resolves early if cancelled mid-flight (caller checks an
1549
- * abort signal post-await for cycle-invariant correctness).
1550
- */
1551
1415
  async animateTo(target, velocityPxPerMs) {
1552
1416
  if (target === this.getSize()) return;
1553
1417
  this.currentTween?.cancel();
@@ -1588,7 +1452,6 @@ var init_animation_runner = __esm({
1588
1452
  if (!this.isDragging()) this.element.style.willChange = "auto";
1589
1453
  }
1590
1454
  }
1591
- /** Tear down the matchMedia listener. Engine calls from destroy(). */
1592
1455
  destroy() {
1593
1456
  this.detachReducedMotion?.();
1594
1457
  this.detachReducedMotion = null;
@@ -1770,12 +1633,8 @@ var init_lifecycle_controller = __esm({
1770
1633
  __publicField(this, "inertSiblings");
1771
1634
  __publicField(this, "shouldApplyInertSiblings");
1772
1635
  __publicField(this, "returnFocus");
1773
- /** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
1774
1636
  __publicField(this, "releaseFocusTrap", null);
1775
1637
  __publicField(this, "releaseScrollLock", null);
1776
- /** True between install() and release(). Guards release()/destroy() from
1777
- * re-running side effects (notably `returnFocus.focus()`) on already-
1778
- * released or never-installed instances. */
1779
1638
  __publicField(this, "installed", false);
1780
1639
  __publicField(this, "destroyed", false);
1781
1640
  this.element = deps.element;
@@ -1789,14 +1648,6 @@ var init_lifecycle_controller = __esm({
1789
1648
  this.returnFocus = opts.returnFocus;
1790
1649
  this.inertSiblings = createInertSiblings(() => this.element);
1791
1650
  }
1792
- /**
1793
- * Run the open-time install steps. Idempotent: each step checks its
1794
- * existing teardown ref so a re-open during a half-cancelled close
1795
- * doesn't double-install. Sets `installed=true` BEFORE the first side
1796
- * effect so a partial throw (detached / shadow-DOM target) leaves the
1797
- * caller's `release()` rollback path armed to undo whatever did succeed.
1798
- * No-op when destroyed.
1799
- */
1800
1651
  install() {
1801
1652
  if (this.destroyed) return;
1802
1653
  this.installed = true;
@@ -1813,22 +1664,10 @@ var init_lifecycle_controller = __esm({
1813
1664
  this.inertSiblings.apply();
1814
1665
  }
1815
1666
  }
1816
- /**
1817
- * Update the post-release focus target. Overlay re-binds this when the
1818
- * consumer changes `returnFocus` after construction; the new value applies
1819
- * on the next `release()`. No-op when destroyed.
1820
- */
1821
1667
  setReturnFocus(target) {
1822
1668
  if (this.destroyed) return;
1823
1669
  this.returnFocus = target;
1824
1670
  }
1825
- /**
1826
- * Release all install-time teardowns. Short-circuits when not installed,
1827
- * so `close() → destroy()` (each routing through here) only restores
1828
- * focus once. The `installed` guard lets `inertSiblings.remove()` and
1829
- * the focus-trap/scroll-lock teardown nullings stay simple — they were
1830
- * already idempotent, but `returnFocus.focus()` was firing on every call.
1831
- */
1832
1671
  release() {
1833
1672
  if (!this.installed) return;
1834
1673
  this.installed = false;
@@ -1842,12 +1681,6 @@ var init_lifecycle_controller = __esm({
1842
1681
  target?.focus?.();
1843
1682
  }
1844
1683
  }
1845
- /**
1846
- * Final teardown — releases any still-installed listeners and seals the
1847
- * controller. After destroy(), install() is a no-op and setReturnFocus()
1848
- * is rejected, so post-destroy mutations can't queue up a stale focus
1849
- * restore.
1850
- */
1851
1684
  destroy() {
1852
1685
  this.release();
1853
1686
  this.destroyed = true;
@@ -1862,12 +1695,10 @@ var init_gestures = __esm({
1862
1695
  "src/core/gestures.ts"() {
1863
1696
  isAxisVertical = (mode) => mode === "bottom" || mode === "top";
1864
1697
  SampleRing = class {
1865
- // current count, ≤ capacity
1866
1698
  constructor(capacity) {
1867
1699
  __publicField(this, "slots");
1868
1700
  __publicField(this, "capacity");
1869
1701
  __publicField(this, "head", 0);
1870
- // next write index
1871
1702
  __publicField(this, "size", 0);
1872
1703
  this.capacity = capacity;
1873
1704
  this.slots = new Array(capacity);
@@ -1880,12 +1711,6 @@ var init_gestures = __esm({
1880
1711
  this.head = (this.head + 1) % this.capacity;
1881
1712
  if (this.size < this.capacity) this.size++;
1882
1713
  }
1883
- /**
1884
- * Drop oldest entries whose timestamp is below `cutoff`, but never shrink
1885
- * below `minKeep`. Mirrors the original
1886
- * `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
1887
- * but without copying — just advances the logical tail.
1888
- */
1889
1714
  pruneBefore(cutoff, minKeep) {
1890
1715
  while (this.size > minKeep) {
1891
1716
  const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
@@ -2038,28 +1863,14 @@ var init_gesture_controller = __esm({
2038
1863
  __publicField(this, "detach", null);
2039
1864
  __publicField(this, "isDragging_", false);
2040
1865
  __publicField(this, "dragStartSize", 0);
2041
- // Only "touch" gestures are eligible for soft-keyboard dismissal —
2042
- // mouse/trackpad users don't have a keyboard to dismiss.
2043
1866
  __publicField(this, "currentPointerKind", "touch");
2044
- // One blur per drag — flipping focus every move event races the keyboard's
2045
- // own animations on iOS and makes the page jitter.
2046
1867
  __publicField(this, "keyboardDismissed", false);
2047
- // Reused payload object for the "drag" event — mutated each onMove instead
2048
- // of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
2049
- // would otherwise churn tens of KB through the GC. Mirrors browser
2050
- // PointerEvent semantics: consumers must NOT retain the reference across
2051
- // frames (clone if needed). See SheetEventMap.drag JSDoc.
2052
1868
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
2053
1869
  this.deps = deps;
2054
1870
  }
2055
- /** True between `dragstart` and `dragend`/`onCancel`. */
2056
1871
  get isDragging() {
2057
1872
  return this.isDragging_;
2058
1873
  }
2059
- /**
2060
- * Bind pointer listeners on the handle and return a teardown. Engine pushes
2061
- * the teardown into its TeardownStack so destroy() drains it once.
2062
- */
2063
1874
  install() {
2064
1875
  if (this.detach) return this.detach;
2065
1876
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
@@ -2116,11 +1927,6 @@ var init_gesture_controller = __esm({
2116
1927
  });
2117
1928
  return this.detach;
2118
1929
  }
2119
- /**
2120
- * Force-clear drag visual state. Engine calls from destroy() when destroyed
2121
- * mid-drag — gesture detach won't synthesize an onCancel, so we have to
2122
- * roll back the data-attribute + will-change ourselves.
2123
- */
2124
1930
  forceClearDragState() {
2125
1931
  if (!this.isDragging_) return;
2126
1932
  this.isDragging_ = false;
@@ -2144,11 +1950,6 @@ var init_aria_slider_writer = __esm({
2144
1950
  mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
2145
1951
  );
2146
1952
  }
2147
- /**
2148
- * Update slider position. `allowedIds` length determines `aria-valuemax`;
2149
- * `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
2150
- * Empty list → strip every dynamic attr so AT skips the handle entirely.
2151
- */
2152
1953
  setValue(allowedIds, activeId) {
2153
1954
  if (allowedIds.length === 0) {
2154
1955
  this.handle.removeAttribute("aria-valuemin");
@@ -2226,9 +2027,6 @@ function resolveEngineOptions(opts, extras) {
2226
2027
  scrimMode: opts.scrimMode,
2227
2028
  scrimColor: opts.scrimColor,
2228
2029
  scrimBlur: opts.scrimBlur,
2229
- // Auto-promote pointer-events when tap-to-close is on so the click
2230
- // listener actually receives events. Caller can still force `false`
2231
- // explicitly to opt out — `??` only kicks in for `undefined`.
2232
2030
  scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
2233
2031
  scrimTapToClose: opts.scrimTapToClose,
2234
2032
  scrimPreset: opts.scrimPreset,
@@ -2249,10 +2047,6 @@ function resolveEngineOptions(opts, extras) {
2249
2047
  closeOnEscape: opts.closeOnEscape,
2250
2048
  lockBodyScroll: opts.lockBodyScroll,
2251
2049
  inertSiblings: opts.inertSiblings,
2252
- // Folded in from `extras` so the lifecycle bag is fully assembled at
2253
- // the call site — engine no longer needs a spread+override at
2254
- // controller construction. When `extras` is omitted, the field stays
2255
- // undefined and the controller uses its own default predicate.
2256
2050
  shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2257
2051
  }
2258
2052
  };
@@ -2321,6 +2115,7 @@ var init_BottomSheetEngine = __esm({
2321
2115
  __publicField(this, "scrollContainer");
2322
2116
  __publicField(this, "backdrop");
2323
2117
  __publicField(this, "screenComponent");
2118
+ __publicField(this, "scrimParent", null);
2324
2119
  __publicField(this, "mode");
2325
2120
  __publicField(this, "flickVelocity");
2326
2121
  __publicField(this, "dragThreshold");
@@ -2331,40 +2126,22 @@ var init_BottomSheetEngine = __esm({
2331
2126
  __publicField(this, "lifecycle");
2332
2127
  __publicField(this, "closeOnBack");
2333
2128
  __publicField(this, "routedTo");
2334
- // Engine retains `snapPointsRaw` for the "find a closed-id" query in
2335
- // close() (raw input, not resolved geometry). Resolver owns geometry.
2336
2129
  __publicField(this, "snapPointsRaw");
2337
2130
  __publicField(this, "snaps");
2338
2131
  __publicField(this, "activeId");
2339
2132
  __publicField(this, "size", 0);
2340
- // Constructed in attach(); a plugin's install() could read `state` before
2341
- // attach completes if the constructor throws partway through. State getter
2342
- // null-guards via `this.gesture?.isDragging ?? false`.
2343
2133
  __publicField(this, "gesture");
2344
2134
  __publicField(this, "rootEl", null);
2345
2135
  __publicField(this, "destroyed", false);
2346
- // In-flight ViewTransition handle. A second snapTo while a VT is in flight
2347
- // would otherwise let Chromium reject the second's promise and flicker —
2348
- // newCycle() calls skipTransition() to abort cleanly.
2349
2136
  __publicField(this, "currentViewTransition", null);
2350
- // Each cycle captures the signal at entry; when a newer cycle aborts the
2351
- // controller, the captured signal flips to `aborted=true` and post-await
2352
- // guards short-circuit (prevents stale snap/open/close emits).
2353
2137
  __publicField(this, "currentAbort", new AbortController());
2354
- // Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
2355
- // visually distinguishable; gating CSSOM writes drops 30-50% of writes
2356
- // during the spring's settle tail.
2357
2138
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
2358
2139
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2359
2140
  __publicField(this, "isTopSheet", true);
2360
- // Pooled payload — identity documented in SheetEventMap.progress JSDoc;
2361
- // consumers must not retain across frames.
2362
2141
  __publicField(this, "progressPayload", {
2363
2142
  value: 0,
2364
2143
  size: 0
2365
2144
  });
2366
- // Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
2367
- // Inner `range` object identity is also stable (mutated in place).
2368
2145
  __publicField(this, "dragContextBuf", {
2369
2146
  size: 0,
2370
2147
  maxAxisSize: 0,
@@ -2375,13 +2152,7 @@ var init_BottomSheetEngine = __esm({
2375
2152
  __publicField(this, "persistKey");
2376
2153
  __publicField(this, "linkedSheets", []);
2377
2154
  __publicField(this, "scrollCache");
2378
- // Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
2379
- // tear down first before the infrastructure they depend on. Toggle-semantic
2380
- // listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
2381
- // fields on their controllers.
2382
2155
  __publicField(this, "teardowns", new TeardownStack());
2383
- // Pre-computed transform template — built once per engine since `mode` is
2384
- // fixed for its lifetime. Avoids the 4-arm switch at 60-120 Hz.
2385
2156
  __publicField(this, "transformTemplate");
2386
2157
  const resolved = resolveEngineOptions(opts, {
2387
2158
  shouldApplyInertSiblings: () => {
@@ -2396,7 +2167,8 @@ var init_BottomSheetEngine = __esm({
2396
2167
  this.handle = opts.handle ?? opts.element;
2397
2168
  this.scrollContainer = opts.scrollContainer;
2398
2169
  this.backdrop = opts.backdrop;
2399
- this.screenComponent = opts.screenComponent;
2170
+ this.screenComponent = opts.scrim ?? opts.screenComponent;
2171
+ this.scrimParent = this.screenComponent?.parentElement ?? null;
2400
2172
  this.mode = resolved.mode;
2401
2173
  this.transformTemplate = buildTransformTemplate(this.mode);
2402
2174
  this.aria = new AriaSliderWriter(this.handle, this.mode);
@@ -2424,8 +2196,6 @@ var init_BottomSheetEngine = __esm({
2424
2196
  getRootEl: () => this.rootEl,
2425
2197
  applySize: (size) => this.applySize(size),
2426
2198
  getSize: () => this.size,
2427
- // Optional chain protects synthetic test paths that drive animateTo
2428
- // before attach() (gesture is constructed there).
2429
2199
  isDragging: () => this.gesture?.isDragging ?? false
2430
2200
  },
2431
2201
  resolved.animation
@@ -2530,21 +2300,6 @@ var init_BottomSheetEngine = __esm({
2530
2300
  on(event, fn) {
2531
2301
  return this.bus.on(event, fn);
2532
2302
  }
2533
- /**
2534
- * Register a plugin. `install` runs synchronously; if it returns a function,
2535
- * it's captured for teardown during `destroy()`. No-op if already destroyed
2536
- * — installing into a torn-down engine would leak listeners.
2537
- *
2538
- * Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
2539
- * so a buggy plugin can't take the engine down with it: the engine stays
2540
- * usable, sibling plugins still install, and the error surfaces in dev tools
2541
- * on the next tick instead of being swallowed. Mirrors the per-fn isolation
2542
- * model used for teardowns.
2543
- *
2544
- * Plugins that accept the second `scope` argument get transactional cleanup:
2545
- * push partial-install cleanups via `scope.add(fn)` and the engine drains
2546
- * them on throw, OR merges them into destroy-time teardowns on success.
2547
- */
2548
2303
  use(plugin) {
2549
2304
  if (this.destroyed) {
2550
2305
  console.warn(
@@ -2554,9 +2309,6 @@ var init_BottomSheetEngine = __esm({
2554
2309
  }
2555
2310
  const scoped = [];
2556
2311
  const scope = {
2557
- // typeof check — not just truthiness — so a JS-only plugin pushing a
2558
- // bogus value is silently rejected instead of queueing a confusing
2559
- // TypeError on destroy() drain.
2560
2312
  add: (fn) => {
2561
2313
  if (typeof fn === "function") scoped.push(fn);
2562
2314
  }
@@ -2585,36 +2337,13 @@ var init_BottomSheetEngine = __esm({
2585
2337
  }
2586
2338
  return this;
2587
2339
  }
2588
- /**
2589
- * Replace the linked-sheets list at runtime. Use when peer engines aren't
2590
- * available at construction time (mounted later in the tree). The wiring
2591
- * to the `open` event is independent of this list — it reads `linkedSheets`
2592
- * lazily on emit, so this setter is a simple field swap.
2593
- */
2594
2340
  setLinkedSheets(sheets) {
2595
2341
  if (this.destroyed) return;
2596
2342
  this.linkedSheets = sheets;
2597
2343
  }
2598
- /** Read-only view of the current allow-list, for cross-sheet target negotiation. */
2599
2344
  getAllowedIds() {
2600
2345
  return this.snaps.getAllowedIds().slice();
2601
2346
  }
2602
- /**
2603
- * Animate to a snap point by id. Resolves when the animation completes.
2604
- *
2605
- * Two call shapes:
2606
- * ```ts
2607
- * engine.snapTo("full"); // default velocity
2608
- * engine.snapTo("full", 0.5); // px/ms initial velocity
2609
- * engine.snapTo("full", { velocity: 0.5, signal }); // abortable
2610
- * ```
2611
- *
2612
- * When `opts.signal` is provided, calling `controller.abort()` cancels the
2613
- * in-flight tween/spring and the returned promise resolves without firing
2614
- * `snap`. The captured `AbortSignal` is checked after each await so external
2615
- * aborts ride the same cycle invariant as internal ones (resize, destroy,
2616
- * concurrent snapTo).
2617
- */
2618
2347
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2619
2348
  if (this.destroyed) return;
2620
2349
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2673,16 +2402,6 @@ var init_BottomSheetEngine = __esm({
2673
2402
  this.handleClose();
2674
2403
  }
2675
2404
  }
2676
- /**
2677
- * Drive the sheet to an arbitrary px size, bypassing snap matching.
2678
- * Useful for preview scrubs, animation chaining, or programmatic gestures.
2679
- * Animates with the configured spring/tween. Resolves when the animation
2680
- * completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
2681
- *
2682
- * Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
2683
- * nor `snap` (the sheet isn't settling to a known snap). `progress` events
2684
- * still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
2685
- */
2686
2405
  async dragTo(targetSize, velocityOrOpts = 0) {
2687
2406
  if (this.destroyed) return;
2688
2407
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2720,20 +2439,10 @@ var init_BottomSheetEngine = __esm({
2720
2439
  this.snaps.setAllowedIds(ids);
2721
2440
  this.updateAriaSlider();
2722
2441
  if (snap && ids.includes(snap)) {
2723
- void this.snapTo(
2724
- snap,
2725
- 0,
2726
- /* internal */
2727
- true
2728
- );
2442
+ void this.snapTo(snap, 0, true);
2729
2443
  } else if (!ids.includes(this.activeId)) {
2730
2444
  const fallback = ids[0];
2731
- if (fallback) void this.snapTo(
2732
- fallback,
2733
- 0,
2734
- /* internal */
2735
- true
2736
- );
2445
+ if (fallback) void this.snapTo(fallback, 0, true);
2737
2446
  }
2738
2447
  }
2739
2448
  setBackdropRange(range) {
@@ -2751,21 +2460,6 @@ var init_BottomSheetEngine = __esm({
2751
2460
  setScrimInteractive(interactive) {
2752
2461
  this.scrim.setScrimInteractive(interactive);
2753
2462
  }
2754
- /**
2755
- * Batch-update multiple scrim fields in one call. Apply order is:
2756
- * 1. `preset` — fills baseline color/blur/range/interactive.
2757
- * 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
2758
- * 3. `enabled` — flips the opacity contribution on/off.
2759
- * 4. `tapToClose` — installs/tears the click listener.
2760
- * 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
2761
- * 6. ONE `applySize` so the per-frame opacity stays consistent.
2762
- *
2763
- * Performance: collecting changes here (vs calling individual setters)
2764
- * avoids repeated `applySize` calls during a multi-field swap. Setters
2765
- * called here suppress their own internal `applySize` either by passing
2766
- * through fast-paths (mode/enabled no-op when value unchanged) or by
2767
- * being visual-only (color/blur/interactive don't influence opacity).
2768
- */
2769
2463
  setScrim(opts) {
2770
2464
  this.scrim.setScrim(opts, () => this.applySize(this.size));
2771
2465
  }
@@ -2781,11 +2475,6 @@ var init_BottomSheetEngine = __esm({
2781
2475
  setScrimOverlay(opts) {
2782
2476
  return this.scrim.setScrimOverlay(opts);
2783
2477
  }
2784
- /**
2785
- * Read-only snapshot of scrim state. Use the `setScrim*` setters to mutate.
2786
- * Stable public API — internals (e.g. cached opacity sentinels) are NOT
2787
- * exposed so consumers can't accidentally couple to ScrimController shape.
2788
- */
2789
2478
  getScrimState() {
2790
2479
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2791
2480
  }
@@ -2831,24 +2520,11 @@ var init_BottomSheetEngine = __esm({
2831
2520
  emit(event, payload) {
2832
2521
  this.bus.emit(event, payload);
2833
2522
  }
2834
- /**
2835
- * Abort the current cycle's signal and replace the controller. Returns the
2836
- * fresh signal so the caller can capture it locally before any await — when
2837
- * a future newCycle() / destroy aborts our controller, the captured signal
2838
- * flips to aborted and post-await guards short-circuit.
2839
- */
2840
2523
  newCycle() {
2841
2524
  this.currentAbort.abort();
2842
2525
  this.currentAbort = new AbortController();
2843
2526
  return this.currentAbort.signal;
2844
2527
  }
2845
- /**
2846
- * Fire `before-snap` and report whether a listener cancelled it. The
2847
- * `cancel()` callback exposed to listeners is frozen after the synchronous
2848
- * emit phase so async callers can't silently miss — they get a console
2849
- * warning. Returns `true` when cancelled so callers can early-return /
2850
- * spring-back without re-implementing the cancel/frozen pattern.
2851
- */
2852
2528
  emitBeforeSnap(target, previousId) {
2853
2529
  let cancelled = false;
2854
2530
  let frozen = false;
@@ -2882,30 +2558,9 @@ var init_BottomSheetEngine = __esm({
2882
2558
  this.snaps.setRaw(this.snapPointsRaw);
2883
2559
  this.scrim.invalidateOpacityCache();
2884
2560
  }
2885
- /**
2886
- * Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
2887
- * Engine wrapper kept so existing call sites don't all need updating;
2888
- * resolver-level memoization survives across method invocations.
2889
- */
2890
2561
  getAllowedRange() {
2891
2562
  return this.snaps.getAllowedRange();
2892
2563
  }
2893
- /**
2894
- * Write ARIA slider value attributes onto the handle so the engine itself
2895
- * is the source of truth for screen-reader announcements. Called from the
2896
- * constructor and from every code path that mutates `activeId` /
2897
- * `allowedIds`. WAI-ARIA 1.2 requires `aria-valuenow` for `role="slider"` —
2898
- * without it screen readers can't announce drag/snap progress to keyboard
2899
- * + AT users (WCAG 4.1.2 Name, Role, Value).
2900
- *
2901
- * Engine does NOT set `role="slider"` itself — that's the adapter's or
2902
- * consumer markup's responsibility. We only decorate with dynamic values.
2903
- *
2904
- * Empty allowed-list is malformed input (settable via `setAllowed([])`);
2905
- * short-circuit to remove ARIA value attributes so screen readers see a
2906
- * no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
2907
- * requires `valuemax > valuemin` for `role="slider"`).
2908
- */
2909
2564
  updateAriaSlider() {
2910
2565
  this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
2911
2566
  }
@@ -2930,11 +2585,6 @@ var init_BottomSheetEngine = __esm({
2930
2585
  element: this.element,
2931
2586
  mode: this.mode,
2932
2587
  getRoot: () => this.rootEl,
2933
- // Single coalesced reader — narrows the DI surface and removes 3 getter
2934
- // dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
2935
- // (this.dragContextBuf) — see field JSDoc for the retention contract.
2936
- // Inner `range` object is mutated in place too so callers caching its
2937
- // identity between frames stay valid.
2938
2588
  getDragContext: () => {
2939
2589
  const buf = this.dragContextBuf;
2940
2590
  const r = this.getAllowedRange();
@@ -3100,7 +2750,6 @@ var init_BottomSheetEngine = __esm({
3100
2750
  }
3101
2751
  });
3102
2752
  }
3103
- /** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
3104
2753
  haptic() {
3105
2754
  if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3106
2755
  try {
@@ -3116,6 +2765,9 @@ var init_BottomSheetEngine = __esm({
3116
2765
  style.transform = this.transformTemplate(offset);
3117
2766
  if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
3118
2767
  style.setProperty("--bs-size", `${size}px`);
2768
+ if (this.scrimParent) {
2769
+ this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2770
+ }
3119
2771
  }
3120
2772
  const progress = this.computeProgress(size);
3121
2773
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -3162,7 +2814,7 @@ var init_BottomSheetEngine = __esm({
3162
2814
  var baseStyles;
3163
2815
  var init_baseStyles = __esm({
3164
2816
  "src/web-component/baseStyles.ts"() {
3165
- baseStyles = `:where(.bs-root){--bs-surface:#ffffff;--bs-surface-border:rgba(225,227,237,1);--bs-shadow:0 -3px 12px rgba(0,0,0,0.12);--bs-radius:20px;--bs-handle-color:#b5b3a8;--bs-handle-width:40px;--bs-handle-height:5px;--bs-z-backdrop:90;--bs-z-sheet:100;--bs-header-min-height:80px;--bs-backdrop-color:rgba(0,0,0,0.5);--bs-scrim-color:rgba(0,0,0,0.4);--bs-scrim-blur:0px;--bs-z-scrim:calc(var(--bs-z-sheet) - 1);--bs-size:0px;--bs-progress:0}.bs-backdrop{position:fixed;inset:0;background:var(--bs-backdrop-color);opacity:0;pointer-events:none;z-index:var(--bs-z-backdrop);transition:opacity 0.15s linear;will-change:opacity}.bs-root[data-dragging="true"] .bs-backdrop,.bs-root[data-animating="true"] .bs-backdrop{transition:none}.bs-sheet{position:fixed;background:var(--bs-surface);box-shadow:var(--bs-shadow);z-index:var(--bs-z-sheet);overflow:hidden;display:flex;flex-direction:column;-webkit-tap-highlight-color:transparent;user-select:none;overscroll-behavior:contain}.bs-sheet[data-mode="bottom"]{left:0;right:0;bottom:0;width:100%;border-top-left-radius:var(--bs-radius);border-top-right-radius:var(--bs-radius);border-top:1px solid var(--bs-surface-border);padding-bottom:env(safe-area-inset-bottom);box-shadow:0 calc(var(--bs-progress,0) * -2px - 3px) calc(var(--bs-progress,0) * 18px + 12px) rgba(0,0,0,calc(var(--bs-progress,0) * 0.15 + 0.12))}.bs-sheet[data-mode="top"]{left:0;right:0;top:0;width:100%;border-bottom-left-radius:var(--bs-radius);border-bottom-right-radius:var(--bs-radius);border-bottom:1px solid var(--bs-surface-border);flex-direction:column-reverse;box-shadow:0 3px 12px rgba(0,0,0,0.12);padding-top:env(safe-area-inset-top)}.bs-sheet[data-mode="left"]{top:0;bottom:0;left:0;height:100%;border-top-right-radius:var(--bs-radius);border-bottom-right-radius:var(--bs-radius);border-right:1px solid var(--bs-surface-border);box-shadow:3px 0 12px rgba(0,0,0,0.12)}.bs-sheet[data-mode="right"]{top:0;bottom:0;right:0;height:100%;border-top-left-radius:var(--bs-radius);border-bottom-left-radius:var(--bs-radius);border-left:1px solid var(--bs-surface-border);box-shadow:-3px 0 12px rgba(0,0,0,0.12)}.bs-sheet[data-mode="left"] .bs-content{padding-right:36px}.bs-sheet[data-mode="right"] .bs-content{padding-left:36px}.bs-handle{flex-shrink:0;min-height:44px;display:flex;flex-direction:column;justify-content:center;padding:8px 16px 4px;cursor:grab;touch-action:none;user-select:none;background:var(--bs-surface);-webkit-tap-highlight-color:transparent;outline:none;position:relative}.bs-handle:active{cursor:grabbing}.bs-handle:focus-visible{outline:2px solid currentColor;outline-offset:-2px;border-radius:var(--bs-radius)}.bs-handle::before{content:"";display:block;width:var(--bs-handle-width);height:var(--bs-handle-height);background:var(--bs-handle-color);border-radius:calc(var(--bs-handle-height) / 2);margin:0 auto 8px;transition:width 0.15s ease,background 0.15s ease,transform 0.15s ease;pointer-events:none}.bs-sheet[data-mode="left"] .bs-handle,.bs-sheet[data-mode="right"] .bs-handle{position:absolute;top:0;bottom:0;width:28px;min-width:0;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0;background:var(--bs-surface);z-index:1;overflow:hidden}.bs-sheet[data-mode="left"] .bs-handle>*,.bs-sheet[data-mode="right"] .bs-handle>*{display:none}.bs-sheet[data-mode="left"] .bs-handle{right:0;border-left:1px solid var(--bs-surface-border)}.bs-sheet[data-mode="right"] .bs-handle{left:0;border-right:1px solid var(--bs-surface-border)}.bs-sheet[data-mode="left"] .bs-handle::before,.bs-sheet[data-mode="right"] .bs-handle::before{width:var(--bs-handle-height);height:var(--bs-handle-width);margin:0}.bs-sheet:hover .bs-handle::before{background:color-mix(in oklab,var(--bs-handle-color),#000 15%)}.bs-handle:active::before{background:color-mix(in oklab,var(--bs-handle-color),#000 35%);transform:scaleX(1.15)}.bs-handle.bs-handle--ticks::after{content:"";display:block;position:absolute;left:50%;bottom:6px;transform:translateX(-50%);width:calc(var(--bs-handle-width) * 2);height:3px;background-image:radial-gradient( circle,var(--bs-handle-color) 0 1.5px,transparent 1.6px );background-size:8px 3px;background-repeat:repeat-x;background-position:center;opacity:0;transition:opacity 0.18s ease-out;pointer-events:none}.bs-root[data-dragging="true"] .bs-handle.bs-handle--ticks::after{opacity:0.4}.bs-sheet[data-mode="top"] .bs-handle.bs-handle--ticks::after{bottom:auto;top:6px}.bs-sheet[data-mode="left"] .bs-handle.bs-handle--ticks::after,.bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after{left:50%;top:50%;bottom:auto;transform:translate(-50%,-50%);width:3px;height:calc(var(--bs-handle-width) * 2);background-size:3px 8px;background-repeat:repeat-y;margin-left:calc(var(--bs-handle-height) + 6px)}.bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after{margin-left:calc(-1 * (var(--bs-handle-height) + 6px))}.bs-content{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;padding:0 16px 16px}.bs-screen{position:absolute;inset:0;pointer-events:none;display:none;z-index:var(--bs-z-scrim)}.bs-scrim{background:var(--bs-scrim-color);transition:opacity 0.15s linear;will-change:opacity}.bs-root[data-dragging="true"] .bs-scrim,.bs-root[data-animating="true"] .bs-scrim{transition:none}.bs-scrim{backdrop-filter:blur(var(--bs-scrim-blur));-webkit-backdrop-filter:blur(var(--bs-scrim-blur))}.bs-button-slot{position:absolute;z-index:calc(var(--bs-z-sheet) + 1);pointer-events:auto}.bs-button-slot[data-side="left"]{left:16px;bottom:calc(var(--bs-size,0px) + 12px);transition:bottom 0.15s ease}.bs-button-slot[data-side="right"]{right:16px;bottom:calc(var(--bs-size,0px) + 12px);transition:bottom 0.15s ease}@media (prefers-reduced-motion:reduce){.bs-backdrop,.bs-handle::before,.bs-button-slot[data-side="left"],.bs-button-slot[data-side="right"]{transition:none !important}}@media (prefers-color-scheme:dark){:where(.bs-root){--bs-surface:#1a1a1d;--bs-surface-border:rgba(255,255,255,0.08);--bs-handle-color:rgba(255,255,255,0.45);--bs-shadow:0 -3px 16px rgba(0,0,0,0.5)}}.bs-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:where(.ovl-root){--ovl-surface:var(--bs-surface,#ffffff);--ovl-surface-border:var(--bs-surface-border,rgba(0,0,0,0.08));--ovl-shadow:0 -8px 40px rgba(0,0,0,0.35);--ovl-radius:var(--bs-radius,20px);--ovl-backdrop-color:rgba(0,0,0,0.5);--ovl-z-backdrop:200;--ovl-z-panel:201}.ovl-backdrop{position:fixed;inset:0;background:var(--ovl-backdrop-color);opacity:0;pointer-events:none;z-index:var(--ovl-z-backdrop)}.ovl-panel{position:fixed;background:var(--ovl-surface);box-shadow:var(--ovl-shadow);z-index:var(--ovl-z-panel);-webkit-tap-highlight-color:transparent;outline:none;transition:transform 0.32s cubic-bezier(0.29,1.24,0.84,0.99),opacity 0.32s ease-out}.ovl-panel[data-state="open"]{transform:translate3d(0,0,0);opacity:1}@starting-style{.ovl-panel[data-edge="bottom"][data-state="open"]{transform:translate3d(0,100%,0);opacity:0}.ovl-panel[data-edge="top"][data-state="open"]{transform:translate3d(0,-100%,0);opacity:0}.ovl-panel[data-edge="left"][data-state="open"]{transform:translate3d(-100%,0,0);opacity:0}.ovl-panel[data-edge="right"][data-state="open"]{transform:translate3d(100%,0,0);opacity:0}}.ovl-panel[data-edge="bottom"]{left:0;right:0;bottom:0;width:100%;border-top-left-radius:var(--ovl-radius);border-top-right-radius:var(--ovl-radius);border-top:1px solid var(--ovl-surface-border);padding-bottom:env(safe-area-inset-bottom)}.ovl-panel[data-edge="top"]{left:0;right:0;top:0;width:100%;border-bottom-left-radius:var(--ovl-radius);border-bottom-right-radius:var(--ovl-radius);border-bottom:1px solid var(--ovl-surface-border);padding-top:env(safe-area-inset-top)}.ovl-panel[data-edge="left"]{top:0;bottom:0;left:0;height:100%;border-top-right-radius:var(--ovl-radius);border-bottom-right-radius:var(--ovl-radius);border-right:1px solid var(--ovl-surface-border)}.ovl-panel[data-edge="right"]{top:0;bottom:0;right:0;height:100%;border-top-left-radius:var(--ovl-radius);border-bottom-left-radius:var(--ovl-radius);border-left:1px solid var(--ovl-surface-border)}@media (prefers-reduced-motion:reduce){.ovl-panel,.ovl-backdrop{transition:none !important}}`;
2817
+ baseStyles = `:where(.bs-root){--bs-surface:#ffffff;--bs-surface-border:rgba(225,227,237,1);--bs-shadow:0 -3px 12px rgba(0,0,0,0.12);--bs-radius:20px;--bs-handle-color:#b5b3a8;--bs-handle-width:40px;--bs-handle-height:5px;--bs-z-backdrop:90;--bs-z-sheet:100;--bs-header-min-height:80px;--bs-backdrop-color:rgba(0,0,0,0.5);--bs-scrim-color:rgba(0,0,0,0.4);--bs-scrim-blur:0px;--bs-z-scrim:calc(var(--bs-z-sheet) - 1);--bs-size:0px;--bs-progress:0}.bs-backdrop{position:fixed;inset:0;background:var(--bs-backdrop-color);opacity:0;pointer-events:none;z-index:var(--bs-z-backdrop);transition:opacity 0.15s linear;will-change:opacity}.bs-root[data-dragging="true"] .bs-backdrop,.bs-root[data-animating="true"] .bs-backdrop{transition:none}.bs-sheet{position:fixed;background:var(--bs-surface);box-shadow:var(--bs-shadow);z-index:var(--bs-z-sheet);overflow:hidden;display:flex;flex-direction:column;-webkit-tap-highlight-color:transparent;user-select:none;overscroll-behavior:contain}.bs-sheet[data-mode="bottom"]{left:0;right:0;bottom:0;width:100%;border-top-left-radius:var(--bs-radius);border-top-right-radius:var(--bs-radius);border-top:1px solid var(--bs-surface-border);padding-bottom:env(safe-area-inset-bottom);box-shadow:0 calc(var(--bs-progress,0) * -2px - 3px) calc(var(--bs-progress,0) * 18px + 12px) rgba(0,0,0,calc(var(--bs-progress,0) * 0.15 + 0.12))}.bs-sheet[data-mode="top"]{left:0;right:0;top:0;width:100%;border-bottom-left-radius:var(--bs-radius);border-bottom-right-radius:var(--bs-radius);border-bottom:1px solid var(--bs-surface-border);flex-direction:column-reverse;box-shadow:0 3px 12px rgba(0,0,0,0.12);padding-top:env(safe-area-inset-top)}.bs-sheet[data-mode="left"]{top:0;bottom:0;left:0;height:100%;border-top-right-radius:var(--bs-radius);border-bottom-right-radius:var(--bs-radius);border-right:1px solid var(--bs-surface-border);box-shadow:3px 0 12px rgba(0,0,0,0.12)}.bs-sheet[data-mode="right"]{top:0;bottom:0;right:0;height:100%;border-top-left-radius:var(--bs-radius);border-bottom-left-radius:var(--bs-radius);border-left:1px solid var(--bs-surface-border);box-shadow:-3px 0 12px rgba(0,0,0,0.12)}.bs-sheet[data-mode="left"] .bs-content{padding-right:36px}.bs-sheet[data-mode="right"] .bs-content{padding-left:36px}.bs-handle{flex-shrink:0;min-height:44px;display:flex;flex-direction:column;justify-content:center;padding:8px 16px 4px;cursor:grab;touch-action:none;user-select:none;background:var(--bs-surface);-webkit-tap-highlight-color:transparent;outline:none;position:relative}.bs-handle:active{cursor:grabbing}.bs-handle:focus-visible{outline:2px solid currentColor;outline-offset:-2px;border-radius:var(--bs-radius)}.bs-handle::before{content:"";display:block;width:var(--bs-handle-width);height:var(--bs-handle-height);background:var(--bs-handle-color);border-radius:calc(var(--bs-handle-height) / 2);margin:0 auto 8px;transition:width 0.15s ease,background 0.15s ease,transform 0.15s ease;pointer-events:none}.bs-sheet[data-mode="left"] .bs-handle,.bs-sheet[data-mode="right"] .bs-handle{position:absolute;top:0;bottom:0;width:28px;min-width:0;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0;background:var(--bs-surface);z-index:1;overflow:hidden}.bs-sheet[data-mode="left"] .bs-handle>*,.bs-sheet[data-mode="right"] .bs-handle>*{display:none}.bs-sheet[data-mode="left"] .bs-handle{right:0;border-left:1px solid var(--bs-surface-border)}.bs-sheet[data-mode="right"] .bs-handle{left:0;border-right:1px solid var(--bs-surface-border)}.bs-sheet[data-mode="left"] .bs-handle::before,.bs-sheet[data-mode="right"] .bs-handle::before{width:var(--bs-handle-height);height:var(--bs-handle-width);margin:0}.bs-sheet:hover .bs-handle::before{background:color-mix(in oklab,var(--bs-handle-color),#000 15%)}.bs-handle:active::before{background:color-mix(in oklab,var(--bs-handle-color),#000 35%);transform:scaleX(1.15)}.bs-handle.bs-handle--ticks::after{content:"";display:block;position:absolute;left:50%;bottom:6px;transform:translateX(-50%);width:calc(var(--bs-handle-width) * 2);height:3px;background-image:radial-gradient( circle,var(--bs-handle-color) 0 1.5px,transparent 1.6px );background-size:8px 3px;background-repeat:repeat-x;background-position:center;opacity:0;transition:opacity 0.18s ease-out;pointer-events:none}.bs-root[data-dragging="true"] .bs-handle.bs-handle--ticks::after{opacity:0.4}.bs-sheet[data-mode="top"] .bs-handle.bs-handle--ticks::after{bottom:auto;top:6px}.bs-sheet[data-mode="left"] .bs-handle.bs-handle--ticks::after,.bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after{left:50%;top:50%;bottom:auto;transform:translate(-50%,-50%);width:3px;height:calc(var(--bs-handle-width) * 2);background-size:3px 8px;background-repeat:repeat-y;margin-left:calc(var(--bs-handle-height) + 6px)}.bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after{margin-left:calc(-1 * (var(--bs-handle-height) + 6px))}.bs-content{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;padding:0 16px 16px}.bs-screen{position:absolute;inset:0;pointer-events:none;z-index:var(--bs-z-scrim)}.bs-scrim-overlay{z-index:calc(var(--bs-z-sheet) + 1);pointer-events:none}.bs-scrim{background:var(--bs-scrim-color);transition:opacity 0.15s linear;will-change:opacity}.bs-root[data-dragging="true"] .bs-scrim,.bs-root[data-animating="true"] .bs-scrim{transition:none}.bs-scrim{backdrop-filter:blur(var(--bs-scrim-blur));-webkit-backdrop-filter:blur(var(--bs-scrim-blur))}.bs-button-slot{position:absolute;z-index:calc(var(--bs-z-sheet) + 1);pointer-events:auto}.bs-button-slot[data-side="left"]{left:16px;bottom:calc(var(--bs-size,0px) + 12px);transition:bottom 0.15s ease}.bs-button-slot[data-side="right"]{right:16px;bottom:calc(var(--bs-size,0px) + 12px);transition:bottom 0.15s ease}@media (prefers-reduced-motion:reduce){.bs-backdrop,.bs-handle::before,.bs-button-slot[data-side="left"],.bs-button-slot[data-side="right"]{transition:none !important}}@media (prefers-color-scheme:dark){:where(.bs-root){--bs-surface:#1a1a1d;--bs-surface-border:rgba(255,255,255,0.08);--bs-handle-color:rgba(255,255,255,0.45);--bs-shadow:0 -3px 16px rgba(0,0,0,0.5)}}.bs-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:where(.ovl-root){--ovl-surface:var(--bs-surface,#ffffff);--ovl-surface-border:var(--bs-surface-border,rgba(0,0,0,0.08));--ovl-shadow:0 -8px 40px rgba(0,0,0,0.35);--ovl-radius:var(--bs-radius,20px);--ovl-backdrop-color:rgba(0,0,0,0.5);--ovl-z-backdrop:200;--ovl-z-panel:201}.ovl-backdrop{position:fixed;inset:0;background:var(--ovl-backdrop-color);opacity:0;pointer-events:none;z-index:var(--ovl-z-backdrop)}.ovl-panel{position:fixed;background:var(--ovl-surface);box-shadow:var(--ovl-shadow);z-index:var(--ovl-z-panel);-webkit-tap-highlight-color:transparent;outline:none;transition:transform 0.32s cubic-bezier(0.29,1.24,0.84,0.99),opacity 0.32s ease-out}.ovl-panel[data-state="open"]{transform:translate3d(0,0,0);opacity:1}@starting-style{.ovl-panel[data-edge="bottom"][data-state="open"]{transform:translate3d(0,100%,0);opacity:0}.ovl-panel[data-edge="top"][data-state="open"]{transform:translate3d(0,-100%,0);opacity:0}.ovl-panel[data-edge="left"][data-state="open"]{transform:translate3d(-100%,0,0);opacity:0}.ovl-panel[data-edge="right"][data-state="open"]{transform:translate3d(100%,0,0);opacity:0}}.ovl-panel[data-edge="bottom"]{left:0;right:0;bottom:0;width:100%;border-top-left-radius:var(--ovl-radius);border-top-right-radius:var(--ovl-radius);border-top:1px solid var(--ovl-surface-border);padding-bottom:env(safe-area-inset-bottom)}.ovl-panel[data-edge="top"]{left:0;right:0;top:0;width:100%;border-bottom-left-radius:var(--ovl-radius);border-bottom-right-radius:var(--ovl-radius);border-bottom:1px solid var(--ovl-surface-border);padding-top:env(safe-area-inset-top)}.ovl-panel[data-edge="left"]{top:0;bottom:0;left:0;height:100%;border-top-right-radius:var(--ovl-radius);border-bottom-right-radius:var(--ovl-radius);border-right:1px solid var(--ovl-surface-border)}.ovl-panel[data-edge="right"]{top:0;bottom:0;right:0;height:100%;border-top-left-radius:var(--ovl-radius);border-bottom-left-radius:var(--ovl-radius);border-left:1px solid var(--ovl-surface-border)}@media (prefers-reduced-motion:reduce){.ovl-panel,.ovl-backdrop{transition:none !important}}`;
3166
2818
  }
3167
2819
  });
3168
2820
 
@@ -3345,6 +2997,9 @@ var init_BottomSheetElement = __esm({
3345
2997
  this.teardownEngine();
3346
2998
  this.initEngine();
3347
2999
  }
3000
+ getEngine() {
3001
+ return this.engine;
3002
+ }
3348
3003
  teardownEngine() {
3349
3004
  this.offHandlers.forEach((off) => off());
3350
3005
  this.offHandlers = [];
@@ -3376,7 +3031,7 @@ var init_BottomSheetElement = __esm({
3376
3031
  handle,
3377
3032
  scrollContainer: content,
3378
3033
  backdrop: showBackdrop ? backdrop : void 0,
3379
- screenComponent: this.refs.screen,
3034
+ scrim: this.refs.screen,
3380
3035
  mode,
3381
3036
  snapPoints: parseSnapPoints(this.getAttribute(ATTR_SNAP_POINTS)),
3382
3037
  allowed: parseList(this.getAttribute(ATTR_ALLOWED)),