@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/svelte.cjs CHANGED
@@ -254,11 +254,6 @@ var SnapResolver = class {
254
254
  this.allowedIds = allowed ?? raw.map((p) => p.id);
255
255
  this.recompute();
256
256
  }
257
- /**
258
- * Re-resolve raw → resolved against the current viewport / fit-measure,
259
- * recompute maxAxisSize, invalidate range cache, notify engine. Called on
260
- * resize, orientationchange, visualViewport changes, and snap-list swaps.
261
- */
262
257
  recompute() {
263
258
  this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
264
259
  this.maxAxisSize = this.resolved.reduce(
@@ -268,15 +263,10 @@ var SnapResolver = class {
268
263
  this.rangeCache = null;
269
264
  this.onMaxAxisSizeChange?.(this.maxAxisSize);
270
265
  }
271
- /** Replace the raw snap list and recompute. Engine calls on `setSnapPoints`. */
272
266
  setRaw(raw) {
273
267
  this.raw = raw;
274
268
  this.recompute();
275
269
  }
276
- /**
277
- * Replace the allow-list. Range cache depends on `(resolvedSnaps ×
278
- * allowedIds)` so flipping ids alone invalidates without a full recompute.
279
- */
280
270
  setAllowedIds(ids) {
281
271
  this.allowedIds = ids;
282
272
  this.rangeCache = null;
@@ -284,11 +274,6 @@ var SnapResolver = class {
284
274
  findById(id) {
285
275
  return findById(id, this.resolved);
286
276
  }
287
- /**
288
- * Pure drag-settle target resolution. Engine passes the gesture-specific
289
- * fields (delta, velocity, pointerKind, flickVelocity, dragThreshold) plus
290
- * `size`/`activeId` since those are still engine-owned.
291
- */
292
277
  findDragSettleTarget(args) {
293
278
  return findDragSettleTarget({
294
279
  ...args,
@@ -297,18 +282,9 @@ var SnapResolver = class {
297
282
  maxAxisSize: this.maxAxisSize
298
283
  });
299
284
  }
300
- /**
301
- * Memoised allowed-range query. Twice-per-drag-frame (gesture onMove +
302
- * applySize→computeProgress); cache collapses 2× iteration of resolvedSnaps
303
- * to one recompute per geometry/allow-list change.
304
- */
305
285
  getAllowedRange() {
306
286
  return this.rangeCache ?? (this.rangeCache = allowedRange(this.resolved, this.allowedIds));
307
287
  }
308
- /**
309
- * Read-only view of resolved snaps. Returned as `readonly` to discourage
310
- * accidental mutation by feature install sites that close over this list.
311
- */
312
288
  getResolvedSnaps() {
313
289
  return this.resolved;
314
290
  }
@@ -318,18 +294,6 @@ var SnapResolver = class {
318
294
  getMaxAxisSize() {
319
295
  return this.maxAxisSize;
320
296
  }
321
- /**
322
- * External clamp override. The resize-observer feature compares
323
- * `getMaxAxisSize()` against the live viewport and shrinks if the largest
324
- * resolved snap exceeds it (mobile address-bar collapse / soft-keyboard
325
- * window resize). The next `recompute()` overwrites this clamp from the
326
- * resolved-snap reduction.
327
- *
328
- * Does NOT invalidate the range cache — viewport clamping doesn't change
329
- * `(resolvedSnaps × allowedIds) → range`. DOES fire `onMaxAxisSizeChange`
330
- * so the resolver owns the layout-axis style write end-to-end (symmetric
331
- * with `recompute()` — feature call sites no longer duplicate the write).
332
- */
333
297
  setMaxAxisSize(n) {
334
298
  this.maxAxisSize = n;
335
299
  this.onMaxAxisSizeChange?.(n);
@@ -704,73 +668,49 @@ var WriteSentinel = class {
704
668
  constructor() {
705
669
  __publicField(this, "last", -1);
706
670
  }
707
- /**
708
- * Returns `true` (and updates the cached value) if `next` differs from the
709
- * cached `last` by more than `epsilon`. Returns `false` (and leaves the
710
- * cache untouched) when the delta is within epsilon — caller skips the
711
- * CSSOM write.
712
- */
713
671
  shouldWrite(next, epsilon) {
714
672
  if (Math.abs(next - this.last) <= epsilon) return false;
715
673
  this.last = next;
716
674
  return true;
717
675
  }
718
- /**
719
- * Force the next `shouldWrite` to pass. Resets the cache to `-1` so any
720
- * non-negative `next` exceeds the epsilon comparison. Called from setters
721
- * that mutate state under the cache (range/mode/enabled flips that change
722
- * the progress→opacity mapping but don't move `progress` itself).
723
- */
724
676
  invalidate() {
725
677
  this.last = -1;
726
678
  }
727
- /**
728
- * Record `value` as the last-written without performing a write. Used
729
- * when a setter writes a literal value through a different path (e.g.
730
- * `setScrimMode("off")` writes `opacity: "0"` inline) and wants future
731
- * `shouldWrite` calls to short-circuit against that known cached state.
732
- */
733
679
  setLastWritten(value) {
734
680
  this.last = value;
735
681
  }
736
- /** Read-only accessor for diagnostic / dedup-aware peek. */
737
682
  get value() {
738
683
  return this.last;
739
684
  }
740
685
  };
741
686
 
687
+ // src/core/primitives/devWarn.ts
688
+ function devWarn(...args) {
689
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
690
+ console.warn(...args);
691
+ }
692
+ }
693
+
742
694
  // src/core/controllers/scrim-controller.ts
695
+ var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
743
696
  var ScrimController = class {
744
697
  constructor(deps, opts) {
745
- // ── DOM refs (mirror engine's; cached for hot path) ─────────────────
746
698
  __publicField(this, "screenComponent");
747
699
  __publicField(this, "backdrop");
748
700
  __publicField(this, "mode");
749
- // ── Engine callbacks ────────────────────────────────────────────────
750
701
  __publicField(this, "isDestroyed");
751
702
  __publicField(this, "isTopSheet");
752
703
  __publicField(this, "getAllowedIds");
753
704
  __publicField(this, "getResolvedSnaps");
754
705
  __publicField(this, "snapToFn");
755
- // ── Mode + state ────────────────────────────────────────────────────
756
706
  __publicField(this, "scrimMode");
757
707
  __publicField(this, "backdropRange");
758
708
  __publicField(this, "screenRange");
759
- // setScrimEnabled toggle — gated through `scrimEnabled` rather than nuking
760
- // ranges so re-enable can restore prior values without a remount. Public
761
- // so tests / introspection can read it without an `as any` cast.
762
709
  __publicField(this, "scrimEnabled", true);
763
710
  __publicField(this, "savedScrimRanges", null);
764
- // ── Tap-to-close ────────────────────────────────────────────────────
765
711
  __publicField(this, "scrimTapToCloseEnabled", false);
766
712
  __publicField(this, "detachScrimTap", null);
767
- // ── Scrim overlay slot (positioned floating element) ────────────────
768
713
  __publicField(this, "scrimOverlayTeardown", null);
769
- // ── Hot-path opacity dedup caches (per-frame writes) ────────────────
770
- // Numeric sentinels migrated to WriteSentinel primitive — single
771
- // invalidation surface, shared with the engine's size/progress sentinels.
772
- // The two string-typed sentinels (`Pointer`, `Display`) keep their inline
773
- // pattern: equality on string union, no epsilon, different reset semantics.
774
714
  __publicField(this, "backdropOpacitySentinel", new WriteSentinel());
775
715
  __publicField(this, "screenOpacitySentinel", new WriteSentinel());
776
716
  __publicField(this, "lastBackdropPointer", "");
@@ -805,41 +745,15 @@ var ScrimController = class {
805
745
  }
806
746
  }
807
747
  }
808
- /**
809
- * Invalidate the opacity dedup caches. Called from any setter that mutates
810
- * range/mode/enabled — opacity is a pure function of (progress, range,
811
- * enabled), so a config change makes the cached values stale even if
812
- * `progress` itself didn't move. Next `applyOpacity(progress, false)` call
813
- * sees the -1 sentinel and re-runs the math instead of short-circuiting.
814
- *
815
- * Public for the engine: `setSnapPoints` mutates resolved geometry which
816
- * shifts `progress→opacity` mapping under the cache; engine calls this to
817
- * keep the next render frame correct on a static sheet.
818
- * @internal
819
- */
820
748
  invalidateOpacityCache() {
821
749
  this.backdropOpacitySentinel.invalidate();
822
750
  this.screenOpacitySentinel.invalidate();
823
751
  }
824
- /** Engine calls this when its `attach()` runs — installs scrim-tap-click if requested. */
825
752
  attach() {
826
753
  if (this.scrimTapToCloseEnabled) {
827
754
  this.setScrimTapToClose(true);
828
755
  }
829
756
  }
830
- // ── Hot-path: opacity computation called per-frame from engine.applySize ──
831
- /**
832
- * Per-frame opacity writer. Computes backdrop + screen opacity from the
833
- * sheet's progress (0..1 within allowed range), gated by sub-pixel dedup
834
- * caches so steady-state frames don't trigger style recalc.
835
- *
836
- * `progressChanged` is the engine's already-computed dedup gate — when
837
- * `false`, opacity is by construction a pure function of `progress` so
838
- * cached values are still valid; we short-circuit the Math.min/max +
839
- * division work entirely. Setters that mutate ranges/mode/enabled
840
- * invalidate the cache by writing -1 sentinels so the next call re-runs
841
- * the math.
842
- */
843
757
  applyOpacity(progress, progressChanged) {
844
758
  if (!progressChanged && this.backdropOpacitySentinel.value !== -1 && this.screenOpacitySentinel.value !== -1) {
845
759
  return;
@@ -885,80 +799,30 @@ var ScrimController = class {
885
799
  }
886
800
  }
887
801
  }
888
- // ── Public setters (engine forwards these 1:1) ─────────────────────
889
- /**
890
- * Replace the [start, end] progress window in which the backdrop fades 0→1.
891
- * @param range - Tuple of two progress values in 0..1 (start <= end).
892
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
893
- * @example
894
- * // Backdrop only starts darkening after the sheet crosses 30% open.
895
- * engine.setBackdropRange([0.3, 1]);
896
- */
897
802
  setBackdropRange(range, applySize) {
898
803
  if (this.isDestroyed()) return;
899
804
  this.backdropRange = range;
900
805
  this.invalidateOpacityCache();
901
806
  applySize();
902
807
  }
903
- /**
904
- * Replace the [start, end] progress window in which the screen scrim fades 0→1.
905
- * @param range - Tuple of two progress values in 0..1 (start <= end).
906
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
907
- * @example
908
- * // Scrim is fully opaque only when the sheet is past 50% — softer feel.
909
- * engine.setScreenRange([0, 0.5]);
910
- */
911
808
  setScreenRange(range, applySize) {
912
809
  if (this.isDestroyed()) return;
913
810
  this.screenRange = range;
914
811
  this.invalidateOpacityCache();
915
812
  applySize();
916
813
  }
917
- /**
918
- * Set the scrim background color. Pass `null`/`undefined` to clear the
919
- * inline write and let consumer CSS take over.
920
- * @param color - Any valid CSS color string, or `null`/`undefined` to clear.
921
- * @example
922
- * engine.setScrimColor("rgba(0, 0, 0, 0.6)");
923
- * engine.setScrimColor(null); // hand back to consumer CSS
924
- */
925
814
  setScrimColor(color) {
926
815
  if (this.isDestroyed()) return;
927
816
  this.applyScrimStyles({ color: color === void 0 ? null : color });
928
817
  }
929
- /**
930
- * Set the scrim backdrop-filter blur radius. Pass `null`/`undefined` to clear.
931
- * @param blur - CSS length (e.g. `"8px"`) consumed as `blur(<value>)`, or
932
- * `null`/`undefined` to remove the filter.
933
- * @example
934
- * engine.setScrimBlur("12px"); // frosted-glass effect
935
- * engine.setScrimBlur(null); // disable blur
936
- */
937
818
  setScrimBlur(blur) {
938
819
  if (this.isDestroyed()) return;
939
820
  this.applyScrimStyles({ blur: blur === void 0 ? null : blur });
940
821
  }
941
- /**
942
- * Toggle whether the scrim layer captures pointer events.
943
- * @param interactive - `true` → `pointer-events: auto`; `false` → `none`.
944
- * @example
945
- * engine.setScrimInteractive(true); // scrim swallows clicks
946
- * engine.setScrimInteractive(false); // clicks pass through to content below
947
- */
948
822
  setScrimInteractive(interactive) {
949
823
  if (this.isDestroyed()) return;
950
824
  this.applyScrimStyles({ interactive });
951
825
  }
952
- /**
953
- * Switch scrim positioning at runtime.
954
- * @param mode - `"full"` (consumer CSS), `"above-sheet"` (engine-pinned inset
955
- * so only the area NOT covered by the sheet is dimmed), or
956
- * `"off"` (display:none + opacity:0 — no remount needed).
957
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
958
- * @example
959
- * engine.setScrimMode("above-sheet"); // dim only the area above the sheet
960
- * engine.setScrimMode("off"); // disable scrim layer
961
- */
962
826
  setScrimMode(mode, applySize) {
963
827
  if (this.isDestroyed()) return;
964
828
  if (mode === this.scrimMode) return;
@@ -988,15 +852,6 @@ var ScrimController = class {
988
852
  }
989
853
  applySize();
990
854
  }
991
- /**
992
- * Enable or disable click-on-scrim → snap to first non-zero allowed snap.
993
- * Auto-promotes `pointer-events: auto` when scrim is non-interactive so the
994
- * click listener actually fires.
995
- * @param enabled - `true` installs the click listener; `false` removes it.
996
- * @example
997
- * engine.setScrimTapToClose(true); // tap outside sheet collapses it
998
- * engine.setScrimTapToClose(false); // require explicit close button
999
- */
1000
855
  setScrimTapToClose(enabled) {
1001
856
  if (this.isDestroyed()) return;
1002
857
  if (!this.screenComponent) return;
@@ -1026,16 +881,6 @@ var ScrimController = class {
1026
881
  this.scrimTapToCloseEnabled = false;
1027
882
  }
1028
883
  }
1029
- /**
1030
- * Toggle the scrim layer without remounting. Disabling stashes current
1031
- * `screenRange`/`backdropRange` and degenerates them to `[1, 1]` (opacity
1032
- * formula → 0 across the whole range); re-enabling restores the saved values.
1033
- * @param enabled - `false` hides scrim, `true` restores prior ranges.
1034
- * @param applySize - Engine-supplied recompute hook; runs one applyOpacity pass.
1035
- * @example
1036
- * engine.setScrimEnabled(false); // hide scrim, ranges remembered
1037
- * engine.setScrimEnabled(true); // restore prior ranges (no remount)
1038
- */
1039
884
  setScrimEnabled(enabled, applySize) {
1040
885
  if (this.isDestroyed()) return;
1041
886
  if (enabled === this.scrimEnabled) return;
@@ -1054,20 +899,6 @@ var ScrimController = class {
1054
899
  }
1055
900
  applySize();
1056
901
  }
1057
- /**
1058
- * Batch-update multiple scrim fields with a single applySize() at the end.
1059
- * If `preset` is set, it seeds color/blur/interactive/range first; explicit
1060
- * fields override.
1061
- * @param opts - Partial scrim config: preset, mode, enabled, tapToClose,
1062
- * color, blur, interactive, range.
1063
- * @param applySize - Engine-supplied recompute hook; runs ONCE per call.
1064
- * @example
1065
- * engine.setScrim({
1066
- * preset: "frosted",
1067
- * mode: "above-sheet",
1068
- * tapToClose: true,
1069
- * });
1070
- */
1071
902
  setScrim(opts, applySize) {
1072
903
  if (this.isDestroyed()) return;
1073
904
  if (opts.preset) {
@@ -1090,27 +921,18 @@ var ScrimController = class {
1090
921
  interactive: opts.interactive
1091
922
  });
1092
923
  if (opts.range !== void 0) {
1093
- this.screenRange = opts.range;
924
+ if (!this.scrimEnabled && this.savedScrimRanges) {
925
+ this.savedScrimRanges = {
926
+ screen: opts.range,
927
+ backdrop: this.savedScrimRanges.backdrop
928
+ };
929
+ } else {
930
+ this.screenRange = opts.range;
931
+ }
1094
932
  }
1095
933
  this.invalidateOpacityCache();
1096
934
  applySize();
1097
935
  }
1098
- /**
1099
- * Inject a positioned floating element into the scrim layer (e.g. a close
1100
- * button anchored top-right). Replaces any prior overlay so repeated calls
1101
- * (reactive effects re-running) don't accumulate wrappers.
1102
- * @param opts - Children element, optional `inset` (default `"16px"`),
1103
- * `position` (9-way grid, default `"top-right"`), and
1104
- * `interactive` flag (default `true`).
1105
- * @returns Teardown function that removes the wrapper from the DOM.
1106
- * @example
1107
- * const teardown = engine.setScrimOverlay({
1108
- * children: closeBtn,
1109
- * position: "top-right",
1110
- * inset: "20px",
1111
- * });
1112
- * // later: teardown(); // remove the floating button
1113
- */
1114
936
  setScrimOverlay(opts) {
1115
937
  if (this.isDestroyed()) return () => {
1116
938
  };
@@ -1118,7 +940,18 @@ var ScrimController = class {
1118
940
  };
1119
941
  this.scrimOverlayTeardown?.();
1120
942
  const screen = this.screenComponent;
1121
- const inset = opts.inset ?? "16px";
943
+ const insetRaw = opts.inset ?? "16px";
944
+ const inset = VALID_CSS_LENGTH.test(insetRaw) ? insetRaw : "16px";
945
+ if (inset !== insetRaw) {
946
+ devWarn(
947
+ `[bottom-sheet] setScrimOverlay: invalid inset ${JSON.stringify(insetRaw)}; expected a CSS length like "16px" / "1rem" / "5%". Falling back to "16px".`
948
+ );
949
+ }
950
+ if (opts.children.ownerDocument && opts.children.ownerDocument !== screen.ownerDocument) {
951
+ devWarn(
952
+ "[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."
953
+ );
954
+ }
1122
955
  const position = opts.position ?? "top-right";
1123
956
  const interactive = opts.interactive ?? true;
1124
957
  const wrapper = screen.ownerDocument.createElement("div");
@@ -1167,12 +1000,30 @@ var ScrimController = class {
1167
1000
  ws.bottom = inset;
1168
1001
  ws.right = inset;
1169
1002
  break;
1003
+ case "sheet-top-left":
1004
+ case "sheet-top-center":
1005
+ case "sheet-top-right":
1006
+ applySheetAnchoredPosition(
1007
+ ws,
1008
+ this.mode,
1009
+ position,
1010
+ inset
1011
+ );
1012
+ break;
1170
1013
  }
1171
1014
  if (interactive) {
1172
1015
  ws.pointerEvents = "auto";
1173
1016
  }
1174
1017
  wrapper.appendChild(opts.children);
1175
- screen.appendChild(wrapper);
1018
+ const host = screen.parentElement;
1019
+ if (!host) {
1020
+ devWarn(
1021
+ "[bottom-sheet] setScrimOverlay: scrim element has no parent, skipping overlay injection. Mount the sheet root before calling setScrimOverlay."
1022
+ );
1023
+ return () => {
1024
+ };
1025
+ }
1026
+ host.appendChild(wrapper);
1176
1027
  const teardown = () => {
1177
1028
  if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
1178
1029
  if (this.scrimOverlayTeardown === teardown) {
@@ -1182,7 +1033,6 @@ var ScrimController = class {
1182
1033
  this.scrimOverlayTeardown = teardown;
1183
1034
  return teardown;
1184
1035
  }
1185
- /** Tear down toggle-state listeners + remove any injected overlay wrapper. */
1186
1036
  destroy() {
1187
1037
  this.detachScrimTap?.();
1188
1038
  this.detachScrimTap = null;
@@ -1201,8 +1051,6 @@ var ScrimController = class {
1201
1051
  this.lastBackdropPointer = "";
1202
1052
  this.lastScreenDisplay = "";
1203
1053
  }
1204
- // ── Private helpers ────────────────────────────────────────────────
1205
- /** Centralized scrim style writer — atomic 3-property write across all setters. */
1206
1054
  applyScrimStyles(opts) {
1207
1055
  if (!this.screenComponent) return;
1208
1056
  const s = this.screenComponent.style;
@@ -1224,7 +1072,6 @@ var ScrimController = class {
1224
1072
  s.pointerEvents = opts.interactive ? "auto" : "none";
1225
1073
  }
1226
1074
  }
1227
- /** Pin the screen overlay to the area NOT occupied by the sheet. */
1228
1075
  applyAboveSheetInset() {
1229
1076
  if (!this.screenComponent) return;
1230
1077
  const insetByMode = {
@@ -1239,6 +1086,35 @@ var ScrimController = class {
1239
1086
  s.pointerEvents = "none";
1240
1087
  }
1241
1088
  };
1089
+ function resolveSheetAnchoredStyle(mode, position, inset) {
1090
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1091
+ switch (mode) {
1092
+ case "bottom":
1093
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1094
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1095
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1096
+ case "top":
1097
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1098
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1099
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1100
+ case "left":
1101
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1102
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1103
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1104
+ case "right":
1105
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1106
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1107
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1108
+ }
1109
+ }
1110
+ function applySheetAnchoredPosition(ws, mode, position, inset) {
1111
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
1112
+ if (style.top !== void 0) ws.top = style.top;
1113
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
1114
+ if (style.left !== void 0) ws.left = style.left;
1115
+ if (style.right !== void 0) ws.right = style.right;
1116
+ if (style.transform !== void 0) ws.transform = style.transform;
1117
+ }
1242
1118
 
1243
1119
  // src/core/animation/animation.ts
1244
1120
  var easeOutBack = (t) => {
@@ -1391,13 +1267,9 @@ var AnimationRunner = class {
1391
1267
  __publicField(this, "duration");
1392
1268
  __publicField(this, "easing");
1393
1269
  __publicField(this, "respectReducedMotion");
1394
- /** Live-tracked via mq listener so animateTo skips matchMedia per snap. */
1395
1270
  __publicField(this, "reducedMotion", false);
1396
- /** Disposes the matchMedia change listener — drained from destroy(). */
1397
1271
  __publicField(this, "detachReducedMotion", null);
1398
- /** Computed once — both inputs (option + API presence) stable for life. */
1399
1272
  __publicField(this, "viewTransitionsAvailable");
1400
- /** In-flight handles. Public for tests / debugging only — engine drives via cancel(). */
1401
1273
  __publicField(this, "currentTween", null);
1402
1274
  __publicField(this, "currentSpring", null);
1403
1275
  this.element = deps.element;
@@ -1428,26 +1300,13 @@ var AnimationRunner = class {
1428
1300
  }
1429
1301
  }
1430
1302
  }
1431
- /**
1432
- * Live derived flag — `true` whenever a spring/tween handle is in flight.
1433
- * Reading the canonical handles (instead of mirroring into a separate
1434
- * boolean) eliminates the reduced-motion early-return latent bug where
1435
- * the flag stayed true forever, and removes the cross-controller
1436
- * setIsAnimating callback.
1437
- */
1438
1303
  get isAnimating() {
1439
1304
  return this.currentTween !== null || this.currentSpring !== null;
1440
1305
  }
1441
- /** Cancel any in-flight tween/spring without touching engine state. */
1442
1306
  cancel() {
1443
1307
  this.currentTween?.cancel();
1444
1308
  this.currentSpring?.cancel();
1445
1309
  }
1446
- /**
1447
- * Animate from current size to `target`. Resolves when the animation
1448
- * completes; resolves early if cancelled mid-flight (caller checks an
1449
- * abort signal post-await for cycle-invariant correctness).
1450
- */
1451
1310
  async animateTo(target, velocityPxPerMs) {
1452
1311
  if (target === this.getSize()) return;
1453
1312
  this.currentTween?.cancel();
@@ -1488,7 +1347,6 @@ var AnimationRunner = class {
1488
1347
  if (!this.isDragging()) this.element.style.willChange = "auto";
1489
1348
  }
1490
1349
  }
1491
- /** Tear down the matchMedia listener. Engine calls from destroy(). */
1492
1350
  destroy() {
1493
1351
  this.detachReducedMotion?.();
1494
1352
  this.detachReducedMotion = null;
@@ -1648,12 +1506,8 @@ var LifecycleController = class {
1648
1506
  __publicField(this, "inertSiblings");
1649
1507
  __publicField(this, "shouldApplyInertSiblings");
1650
1508
  __publicField(this, "returnFocus");
1651
- /** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
1652
1509
  __publicField(this, "releaseFocusTrap", null);
1653
1510
  __publicField(this, "releaseScrollLock", null);
1654
- /** True between install() and release(). Guards release()/destroy() from
1655
- * re-running side effects (notably `returnFocus.focus()`) on already-
1656
- * released or never-installed instances. */
1657
1511
  __publicField(this, "installed", false);
1658
1512
  __publicField(this, "destroyed", false);
1659
1513
  this.element = deps.element;
@@ -1667,14 +1521,6 @@ var LifecycleController = class {
1667
1521
  this.returnFocus = opts.returnFocus;
1668
1522
  this.inertSiblings = createInertSiblings(() => this.element);
1669
1523
  }
1670
- /**
1671
- * Run the open-time install steps. Idempotent: each step checks its
1672
- * existing teardown ref so a re-open during a half-cancelled close
1673
- * doesn't double-install. Sets `installed=true` BEFORE the first side
1674
- * effect so a partial throw (detached / shadow-DOM target) leaves the
1675
- * caller's `release()` rollback path armed to undo whatever did succeed.
1676
- * No-op when destroyed.
1677
- */
1678
1524
  install() {
1679
1525
  if (this.destroyed) return;
1680
1526
  this.installed = true;
@@ -1691,22 +1537,10 @@ var LifecycleController = class {
1691
1537
  this.inertSiblings.apply();
1692
1538
  }
1693
1539
  }
1694
- /**
1695
- * Update the post-release focus target. Overlay re-binds this when the
1696
- * consumer changes `returnFocus` after construction; the new value applies
1697
- * on the next `release()`. No-op when destroyed.
1698
- */
1699
1540
  setReturnFocus(target) {
1700
1541
  if (this.destroyed) return;
1701
1542
  this.returnFocus = target;
1702
1543
  }
1703
- /**
1704
- * Release all install-time teardowns. Short-circuits when not installed,
1705
- * so `close() → destroy()` (each routing through here) only restores
1706
- * focus once. The `installed` guard lets `inertSiblings.remove()` and
1707
- * the focus-trap/scroll-lock teardown nullings stay simple — they were
1708
- * already idempotent, but `returnFocus.focus()` was firing on every call.
1709
- */
1710
1544
  release() {
1711
1545
  if (!this.installed) return;
1712
1546
  this.installed = false;
@@ -1720,12 +1554,6 @@ var LifecycleController = class {
1720
1554
  target?.focus?.();
1721
1555
  }
1722
1556
  }
1723
- /**
1724
- * Final teardown — releases any still-installed listeners and seals the
1725
- * controller. After destroy(), install() is a no-op and setReturnFocus()
1726
- * is rejected, so post-destroy mutations can't queue up a stale focus
1727
- * restore.
1728
- */
1729
1557
  destroy() {
1730
1558
  this.release();
1731
1559
  this.destroyed = true;
@@ -1735,12 +1563,10 @@ var LifecycleController = class {
1735
1563
  // src/core/gestures.ts
1736
1564
  var isAxisVertical = (mode) => mode === "bottom" || mode === "top";
1737
1565
  var SampleRing = class {
1738
- // current count, ≤ capacity
1739
1566
  constructor(capacity) {
1740
1567
  __publicField(this, "slots");
1741
1568
  __publicField(this, "capacity");
1742
1569
  __publicField(this, "head", 0);
1743
- // next write index
1744
1570
  __publicField(this, "size", 0);
1745
1571
  this.capacity = capacity;
1746
1572
  this.slots = new Array(capacity);
@@ -1753,12 +1579,6 @@ var SampleRing = class {
1753
1579
  this.head = (this.head + 1) % this.capacity;
1754
1580
  if (this.size < this.capacity) this.size++;
1755
1581
  }
1756
- /**
1757
- * Drop oldest entries whose timestamp is below `cutoff`, but never shrink
1758
- * below `minKeep`. Mirrors the original
1759
- * `while (samples.length > minKeep && samples[0].t < cutoff) samples.shift()`
1760
- * but without copying — just advances the logical tail.
1761
- */
1762
1582
  pruneBefore(cutoff, minKeep) {
1763
1583
  while (this.size > minKeep) {
1764
1584
  const oldestIdx = (this.head - this.size + this.capacity) % this.capacity;
@@ -1894,28 +1714,14 @@ var GestureController = class {
1894
1714
  __publicField(this, "detach", null);
1895
1715
  __publicField(this, "isDragging_", false);
1896
1716
  __publicField(this, "dragStartSize", 0);
1897
- // Only "touch" gestures are eligible for soft-keyboard dismissal —
1898
- // mouse/trackpad users don't have a keyboard to dismiss.
1899
1717
  __publicField(this, "currentPointerKind", "touch");
1900
- // One blur per drag — flipping focus every move event races the keyboard's
1901
- // own animations on iOS and makes the page jitter.
1902
1718
  __publicField(this, "keyboardDismissed", false);
1903
- // Reused payload object for the "drag" event — mutated each onMove instead
1904
- // of allocating a fresh `{size, delta}` per frame. At 60-120Hz a long drag
1905
- // would otherwise churn tens of KB through the GC. Mirrors browser
1906
- // PointerEvent semantics: consumers must NOT retain the reference across
1907
- // frames (clone if needed). See SheetEventMap.drag JSDoc.
1908
1719
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1909
1720
  this.deps = deps;
1910
1721
  }
1911
- /** True between `dragstart` and `dragend`/`onCancel`. */
1912
1722
  get isDragging() {
1913
1723
  return this.isDragging_;
1914
1724
  }
1915
- /**
1916
- * Bind pointer listeners on the handle and return a teardown. Engine pushes
1917
- * the teardown into its TeardownStack so destroy() drains it once.
1918
- */
1919
1725
  install() {
1920
1726
  if (this.detach) return this.detach;
1921
1727
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
@@ -1972,11 +1778,6 @@ var GestureController = class {
1972
1778
  });
1973
1779
  return this.detach;
1974
1780
  }
1975
- /**
1976
- * Force-clear drag visual state. Engine calls from destroy() when destroyed
1977
- * mid-drag — gesture detach won't synthesize an onCancel, so we have to
1978
- * roll back the data-attribute + will-change ourselves.
1979
- */
1980
1781
  forceClearDragState() {
1981
1782
  if (!this.isDragging_) return;
1982
1783
  this.isDragging_ = false;
@@ -1995,11 +1796,6 @@ var AriaSliderWriter = class {
1995
1796
  mode === "bottom" || mode === "top" ? "vertical" : "horizontal"
1996
1797
  );
1997
1798
  }
1998
- /**
1999
- * Update slider position. `allowedIds` length determines `aria-valuemax`;
2000
- * `activeId` indexes into it for `aria-valuenow` + `aria-valuetext`.
2001
- * Empty list → strip every dynamic attr so AT skips the handle entirely.
2002
- */
2003
1799
  setValue(allowedIds, activeId) {
2004
1800
  if (allowedIds.length === 0) {
2005
1801
  this.handle.removeAttribute("aria-valuemin");
@@ -2073,9 +1869,6 @@ function resolveEngineOptions(opts, extras) {
2073
1869
  scrimMode: opts.scrimMode,
2074
1870
  scrimColor: opts.scrimColor,
2075
1871
  scrimBlur: opts.scrimBlur,
2076
- // Auto-promote pointer-events when tap-to-close is on so the click
2077
- // listener actually receives events. Caller can still force `false`
2078
- // explicitly to opt out — `??` only kicks in for `undefined`.
2079
1872
  scrimInteractive: opts.scrimInteractive ?? (opts.scrimTapToClose ? true : void 0),
2080
1873
  scrimTapToClose: opts.scrimTapToClose,
2081
1874
  scrimPreset: opts.scrimPreset,
@@ -2096,10 +1889,6 @@ function resolveEngineOptions(opts, extras) {
2096
1889
  closeOnEscape: opts.closeOnEscape,
2097
1890
  lockBodyScroll: opts.lockBodyScroll,
2098
1891
  inertSiblings: opts.inertSiblings,
2099
- // Folded in from `extras` so the lifecycle bag is fully assembled at
2100
- // the call site — engine no longer needs a spread+override at
2101
- // controller construction. When `extras` is omitted, the field stays
2102
- // undefined and the controller uses its own default predicate.
2103
1892
  shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2104
1893
  }
2105
1894
  };
@@ -2134,6 +1923,7 @@ var BottomSheetEngine = class {
2134
1923
  __publicField(this, "scrollContainer");
2135
1924
  __publicField(this, "backdrop");
2136
1925
  __publicField(this, "screenComponent");
1926
+ __publicField(this, "scrimParent", null);
2137
1927
  __publicField(this, "mode");
2138
1928
  __publicField(this, "flickVelocity");
2139
1929
  __publicField(this, "dragThreshold");
@@ -2144,40 +1934,22 @@ var BottomSheetEngine = class {
2144
1934
  __publicField(this, "lifecycle");
2145
1935
  __publicField(this, "closeOnBack");
2146
1936
  __publicField(this, "routedTo");
2147
- // Engine retains `snapPointsRaw` for the "find a closed-id" query in
2148
- // close() (raw input, not resolved geometry). Resolver owns geometry.
2149
1937
  __publicField(this, "snapPointsRaw");
2150
1938
  __publicField(this, "snaps");
2151
1939
  __publicField(this, "activeId");
2152
1940
  __publicField(this, "size", 0);
2153
- // Constructed in attach(); a plugin's install() could read `state` before
2154
- // attach completes if the constructor throws partway through. State getter
2155
- // null-guards via `this.gesture?.isDragging ?? false`.
2156
1941
  __publicField(this, "gesture");
2157
1942
  __publicField(this, "rootEl", null);
2158
1943
  __publicField(this, "destroyed", false);
2159
- // In-flight ViewTransition handle. A second snapTo while a VT is in flight
2160
- // would otherwise let Chromium reject the second's promise and flicker —
2161
- // newCycle() calls skipTransition() to abort cleanly.
2162
1944
  __publicField(this, "currentViewTransition", null);
2163
- // Each cycle captures the signal at entry; when a newer cycle aborts the
2164
- // controller, the captured signal flips to `aborted=true` and post-await
2165
- // guards short-circuit (prevents stale snap/open/close emits).
2166
1945
  __publicField(this, "currentAbort", new AbortController());
2167
- // Sub-pixel `--bs-size` deltas and <1% `--bs-progress` deltas are not
2168
- // visually distinguishable; gating CSSOM writes drops 30-50% of writes
2169
- // during the spring's settle tail.
2170
1946
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
2171
1947
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
2172
1948
  __publicField(this, "isTopSheet", true);
2173
- // Pooled payload — identity documented in SheetEventMap.progress JSDoc;
2174
- // consumers must not retain across frames.
2175
1949
  __publicField(this, "progressPayload", {
2176
1950
  value: 0,
2177
1951
  size: 0
2178
1952
  });
2179
- // Pooled buffer for `getDragContext()` — onMove calls this 60-120×/s.
2180
- // Inner `range` object identity is also stable (mutated in place).
2181
1953
  __publicField(this, "dragContextBuf", {
2182
1954
  size: 0,
2183
1955
  maxAxisSize: 0,
@@ -2188,13 +1960,7 @@ var BottomSheetEngine = class {
2188
1960
  __publicField(this, "persistKey");
2189
1961
  __publicField(this, "linkedSheets", []);
2190
1962
  __publicField(this, "scrollCache");
2191
- // Single LIFO stack for ALL destroy-only teardowns. Plugins (added last)
2192
- // tear down first before the infrastructure they depend on. Toggle-semantic
2193
- // listeners (focus-trap / scroll-lock / scrim tap-to-close) stay as named
2194
- // fields on their controllers.
2195
1963
  __publicField(this, "teardowns", new TeardownStack());
2196
- // Pre-computed transform template — built once per engine since `mode` is
2197
- // fixed for its lifetime. Avoids the 4-arm switch at 60-120 Hz.
2198
1964
  __publicField(this, "transformTemplate");
2199
1965
  const resolved = resolveEngineOptions(opts, {
2200
1966
  shouldApplyInertSiblings: () => {
@@ -2209,7 +1975,8 @@ var BottomSheetEngine = class {
2209
1975
  this.handle = opts.handle ?? opts.element;
2210
1976
  this.scrollContainer = opts.scrollContainer;
2211
1977
  this.backdrop = opts.backdrop;
2212
- this.screenComponent = opts.screenComponent;
1978
+ this.screenComponent = opts.scrim ?? opts.screenComponent;
1979
+ this.scrimParent = this.screenComponent?.parentElement ?? null;
2213
1980
  this.mode = resolved.mode;
2214
1981
  this.transformTemplate = buildTransformTemplate(this.mode);
2215
1982
  this.aria = new AriaSliderWriter(this.handle, this.mode);
@@ -2237,8 +2004,6 @@ var BottomSheetEngine = class {
2237
2004
  getRootEl: () => this.rootEl,
2238
2005
  applySize: (size) => this.applySize(size),
2239
2006
  getSize: () => this.size,
2240
- // Optional chain protects synthetic test paths that drive animateTo
2241
- // before attach() (gesture is constructed there).
2242
2007
  isDragging: () => this.gesture?.isDragging ?? false
2243
2008
  },
2244
2009
  resolved.animation
@@ -2343,21 +2108,6 @@ var BottomSheetEngine = class {
2343
2108
  on(event, fn) {
2344
2109
  return this.bus.on(event, fn);
2345
2110
  }
2346
- /**
2347
- * Register a plugin. `install` runs synchronously; if it returns a function,
2348
- * it's captured for teardown during `destroy()`. No-op if already destroyed
2349
- * — installing into a torn-down engine would leak listeners.
2350
- *
2351
- * Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
2352
- * so a buggy plugin can't take the engine down with it: the engine stays
2353
- * usable, sibling plugins still install, and the error surfaces in dev tools
2354
- * on the next tick instead of being swallowed. Mirrors the per-fn isolation
2355
- * model used for teardowns.
2356
- *
2357
- * Plugins that accept the second `scope` argument get transactional cleanup:
2358
- * push partial-install cleanups via `scope.add(fn)` and the engine drains
2359
- * them on throw, OR merges them into destroy-time teardowns on success.
2360
- */
2361
2111
  use(plugin) {
2362
2112
  if (this.destroyed) {
2363
2113
  console.warn(
@@ -2367,9 +2117,6 @@ var BottomSheetEngine = class {
2367
2117
  }
2368
2118
  const scoped = [];
2369
2119
  const scope = {
2370
- // typeof check — not just truthiness — so a JS-only plugin pushing a
2371
- // bogus value is silently rejected instead of queueing a confusing
2372
- // TypeError on destroy() drain.
2373
2120
  add: (fn) => {
2374
2121
  if (typeof fn === "function") scoped.push(fn);
2375
2122
  }
@@ -2398,36 +2145,13 @@ var BottomSheetEngine = class {
2398
2145
  }
2399
2146
  return this;
2400
2147
  }
2401
- /**
2402
- * Replace the linked-sheets list at runtime. Use when peer engines aren't
2403
- * available at construction time (mounted later in the tree). The wiring
2404
- * to the `open` event is independent of this list — it reads `linkedSheets`
2405
- * lazily on emit, so this setter is a simple field swap.
2406
- */
2407
2148
  setLinkedSheets(sheets) {
2408
2149
  if (this.destroyed) return;
2409
2150
  this.linkedSheets = sheets;
2410
2151
  }
2411
- /** Read-only view of the current allow-list, for cross-sheet target negotiation. */
2412
2152
  getAllowedIds() {
2413
2153
  return this.snaps.getAllowedIds().slice();
2414
2154
  }
2415
- /**
2416
- * Animate to a snap point by id. Resolves when the animation completes.
2417
- *
2418
- * Two call shapes:
2419
- * ```ts
2420
- * engine.snapTo("full"); // default velocity
2421
- * engine.snapTo("full", 0.5); // px/ms initial velocity
2422
- * engine.snapTo("full", { velocity: 0.5, signal }); // abortable
2423
- * ```
2424
- *
2425
- * When `opts.signal` is provided, calling `controller.abort()` cancels the
2426
- * in-flight tween/spring and the returned promise resolves without firing
2427
- * `snap`. The captured `AbortSignal` is checked after each await so external
2428
- * aborts ride the same cycle invariant as internal ones (resize, destroy,
2429
- * concurrent snapTo).
2430
- */
2431
2155
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2432
2156
  if (this.destroyed) return;
2433
2157
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2486,16 +2210,6 @@ var BottomSheetEngine = class {
2486
2210
  this.handleClose();
2487
2211
  }
2488
2212
  }
2489
- /**
2490
- * Drive the sheet to an arbitrary px size, bypassing snap matching.
2491
- * Useful for preview scrubs, animation chaining, or programmatic gestures.
2492
- * Animates with the configured spring/tween. Resolves when the animation
2493
- * completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
2494
- *
2495
- * Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
2496
- * nor `snap` (the sheet isn't settling to a known snap). `progress` events
2497
- * still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
2498
- */
2499
2213
  async dragTo(targetSize, velocityOrOpts = 0) {
2500
2214
  if (this.destroyed) return;
2501
2215
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2533,20 +2247,10 @@ var BottomSheetEngine = class {
2533
2247
  this.snaps.setAllowedIds(ids);
2534
2248
  this.updateAriaSlider();
2535
2249
  if (snap && ids.includes(snap)) {
2536
- void this.snapTo(
2537
- snap,
2538
- 0,
2539
- /* internal */
2540
- true
2541
- );
2250
+ void this.snapTo(snap, 0, true);
2542
2251
  } else if (!ids.includes(this.activeId)) {
2543
2252
  const fallback = ids[0];
2544
- if (fallback) void this.snapTo(
2545
- fallback,
2546
- 0,
2547
- /* internal */
2548
- true
2549
- );
2253
+ if (fallback) void this.snapTo(fallback, 0, true);
2550
2254
  }
2551
2255
  }
2552
2256
  setBackdropRange(range) {
@@ -2564,21 +2268,6 @@ var BottomSheetEngine = class {
2564
2268
  setScrimInteractive(interactive) {
2565
2269
  this.scrim.setScrimInteractive(interactive);
2566
2270
  }
2567
- /**
2568
- * Batch-update multiple scrim fields in one call. Apply order is:
2569
- * 1. `preset` — fills baseline color/blur/range/interactive.
2570
- * 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
2571
- * 3. `enabled` — flips the opacity contribution on/off.
2572
- * 4. `tapToClose` — installs/tears the click listener.
2573
- * 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
2574
- * 6. ONE `applySize` so the per-frame opacity stays consistent.
2575
- *
2576
- * Performance: collecting changes here (vs calling individual setters)
2577
- * avoids repeated `applySize` calls during a multi-field swap. Setters
2578
- * called here suppress their own internal `applySize` either by passing
2579
- * through fast-paths (mode/enabled no-op when value unchanged) or by
2580
- * being visual-only (color/blur/interactive don't influence opacity).
2581
- */
2582
2271
  setScrim(opts) {
2583
2272
  this.scrim.setScrim(opts, () => this.applySize(this.size));
2584
2273
  }
@@ -2594,11 +2283,6 @@ var BottomSheetEngine = class {
2594
2283
  setScrimOverlay(opts) {
2595
2284
  return this.scrim.setScrimOverlay(opts);
2596
2285
  }
2597
- /**
2598
- * Read-only snapshot of scrim state. Use the `setScrim*` setters to mutate.
2599
- * Stable public API — internals (e.g. cached opacity sentinels) are NOT
2600
- * exposed so consumers can't accidentally couple to ScrimController shape.
2601
- */
2602
2286
  getScrimState() {
2603
2287
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2604
2288
  }
@@ -2644,24 +2328,11 @@ var BottomSheetEngine = class {
2644
2328
  emit(event, payload) {
2645
2329
  this.bus.emit(event, payload);
2646
2330
  }
2647
- /**
2648
- * Abort the current cycle's signal and replace the controller. Returns the
2649
- * fresh signal so the caller can capture it locally before any await — when
2650
- * a future newCycle() / destroy aborts our controller, the captured signal
2651
- * flips to aborted and post-await guards short-circuit.
2652
- */
2653
2331
  newCycle() {
2654
2332
  this.currentAbort.abort();
2655
2333
  this.currentAbort = new AbortController();
2656
2334
  return this.currentAbort.signal;
2657
2335
  }
2658
- /**
2659
- * Fire `before-snap` and report whether a listener cancelled it. The
2660
- * `cancel()` callback exposed to listeners is frozen after the synchronous
2661
- * emit phase so async callers can't silently miss — they get a console
2662
- * warning. Returns `true` when cancelled so callers can early-return /
2663
- * spring-back without re-implementing the cancel/frozen pattern.
2664
- */
2665
2336
  emitBeforeSnap(target, previousId) {
2666
2337
  let cancelled = false;
2667
2338
  let frozen = false;
@@ -2695,30 +2366,9 @@ var BottomSheetEngine = class {
2695
2366
  this.snaps.setRaw(this.snapPointsRaw);
2696
2367
  this.scrim.invalidateOpacityCache();
2697
2368
  }
2698
- /**
2699
- * Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
2700
- * Engine wrapper kept so existing call sites don't all need updating;
2701
- * resolver-level memoization survives across method invocations.
2702
- */
2703
2369
  getAllowedRange() {
2704
2370
  return this.snaps.getAllowedRange();
2705
2371
  }
2706
- /**
2707
- * Write ARIA slider value attributes onto the handle so the engine itself
2708
- * is the source of truth for screen-reader announcements. Called from the
2709
- * constructor and from every code path that mutates `activeId` /
2710
- * `allowedIds`. WAI-ARIA 1.2 requires `aria-valuenow` for `role="slider"` —
2711
- * without it screen readers can't announce drag/snap progress to keyboard
2712
- * + AT users (WCAG 4.1.2 Name, Role, Value).
2713
- *
2714
- * Engine does NOT set `role="slider"` itself — that's the adapter's or
2715
- * consumer markup's responsibility. We only decorate with dynamic values.
2716
- *
2717
- * Empty allowed-list is malformed input (settable via `setAllowed([])`);
2718
- * short-circuit to remove ARIA value attributes so screen readers see a
2719
- * no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
2720
- * requires `valuemax > valuemin` for `role="slider"`).
2721
- */
2722
2372
  updateAriaSlider() {
2723
2373
  this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
2724
2374
  }
@@ -2743,11 +2393,6 @@ var BottomSheetEngine = class {
2743
2393
  element: this.element,
2744
2394
  mode: this.mode,
2745
2395
  getRoot: () => this.rootEl,
2746
- // Single coalesced reader — narrows the DI surface and removes 3 getter
2747
- // dispatches per onMove frame at 60-120Hz. Returns a SHARED buffer
2748
- // (this.dragContextBuf) — see field JSDoc for the retention contract.
2749
- // Inner `range` object is mutated in place too so callers caching its
2750
- // identity between frames stay valid.
2751
2396
  getDragContext: () => {
2752
2397
  const buf = this.dragContextBuf;
2753
2398
  const r = this.getAllowedRange();
@@ -2913,7 +2558,6 @@ var BottomSheetEngine = class {
2913
2558
  }
2914
2559
  });
2915
2560
  }
2916
- /** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
2917
2561
  haptic() {
2918
2562
  if (typeof navigator !== "undefined" && "vibrate" in navigator) {
2919
2563
  try {
@@ -2929,6 +2573,9 @@ var BottomSheetEngine = class {
2929
2573
  style.transform = this.transformTemplate(offset);
2930
2574
  if (this.sizeWriteSentinel.shouldWrite(size, SIZE_WRITE_EPSILON)) {
2931
2575
  style.setProperty("--bs-size", `${size}px`);
2576
+ if (this.scrimParent) {
2577
+ this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2578
+ }
2932
2579
  }
2933
2580
  const progress = this.computeProgress(size);
2934
2581
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -3020,14 +2667,14 @@ function createBottomSheet(opts) {
3020
2667
  destroy: () => {
3021
2668
  engine?.destroy();
3022
2669
  engine = null;
3023
- }
2670
+ },
2671
+ getEngine: () => engine
3024
2672
  };
3025
2673
  }
3026
2674
  var root_1 = $2__namespace.from_html(`<div class="bs-backdrop" aria-hidden="true"></div>`);
3027
- var root_2 = $2__namespace.from_html(`<div class="bs-screen"><!></div>`);
3028
2675
  var root_3 = $2__namespace.from_html(`<div class="bs-button-slot" data-side="left"><!></div>`);
3029
2676
  var root_4 = $2__namespace.from_html(`<div class="bs-button-slot" data-side="right"><!></div>`);
3030
- var root = $2__namespace.from_html(`<div class="bs-root"><!> <!> <section class="bs-sheet" role="dialog"><!> <!> <div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0"><!></div> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section></div>`);
2677
+ var root = $2__namespace.from_html(`<div class="bs-root"><!> <div class="bs-screen"><!></div> <section class="bs-sheet" role="dialog"><!> <!> <div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet"><!></div> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section></div>`);
3031
2678
  function BottomSheet($$anchor, $$props) {
3032
2679
  $2__namespace.push($$props, true);
3033
2680
  let mode = $2__namespace.prop($$props, "mode", 3, "bottom"), animation = $2__namespace.prop($$props, "animation", 3, "spring"), focusTrap = $2__namespace.prop($$props, "focusTrap", 3, false), closeOnEscape = $2__namespace.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2__namespace.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2__namespace.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2__namespace.prop($$props, "rubberBand", 3, true), backdrop = $2__namespace.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2__namespace.prop($$props, "closeOnBackdrop", 3, true), ariaLabel = $2__namespace.prop($$props, "ariaLabel", 3, "Bottom sheet");
@@ -3051,7 +2698,7 @@ function BottomSheet($$anchor, $$props) {
3051
2698
  handle: $2__namespace.get(handleEl),
3052
2699
  scrollContainer: $2__namespace.get(contentEl),
3053
2700
  backdrop: $2__namespace.get(backdropEl),
3054
- screenComponent: $2__namespace.get(screenEl),
2701
+ scrim: $2__namespace.get(screenEl),
3055
2702
  mode: mode(),
3056
2703
  snapPoints: $$props.snapPoints,
3057
2704
  allowed: $$props.allowed,
@@ -3088,8 +2735,26 @@ function BottomSheet($$anchor, $$props) {
3088
2735
  const open = (id) => engine?.open(id) ?? Promise.resolve();
3089
2736
  const close = () => engine?.close() ?? Promise.resolve();
3090
2737
  const setAllowed = (ids, snap) => engine?.setAllowed(ids, snap);
2738
+ const setSnapPoints = (points, nextAllowed) => engine?.setSnapPoints(points, nextAllowed);
2739
+ const setScrim = (opts) => engine?.setScrim(opts);
2740
+ const setScrimOverlay = (opts) => engine?.setScrimOverlay(opts) ?? (() => {
2741
+ });
3091
2742
  const getState = () => engine?.state ?? $2__namespace.get(viewState);
3092
- var $$exports = { snapTo, open, close, setAllowed, getState };
2743
+ const getEngine = () => engine;
2744
+ let allowedIds = $2__namespace.derived(() => $$props.allowed && $$props.allowed.length > 0 ? $$props.allowed : $$props.snapPoints.map((s) => s.id));
2745
+ let activeIdx = $2__namespace.derived(() => $2__namespace.get(allowedIds).indexOf($2__namespace.get(viewState).activeId));
2746
+ let isVerticalAxis = $2__namespace.derived(() => mode() === "bottom" || mode() === "top");
2747
+ var $$exports = {
2748
+ snapTo,
2749
+ open,
2750
+ close,
2751
+ setAllowed,
2752
+ setSnapPoints,
2753
+ setScrim,
2754
+ setScrimOverlay,
2755
+ getState,
2756
+ getEngine
2757
+ };
3093
2758
  var div = root();
3094
2759
  var node = $2__namespace.child(div);
3095
2760
  {
@@ -3105,21 +2770,22 @@ function BottomSheet($$anchor, $$props) {
3105
2770
  if (backdrop()) $$render(consequent);
3106
2771
  });
3107
2772
  }
3108
- var node_1 = $2__namespace.sibling(node, 2);
2773
+ var div_2 = $2__namespace.sibling(node, 2);
2774
+ var node_1 = $2__namespace.child(div_2);
3109
2775
  {
3110
2776
  var consequent_1 = ($$anchor2) => {
3111
- var div_2 = root_2();
3112
- var node_2 = $2__namespace.child(div_2);
2777
+ var fragment = $2__namespace.comment();
2778
+ var node_2 = $2__namespace.first_child(fragment);
3113
2779
  $2__namespace.snippet(node_2, () => $$props.screen);
3114
- $2__namespace.reset(div_2);
3115
- $2__namespace.bind_this(div_2, ($$value) => $2__namespace.set(screenEl, $$value), () => $2__namespace.get(screenEl));
3116
- $2__namespace.append($$anchor2, div_2);
2780
+ $2__namespace.append($$anchor2, fragment);
3117
2781
  };
3118
2782
  $2__namespace.if(node_1, ($$render) => {
3119
2783
  if ($$props.screen) $$render(consequent_1);
3120
2784
  });
3121
2785
  }
3122
- var section = $2__namespace.sibling(node_1, 2);
2786
+ $2__namespace.reset(div_2);
2787
+ $2__namespace.bind_this(div_2, ($$value) => $2__namespace.set(screenEl, $$value), () => $2__namespace.get(screenEl));
2788
+ var section = $2__namespace.sibling(div_2, 2);
3123
2789
  var node_3 = $2__namespace.child(section);
3124
2790
  {
3125
2791
  var consequent_2 = ($$anchor2) => {
@@ -3147,13 +2813,14 @@ function BottomSheet($$anchor, $$props) {
3147
2813
  });
3148
2814
  }
3149
2815
  var div_5 = $2__namespace.sibling(node_5, 2);
2816
+ $2__namespace.set_attribute(div_5, "aria-valuemin", 0);
3150
2817
  var node_7 = $2__namespace.child(div_5);
3151
2818
  {
3152
2819
  var consequent_4 = ($$anchor2) => {
3153
- var fragment = $2__namespace.comment();
3154
- var node_8 = $2__namespace.first_child(fragment);
2820
+ var fragment_1 = $2__namespace.comment();
2821
+ var node_8 = $2__namespace.first_child(fragment_1);
3155
2822
  $2__namespace.snippet(node_8, () => $$props.header, () => $2__namespace.get(viewState));
3156
- $2__namespace.append($$anchor2, fragment);
2823
+ $2__namespace.append($$anchor2, fragment_1);
3157
2824
  };
3158
2825
  $2__namespace.if(node_7, ($$render) => {
3159
2826
  if ($$props.header) $$render(consequent_4);
@@ -3165,10 +2832,10 @@ function BottomSheet($$anchor, $$props) {
3165
2832
  var node_9 = $2__namespace.child(div_6);
3166
2833
  {
3167
2834
  var consequent_5 = ($$anchor2) => {
3168
- var fragment_1 = $2__namespace.comment();
3169
- var node_10 = $2__namespace.first_child(fragment_1);
2835
+ var fragment_2 = $2__namespace.comment();
2836
+ var node_10 = $2__namespace.first_child(fragment_2);
3170
2837
  $2__namespace.snippet(node_10, () => $$props.children);
3171
- $2__namespace.append($$anchor2, fragment_1);
2838
+ $2__namespace.append($$anchor2, fragment_2);
3172
2839
  };
3173
2840
  $2__namespace.if(node_9, ($$render) => {
3174
2841
  if ($$props.children) $$render(consequent_5);
@@ -3182,13 +2849,23 @@ function BottomSheet($$anchor, $$props) {
3182
2849
  $2__namespace.reset(section);
3183
2850
  $2__namespace.bind_this(section, ($$value) => $2__namespace.set(sheetEl, $$value), () => $2__namespace.get(sheetEl));
3184
2851
  $2__namespace.reset(div);
3185
- $2__namespace.template_effect(() => {
3186
- $2__namespace.set_attribute(section, "data-mode", mode());
3187
- $2__namespace.set_attribute(section, "data-active", $2__namespace.get(viewState).activeId);
3188
- $2__namespace.set_attribute(section, "aria-modal", focusTrap() ? "true" : void 0);
3189
- $2__namespace.set_attribute(section, "aria-label", ariaLabel());
3190
- $2__namespace.set_text(text, $2__namespace.get(viewState).activeId);
3191
- });
2852
+ $2__namespace.template_effect(
2853
+ ($0, $1) => {
2854
+ $2__namespace.set_attribute(section, "data-mode", mode());
2855
+ $2__namespace.set_attribute(section, "data-active", $2__namespace.get(viewState).activeId);
2856
+ $2__namespace.set_attribute(section, "aria-modal", focusTrap() ? "true" : void 0);
2857
+ $2__namespace.set_attribute(section, "aria-label", ariaLabel());
2858
+ $2__namespace.set_attribute(div_5, "aria-orientation", $2__namespace.get(isVerticalAxis) ? "vertical" : "horizontal");
2859
+ $2__namespace.set_attribute(div_5, "aria-valuemax", $0);
2860
+ $2__namespace.set_attribute(div_5, "aria-valuenow", $1);
2861
+ $2__namespace.set_attribute(div_5, "aria-valuetext", $2__namespace.get(viewState).activeId);
2862
+ $2__namespace.set_text(text, $2__namespace.get(viewState).activeId);
2863
+ },
2864
+ [
2865
+ () => Math.max(0, $2__namespace.get(allowedIds).length - 1),
2866
+ () => Math.max(0, $2__namespace.get(activeIdx))
2867
+ ]
2868
+ );
3192
2869
  $2__namespace.append($$anchor, div);
3193
2870
  return $2__namespace.pop($$exports);
3194
2871
  }