@surdeddd/bottom-sheet 0.2.0 → 0.3.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 (49) hide show
  1. package/README.md +51 -21
  2. package/dist/{BottomSheetEngine-CIT9Htti.d.cts → BottomSheetEngine-CAymrH-P.d.cts} +67 -1
  3. package/dist/{BottomSheetEngine-Df9MwBbm.d.ts → BottomSheetEngine-DcqcuudN.d.ts} +67 -1
  4. package/dist/element.cjs +699 -116
  5. package/dist/element.d.cts +7 -2
  6. package/dist/element.d.ts +7 -2
  7. package/dist/element.iife.global.js +2 -2
  8. package/dist/element.js +699 -116
  9. package/dist/index.cjs +715 -145
  10. package/dist/index.d.cts +17 -3
  11. package/dist/index.d.ts +17 -3
  12. package/dist/index.js +713 -146
  13. package/dist/integrations/formik.d.cts +1 -1
  14. package/dist/integrations/formik.d.ts +1 -1
  15. package/dist/integrations/react-hook-form.d.cts +1 -1
  16. package/dist/integrations/react-hook-form.d.ts +1 -1
  17. package/dist/overlay.cjs +168 -35
  18. package/dist/overlay.d.cts +13 -0
  19. package/dist/overlay.d.ts +13 -0
  20. package/dist/overlay.js +168 -35
  21. package/dist/preact.d.cts +1 -1
  22. package/dist/preact.d.ts +1 -1
  23. package/dist/qwik.cjs +619 -119
  24. package/dist/qwik.d.cts +2 -2
  25. package/dist/qwik.d.ts +2 -2
  26. package/dist/qwik.js +620 -120
  27. package/dist/react.cjs +821 -191
  28. package/dist/react.d.cts +20 -3
  29. package/dist/react.d.ts +20 -3
  30. package/dist/react.js +821 -191
  31. package/dist/solid.cjs +656 -161
  32. package/dist/solid.d.cts +263 -4
  33. package/dist/solid.d.ts +263 -4
  34. package/dist/solid.js +657 -162
  35. package/dist/solid.source.jsx +3215 -0
  36. package/dist/styles.css +10 -10
  37. package/dist/svelte-core.cjs +3149 -0
  38. package/dist/svelte-core.js +3147 -0
  39. package/dist/svelte-src/BottomSheet.svelte +210 -0
  40. package/dist/svelte-src/index.js +2 -0
  41. package/dist/svelte.cjs +618 -124
  42. package/dist/svelte.d.cts +100 -0
  43. package/dist/svelte.d.ts +7 -3
  44. package/dist/svelte.js +619 -125
  45. package/dist/vue.cjs +612 -119
  46. package/dist/vue.d.cts +78 -0
  47. package/dist/vue.d.ts +6 -2
  48. package/dist/vue.js +612 -119
  49. package/package.json +103 -43
package/dist/react.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { forwardRef, useRef, useState, useEffect, useImperativeHandle, useMemo, useCallback, useSyncExternalStore, useLayoutEffect } from 'react';
2
+ import { createPortal } from 'react-dom';
2
3
  import { jsxs, jsx } from 'react/jsx-runtime';
3
4
 
4
5
  var __defProp = Object.defineProperty;
@@ -23,6 +24,18 @@ var SheetStack = class {
23
24
  this.entries.splice(idx, 1);
24
25
  this.recompute();
25
26
  }
27
+ promote(id) {
28
+ const idx = this.entries.findIndex((e) => e.id === id);
29
+ if (idx === -1) return;
30
+ if (idx !== this.entries.length - 1) {
31
+ const [entry] = this.entries.splice(idx, 1);
32
+ this.entries.push(entry);
33
+ }
34
+ this.recompute();
35
+ }
36
+ update() {
37
+ this.recompute();
38
+ }
26
39
  size() {
27
40
  return this.entries.length;
28
41
  }
@@ -30,9 +43,24 @@ var SheetStack = class {
30
43
  this.entries = [];
31
44
  }
32
45
  recompute() {
46
+ let top = null;
47
+ const open = [];
48
+ for (const entry of this.entries) {
49
+ if (!entry.isOpen || entry.isOpen()) {
50
+ top = entry;
51
+ open.push(entry);
52
+ }
53
+ }
54
+ if (!top && this.entries.length > 0) {
55
+ top = this.entries[this.entries.length - 1];
56
+ }
33
57
  this.entries.forEach((entry, i) => {
34
58
  entry.setZIndex(BASE_Z + i * STEP);
35
- entry.setIsTop(i === this.entries.length - 1);
59
+ entry.setIsTop(entry === top);
60
+ if (entry.setDepth) {
61
+ const openIdx = open.indexOf(entry);
62
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
63
+ }
36
64
  });
37
65
  }
38
66
  };
@@ -323,14 +351,17 @@ function installAutoCollapse(deps) {
323
351
  const fire = () => {
324
352
  if (deps.isDestroyed()) return;
325
353
  if (deps.isDragging()) return;
326
- const allowed = deps.getAllowedIds();
327
- const target = allowed.find((id) => {
354
+ const active = deps.resolveSnap(deps.getActiveId());
355
+ if (!active || active.size === 0) return;
356
+ let target = null;
357
+ for (const id of deps.getAllowedIds()) {
328
358
  const snap = deps.resolveSnap(id);
329
- return snap !== null && snap.size > 0;
330
- });
359
+ if (!snap || snap.size <= 0) continue;
360
+ if (!target || snap.size < target.size) target = snap;
361
+ }
331
362
  if (!target) return;
332
- if (target === deps.getActiveId()) return;
333
- deps.snapTo(target);
363
+ if (target.id === active.id || target.size >= active.size) return;
364
+ deps.snapTo(target.id);
334
365
  };
335
366
  const reset = () => {
336
367
  clearTimer();
@@ -341,6 +372,7 @@ function installAutoCollapse(deps) {
341
372
  }, ms);
342
373
  };
343
374
  const unsubscribe = deps.on("snap", () => reset());
375
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
344
376
  return () => {
345
377
  clearTimer();
346
378
  unsubscribe();
@@ -353,6 +385,7 @@ function notifyLinkedSheets(sheets, self) {
353
385
  for (const linked of sheets) {
354
386
  if (linked === self) continue;
355
387
  try {
388
+ if (linked.state.size === 0) continue;
356
389
  const ids = linked.getAllowedIds();
357
390
  const target = ids.find((id) => id !== "closed");
358
391
  if (target && target !== linked.state.activeId) {
@@ -399,7 +432,7 @@ function installRoute(deps) {
399
432
  const onPop = (event) => {
400
433
  if (deps.isDestroyed()) return;
401
434
  const state = event.state;
402
- if (state && state.__bsRouted === target && deps.getSize() > 0) {
435
+ if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
403
436
  routePushed = false;
404
437
  void deps.close();
405
438
  }
@@ -553,13 +586,17 @@ function installResizeObserver(deps) {
553
586
  if (viewportSize2 > 0 && viewportSize2 < max) {
554
587
  deps.setMaxAxisSize(viewportSize2);
555
588
  }
589
+ const wasAnimating = deps.isAnimating?.() ?? false;
556
590
  deps.cancelInFlight();
557
591
  deps.newCycle();
558
592
  const current = deps.resolveActiveSnap();
559
593
  const newMax = deps.getMaxAxisSize();
560
594
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
561
595
  deps.setSize(targetSize);
562
- if (current && !deps.isDragging()) deps.applySize(targetSize);
596
+ if (current && !deps.isDragging()) {
597
+ deps.applySize(targetSize);
598
+ if (wasAnimating) deps.resyncAfterCancel?.();
599
+ }
563
600
  };
564
601
  let resizeObserver = null;
565
602
  if (typeof ResizeObserver !== "undefined") {
@@ -674,6 +711,85 @@ function devWarn(...args) {
674
711
  }
675
712
  }
676
713
 
714
+ // src/core/primitives/overlay-position.ts
715
+ function resolveSheetAnchoredStyle(mode, position, inset) {
716
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
717
+ switch (mode) {
718
+ case "bottom":
719
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
720
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
721
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
722
+ case "top":
723
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
724
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
725
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
726
+ case "left":
727
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
728
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
729
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
730
+ case "right":
731
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
732
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
733
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
734
+ }
735
+ }
736
+ function applyOverlayPosition(ws, mode, position, inset) {
737
+ switch (position) {
738
+ case "top-left":
739
+ ws.top = inset;
740
+ ws.left = inset;
741
+ break;
742
+ case "top-center":
743
+ ws.top = inset;
744
+ ws.left = "50%";
745
+ ws.transform = "translateX(-50%)";
746
+ break;
747
+ case "top-right":
748
+ ws.top = inset;
749
+ ws.right = inset;
750
+ break;
751
+ case "center-left":
752
+ ws.top = "50%";
753
+ ws.left = inset;
754
+ ws.transform = "translateY(-50%)";
755
+ break;
756
+ case "center":
757
+ ws.top = "50%";
758
+ ws.left = "50%";
759
+ ws.transform = "translate(-50%, -50%)";
760
+ break;
761
+ case "center-right":
762
+ ws.top = "50%";
763
+ ws.right = inset;
764
+ ws.transform = "translateY(-50%)";
765
+ break;
766
+ case "bottom-left":
767
+ ws.bottom = inset;
768
+ ws.left = inset;
769
+ break;
770
+ case "bottom-center":
771
+ ws.bottom = inset;
772
+ ws.left = "50%";
773
+ ws.transform = "translateX(-50%)";
774
+ break;
775
+ case "bottom-right":
776
+ ws.bottom = inset;
777
+ ws.right = inset;
778
+ break;
779
+ case "sheet-top-left":
780
+ case "sheet-top-center":
781
+ case "sheet-top-right": {
782
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
783
+ if (style.top !== void 0) ws.top = style.top;
784
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
785
+ if (style.left !== void 0) ws.left = style.left;
786
+ if (style.right !== void 0) ws.right = style.right;
787
+ if (style.transform !== void 0) ws.transform = style.transform;
788
+ break;
789
+ }
790
+ }
791
+ }
792
+
677
793
  // src/core/controllers/scrim-controller.ts
678
794
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
679
795
  var ScrimController = class {
@@ -686,6 +802,8 @@ var ScrimController = class {
686
802
  __publicField(this, "getAllowedIds");
687
803
  __publicField(this, "getResolvedSnaps");
688
804
  __publicField(this, "snapToFn");
805
+ __publicField(this, "closeFn");
806
+ __publicField(this, "scrimInteractive", false);
689
807
  __publicField(this, "scrimMode");
690
808
  __publicField(this, "backdropRange");
691
809
  __publicField(this, "screenRange");
@@ -706,6 +824,7 @@ var ScrimController = class {
706
824
  this.getAllowedIds = deps.getAllowedIds;
707
825
  this.getResolvedSnaps = deps.getResolvedSnaps;
708
826
  this.snapToFn = deps.snapTo;
827
+ this.closeFn = deps.close;
709
828
  this.scrimMode = opts.scrimMode ?? "full";
710
829
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
711
830
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
@@ -847,11 +966,15 @@ var ScrimController = class {
847
966
  }
848
967
  const onClick = () => {
849
968
  if (this.isDestroyed()) return;
969
+ if (this.closeFn) {
970
+ this.closeFn();
971
+ return;
972
+ }
850
973
  const allowed = this.getAllowedIds();
851
974
  const snaps = this.getResolvedSnaps();
852
975
  const fallback = allowed.find((id) => {
853
976
  const snap = findById(id, snaps);
854
- return snap !== null && snap.size > 0;
977
+ return snap !== null && snap.size === 0;
855
978
  });
856
979
  if (fallback) this.snapToFn(fallback);
857
980
  };
@@ -868,6 +991,7 @@ var ScrimController = class {
868
991
  if (this.isDestroyed()) return;
869
992
  if (enabled === this.scrimEnabled) return;
870
993
  this.scrimEnabled = enabled;
994
+ this.invalidateOpacityCache();
871
995
  if (!enabled) {
872
996
  this.savedScrimRanges = {
873
997
  screen: this.screenRange,
@@ -891,7 +1015,12 @@ var ScrimController = class {
891
1015
  blur: cfg.blur ?? null,
892
1016
  interactive: cfg.interactive
893
1017
  });
894
- this.screenRange = cfg.range;
1018
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1019
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1020
+ } else {
1021
+ this.screenRange = cfg.range;
1022
+ this.backdropRange = cfg.range;
1023
+ }
895
1024
  }
896
1025
  const noop = () => {
897
1026
  };
@@ -941,59 +1070,7 @@ var ScrimController = class {
941
1070
  wrapper.className = "bs-scrim-overlay";
942
1071
  const ws = wrapper.style;
943
1072
  ws.position = "absolute";
944
- switch (position) {
945
- case "top-left":
946
- ws.top = inset;
947
- ws.left = inset;
948
- break;
949
- case "top-center":
950
- ws.top = inset;
951
- ws.left = "50%";
952
- ws.transform = "translateX(-50%)";
953
- break;
954
- case "top-right":
955
- ws.top = inset;
956
- ws.right = inset;
957
- break;
958
- case "center-left":
959
- ws.top = "50%";
960
- ws.left = inset;
961
- ws.transform = "translateY(-50%)";
962
- break;
963
- case "center":
964
- ws.top = "50%";
965
- ws.left = "50%";
966
- ws.transform = "translate(-50%, -50%)";
967
- break;
968
- case "center-right":
969
- ws.top = "50%";
970
- ws.right = inset;
971
- ws.transform = "translateY(-50%)";
972
- break;
973
- case "bottom-left":
974
- ws.bottom = inset;
975
- ws.left = inset;
976
- break;
977
- case "bottom-center":
978
- ws.bottom = inset;
979
- ws.left = "50%";
980
- ws.transform = "translateX(-50%)";
981
- break;
982
- case "bottom-right":
983
- ws.bottom = inset;
984
- ws.right = inset;
985
- break;
986
- case "sheet-top-left":
987
- case "sheet-top-center":
988
- case "sheet-top-right":
989
- applySheetAnchoredPosition(
990
- ws,
991
- this.mode,
992
- position,
993
- inset
994
- );
995
- break;
996
- }
1073
+ applyOverlayPosition(ws, this.mode, position, inset);
997
1074
  if (interactive) {
998
1075
  ws.pointerEvents = "auto";
999
1076
  }
@@ -1052,6 +1129,7 @@ var ScrimController = class {
1052
1129
  }
1053
1130
  }
1054
1131
  if (opts.interactive !== void 0) {
1132
+ this.scrimInteractive = opts.interactive;
1055
1133
  s.pointerEvents = opts.interactive ? "auto" : "none";
1056
1134
  }
1057
1135
  }
@@ -1066,38 +1144,9 @@ var ScrimController = class {
1066
1144
  const s = this.screenComponent.style;
1067
1145
  s.position = "fixed";
1068
1146
  s.inset = insetByMode[this.mode];
1069
- s.pointerEvents = "none";
1147
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1070
1148
  }
1071
1149
  };
1072
- function resolveSheetAnchoredStyle(mode, position, inset) {
1073
- const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1074
- switch (mode) {
1075
- case "bottom":
1076
- if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1077
- if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1078
- return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1079
- case "top":
1080
- if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1081
- if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1082
- return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1083
- case "left":
1084
- if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1085
- if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1086
- return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1087
- case "right":
1088
- if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1089
- if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1090
- return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1091
- }
1092
- }
1093
- function applySheetAnchoredPosition(ws, mode, position, inset) {
1094
- const style = resolveSheetAnchoredStyle(mode, position, inset);
1095
- if (style.top !== void 0) ws.top = style.top;
1096
- if (style.bottom !== void 0) ws.bottom = style.bottom;
1097
- if (style.left !== void 0) ws.left = style.left;
1098
- if (style.right !== void 0) ws.right = style.right;
1099
- if (style.transform !== void 0) ws.transform = style.transform;
1100
- }
1101
1150
 
1102
1151
  // src/core/animation/animation.ts
1103
1152
  var easeOutBack = (t) => {
@@ -1149,6 +1198,10 @@ var tween = ({
1149
1198
  promise
1150
1199
  };
1151
1200
  };
1201
+ var prefersReducedMotion = () => {
1202
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1203
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1204
+ };
1152
1205
 
1153
1206
  // src/core/animation/spring.ts
1154
1207
  var DEFAULT_SPRING = {
@@ -1179,14 +1232,20 @@ var runSpring = ({
1179
1232
  if (cancelled) return;
1180
1233
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1181
1234
  lastTime = now;
1182
- const subDt = Math.min(dt, 1 / 240);
1183
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1235
+ const steps = Math.max(1, Math.ceil(dt * 240));
1236
+ const subDt = dt / steps;
1237
+ let stepsLeft = steps;
1184
1238
  while (stepsLeft-- > 0) {
1185
1239
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1186
1240
  const accel = force / cfg.mass;
1187
1241
  v += accel * subDt;
1188
1242
  x += v * subDt;
1189
1243
  }
1244
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1245
+ onUpdate(to, 0);
1246
+ resolve();
1247
+ return;
1248
+ }
1190
1249
  onUpdate(x, v);
1191
1250
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1192
1251
  if (isAtRest) {
@@ -1361,9 +1420,13 @@ var isVisible = (el) => {
1361
1420
  var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1362
1421
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1363
1422
  );
1423
+ var trapStack = [];
1364
1424
  var installFocusTrap = (container, options = {}) => {
1365
1425
  if (typeof document === "undefined") return () => {
1366
1426
  };
1427
+ const token = {};
1428
+ trapStack.push(token);
1429
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1367
1430
  const previouslyFocused = document.activeElement;
1368
1431
  const focusInitial = () => {
1369
1432
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1371,6 +1434,7 @@ var installFocusTrap = (container, options = {}) => {
1371
1434
  };
1372
1435
  focusInitial();
1373
1436
  const handleKey = (e) => {
1437
+ if (!isActive()) return;
1374
1438
  if (e.key === "Escape" && options.onEscape) {
1375
1439
  options.onEscape();
1376
1440
  return;
@@ -1393,6 +1457,7 @@ var installFocusTrap = (container, options = {}) => {
1393
1457
  }
1394
1458
  };
1395
1459
  const handleFocusIn = (e) => {
1460
+ if (!isActive()) return;
1396
1461
  const target = e.target;
1397
1462
  if (target && !container.contains(target)) {
1398
1463
  const list = focusables(container);
@@ -1405,6 +1470,8 @@ var installFocusTrap = (container, options = {}) => {
1405
1470
  return () => {
1406
1471
  if (released) return;
1407
1472
  released = true;
1473
+ const idx = trapStack.indexOf(token);
1474
+ if (idx !== -1) trapStack.splice(idx, 1);
1408
1475
  document.removeEventListener("keydown", handleKey, true);
1409
1476
  document.removeEventListener("focusin", handleFocusIn, true);
1410
1477
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1460,11 +1527,17 @@ function createInertSiblings(rootProvider) {
1460
1527
  return {
1461
1528
  apply() {
1462
1529
  if (typeof document === "undefined") return;
1463
- const root = rootProvider();
1464
- const parent = root.parentElement ?? document.body;
1530
+ let top = rootProvider();
1531
+ while (top.parentElement && top.parentElement !== document.body) {
1532
+ top = top.parentElement;
1533
+ }
1534
+ const parent = top.parentElement ?? document.body;
1465
1535
  for (const child of Array.from(parent.children)) {
1466
- if (child === root) continue;
1536
+ if (child === top) continue;
1467
1537
  if (child.hasAttribute("inert")) continue;
1538
+ if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
1539
+ continue;
1540
+ }
1468
1541
  child.setAttribute("inert", "");
1469
1542
  tracked.push(child);
1470
1543
  }
@@ -1480,7 +1553,6 @@ function createInertSiblings(rootProvider) {
1480
1553
  var LifecycleController = class {
1481
1554
  constructor(deps, opts) {
1482
1555
  __publicField(this, "element");
1483
- __publicField(this, "closeFn");
1484
1556
  __publicField(this, "focusTrapEnabled");
1485
1557
  __publicField(this, "initialFocus");
1486
1558
  __publicField(this, "closeOnEscape");
@@ -1494,7 +1566,6 @@ var LifecycleController = class {
1494
1566
  __publicField(this, "installed", false);
1495
1567
  __publicField(this, "destroyed", false);
1496
1568
  this.element = deps.element;
1497
- this.closeFn = deps.close;
1498
1569
  this.focusTrapEnabled = opts.focusTrap ?? false;
1499
1570
  this.initialFocus = opts.initialFocus;
1500
1571
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1504,6 +1575,9 @@ var LifecycleController = class {
1504
1575
  this.returnFocus = opts.returnFocus;
1505
1576
  this.inertSiblings = createInertSiblings(() => this.element);
1506
1577
  }
1578
+ get isInstalled() {
1579
+ return this.installed;
1580
+ }
1507
1581
  install() {
1508
1582
  if (this.destroyed) return;
1509
1583
  this.installed = true;
@@ -1512,8 +1586,7 @@ var LifecycleController = class {
1512
1586
  }
1513
1587
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1514
1588
  this.releaseFocusTrap = installFocusTrap(this.element, {
1515
- initialFocus: this.initialFocus,
1516
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1589
+ initialFocus: this.initialFocus
1517
1590
  });
1518
1591
  }
1519
1592
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1667,10 +1740,11 @@ var installGestures = (handle, mode, callbacks) => {
1667
1740
  };
1668
1741
 
1669
1742
  // src/core/primitives/rubber-band.ts
1670
- var RUBBER_BAND_FACTOR = 0.55;
1671
1743
  function rubberBand(overshoot, maxAxisSize) {
1672
1744
  const cap = Math.min(maxAxisSize * 0.15, 80);
1673
- return Math.sign(overshoot) * Math.pow(Math.abs(overshoot), RUBBER_BAND_FACTOR) * Math.min(6, cap / Math.max(Math.abs(overshoot), 1));
1745
+ if (cap <= 0) return 0;
1746
+ const abs = Math.abs(overshoot);
1747
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1674
1748
  }
1675
1749
 
1676
1750
  // src/core/features/soft-keyboard.ts
@@ -1755,6 +1829,10 @@ var GestureController = class {
1755
1829
  this.isDragging_ = false;
1756
1830
  this.keyboardDismissed = false;
1757
1831
  this.deps.getRoot()?.removeAttribute("data-dragging");
1832
+ this.deps.emit("dragend", {
1833
+ size: this.deps.getDragContext().size,
1834
+ velocity: 0
1835
+ });
1758
1836
  const restoreTo = this.dragStartSize;
1759
1837
  void this.deps.animateTo(restoreTo, 0);
1760
1838
  }
@@ -1828,6 +1906,287 @@ function createScrollCache(deps) {
1828
1906
  };
1829
1907
  }
1830
1908
 
1909
+ // src/core/primitives/anchor-animations.ts
1910
+ var DEFAULT_DURATION3 = 200;
1911
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1912
+ var PRESET_FRAMES = {
1913
+ fade: {
1914
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1915
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1916
+ },
1917
+ scale: {
1918
+ enter: [
1919
+ { opacity: 0, transform: "scale(0.85)" },
1920
+ { opacity: 1, transform: "scale(1)" }
1921
+ ],
1922
+ exit: [
1923
+ { opacity: 1, transform: "scale(1)" },
1924
+ { opacity: 0, transform: "scale(0.85)" }
1925
+ ]
1926
+ },
1927
+ slide: {
1928
+ enter: [
1929
+ { opacity: 0, transform: "translateY(10px)" },
1930
+ { opacity: 1, transform: "translateY(0)" }
1931
+ ],
1932
+ exit: [
1933
+ { opacity: 1, transform: "translateY(0)" },
1934
+ { opacity: 0, transform: "translateY(10px)" }
1935
+ ]
1936
+ },
1937
+ pop: {
1938
+ enter: [
1939
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
1940
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
1941
+ { opacity: 1, transform: "scale(1)", offset: 1 }
1942
+ ],
1943
+ exit: [
1944
+ { opacity: 1, transform: "scale(1)" },
1945
+ { opacity: 0, transform: "scale(0.6)" }
1946
+ ]
1947
+ }
1948
+ };
1949
+ var INSTANT = {
1950
+ finished: Promise.resolve(),
1951
+ cancel: () => {
1952
+ }
1953
+ };
1954
+ function resolveTransitionFrames(spec, phase) {
1955
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
1956
+ if (spec === "none") return fallback;
1957
+ if (spec === void 0 || typeof spec === "string") {
1958
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
1959
+ return {
1960
+ frames: preset2[phase],
1961
+ duration: DEFAULT_DURATION3,
1962
+ easing: DEFAULT_EASING
1963
+ };
1964
+ }
1965
+ const custom = phase === "enter" ? spec.enter : spec.exit;
1966
+ if (custom) {
1967
+ return {
1968
+ frames: custom,
1969
+ duration: spec.duration ?? DEFAULT_DURATION3,
1970
+ easing: spec.easing ?? DEFAULT_EASING
1971
+ };
1972
+ }
1973
+ if (spec.preset === "none") return fallback;
1974
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
1975
+ return {
1976
+ frames: preset[phase],
1977
+ duration: spec.duration ?? DEFAULT_DURATION3,
1978
+ easing: spec.easing ?? DEFAULT_EASING
1979
+ };
1980
+ }
1981
+ function runAnchorTransition(el, spec, phase) {
1982
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
1983
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
1984
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
1985
+ if (!frames || duration <= 0) return INSTANT;
1986
+ if (typeof el.animate !== "function") return INSTANT;
1987
+ const animation = el.animate(frames, {
1988
+ duration,
1989
+ easing,
1990
+ fill: "both"
1991
+ });
1992
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
1993
+ return {
1994
+ finished,
1995
+ cancel: () => {
1996
+ try {
1997
+ animation.cancel();
1998
+ } catch {
1999
+ return;
2000
+ }
2001
+ }
2002
+ };
2003
+ }
2004
+
2005
+ // src/core/features/sheet-anchors.ts
2006
+ function attachAnchor(deps, opts) {
2007
+ const doc = deps.host.ownerDocument;
2008
+ const wrapper = doc.createElement("div");
2009
+ wrapper.className = "bs-anchor";
2010
+ const interactive = opts.interactive ?? true;
2011
+ const position = opts.position ?? "sheet-top-right";
2012
+ const isDock = position === "dock-bottom" || position === "dock-top";
2013
+ const ws = wrapper.style;
2014
+ ws.position = "fixed";
2015
+ if (isDock) {
2016
+ ws.left = "0";
2017
+ ws.right = "0";
2018
+ if (position === "dock-bottom") {
2019
+ ws.bottom = "0";
2020
+ } else {
2021
+ ws.top = "0";
2022
+ }
2023
+ } else {
2024
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2025
+ }
2026
+ if (opts.fadeRange) {
2027
+ const [r0, r1] = opts.fadeRange;
2028
+ const span = Math.max(r1 - r0, 1e-4);
2029
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2030
+ }
2031
+ wrapper.appendChild(opts.element);
2032
+ deps.host.appendChild(wrapper);
2033
+ let visible = null;
2034
+ let inFlight = null;
2035
+ const shouldShow = (state) => {
2036
+ if (typeof opts.showOn === "function") return opts.showOn(state);
2037
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
2038
+ return isDock || state.size > 0;
2039
+ };
2040
+ const applyVisibility = (next, animate) => {
2041
+ if (next === visible) return;
2042
+ visible = next;
2043
+ inFlight?.cancel();
2044
+ inFlight = null;
2045
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2046
+ if (next) {
2047
+ wrapper.style.visibility = "";
2048
+ if (animate) {
2049
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2050
+ }
2051
+ return;
2052
+ }
2053
+ if (!animate) {
2054
+ wrapper.style.visibility = "hidden";
2055
+ return;
2056
+ }
2057
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2058
+ inFlight = handle;
2059
+ void handle.finished.then(() => {
2060
+ if (inFlight === handle && visible === false) {
2061
+ wrapper.style.visibility = "hidden";
2062
+ }
2063
+ });
2064
+ };
2065
+ const evaluate = (animate) => {
2066
+ if (deps.isDestroyed()) return;
2067
+ applyVisibility(shouldShow(deps.getState()), animate);
2068
+ };
2069
+ const offs = [
2070
+ deps.on("snap", () => evaluate(true)),
2071
+ deps.on("open", () => evaluate(true)),
2072
+ deps.on("close", () => evaluate(true))
2073
+ ];
2074
+ evaluate(false);
2075
+ return {
2076
+ wrapper,
2077
+ detach: () => {
2078
+ offs.forEach((off) => off());
2079
+ inFlight?.cancel();
2080
+ inFlight = null;
2081
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2082
+ },
2083
+ syncZ: (z) => {
2084
+ wrapper.style.zIndex = String(z);
2085
+ }
2086
+ };
2087
+ }
2088
+
2089
+ // src/core/features/scrim-stages.ts
2090
+ var matchesId = (def, activeId) => {
2091
+ if (def.for === void 0) return false;
2092
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2093
+ };
2094
+ var matchesRange = (def, progress) => {
2095
+ if (!def.forRange) return false;
2096
+ const [from, to] = def.forRange;
2097
+ return progress >= from && progress <= to;
2098
+ };
2099
+ function installScrimStages(deps, opts) {
2100
+ const doc = deps.host.ownerDocument;
2101
+ const entries = opts.stages.map((def) => {
2102
+ const wrapper = doc.createElement("div");
2103
+ wrapper.className = "bs-scrim-stage";
2104
+ const ws = wrapper.style;
2105
+ ws.position = "absolute";
2106
+ applyOverlayPosition(
2107
+ ws,
2108
+ deps.mode,
2109
+ def.position ?? opts.position ?? "center",
2110
+ def.inset ?? opts.inset ?? "16px"
2111
+ );
2112
+ ws.visibility = "hidden";
2113
+ ws.pointerEvents = "none";
2114
+ wrapper.appendChild(def.element);
2115
+ deps.host.appendChild(wrapper);
2116
+ return { def, wrapper, inFlight: null };
2117
+ });
2118
+ let active = null;
2119
+ const resolveActive = () => {
2120
+ const state = deps.getState();
2121
+ if (state.size === 0) {
2122
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2123
+ }
2124
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2125
+ };
2126
+ const hide = (entry, animate) => {
2127
+ entry.inFlight?.cancel();
2128
+ entry.wrapper.style.pointerEvents = "none";
2129
+ if (!animate) {
2130
+ entry.inFlight = null;
2131
+ entry.wrapper.style.visibility = "hidden";
2132
+ return;
2133
+ }
2134
+ const handle = runAnchorTransition(
2135
+ entry.def.element,
2136
+ entry.def.animation ?? opts.animation,
2137
+ "exit"
2138
+ );
2139
+ entry.inFlight = handle;
2140
+ void handle.finished.then(() => {
2141
+ if (entry.inFlight === handle && active !== entry) {
2142
+ entry.wrapper.style.visibility = "hidden";
2143
+ }
2144
+ });
2145
+ };
2146
+ const show = (entry, animate) => {
2147
+ entry.inFlight?.cancel();
2148
+ entry.inFlight = null;
2149
+ entry.wrapper.style.visibility = "";
2150
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2151
+ if (animate) {
2152
+ entry.inFlight = runAnchorTransition(
2153
+ entry.def.element,
2154
+ entry.def.animation ?? opts.animation,
2155
+ "enter"
2156
+ );
2157
+ }
2158
+ };
2159
+ const update = (animate) => {
2160
+ if (deps.isDestroyed()) return;
2161
+ const next = resolveActive();
2162
+ if (next === active) return;
2163
+ const prev = active;
2164
+ active = next;
2165
+ if (prev) hide(prev, animate);
2166
+ if (next) show(next, animate);
2167
+ };
2168
+ const offs = [
2169
+ deps.on("snap", () => update(true)),
2170
+ deps.on("open", () => update(true)),
2171
+ deps.on("close", () => update(true))
2172
+ ];
2173
+ if (entries.some((e) => e.def.forRange)) {
2174
+ offs.push(deps.on("progress", () => update(true)));
2175
+ }
2176
+ update(false);
2177
+ return () => {
2178
+ offs.forEach((off) => off());
2179
+ for (const entry of entries) {
2180
+ entry.inFlight?.cancel();
2181
+ entry.inFlight = null;
2182
+ if (entry.wrapper.parentNode) {
2183
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2184
+ }
2185
+ }
2186
+ active = null;
2187
+ };
2188
+ }
2189
+
1831
2190
  // src/core/primitives/engine-options.ts
1832
2191
  var DEFAULT_FLICK_VELOCITY = 0.65;
1833
2192
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1944,6 +2303,12 @@ var BottomSheetEngine = class {
1944
2303
  __publicField(this, "linkedSheets", []);
1945
2304
  __publicField(this, "scrollCache");
1946
2305
  __publicField(this, "teardowns", new TeardownStack());
2306
+ __publicField(this, "anchors", []);
2307
+ __publicField(this, "anchorHost", null);
2308
+ __publicField(this, "stackZ", 100);
2309
+ __publicField(this, "detachScrimStages", null);
2310
+ __publicField(this, "stackEffectEnabled", false);
2311
+ __publicField(this, "stackEffectPrimed", false);
1947
2312
  __publicField(this, "transformTemplate");
1948
2313
  const resolved = resolveEngineOptions(opts, {
1949
2314
  shouldApplyInertSiblings: () => {
@@ -1977,6 +2342,9 @@ var BottomSheetEngine = class {
1977
2342
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1978
2343
  snapTo: (id) => {
1979
2344
  void this.snapTo(id);
2345
+ },
2346
+ close: () => {
2347
+ void this.close();
1980
2348
  }
1981
2349
  },
1982
2350
  resolved.scrim
@@ -2000,6 +2368,7 @@ var BottomSheetEngine = class {
2000
2368
  );
2001
2369
  this.closeOnBack = resolved.closeOnBack;
2002
2370
  this.routedTo = opts.routedTo;
2371
+ this.stackEffectEnabled = opts.stackEffect ?? false;
2003
2372
  this.snapPointsRaw = opts.snapPoints;
2004
2373
  this.persistKey = opts.persistKey;
2005
2374
  if (opts.linkedSheets) {
@@ -2012,7 +2381,7 @@ var BottomSheetEngine = class {
2012
2381
  this.snapPointsRaw,
2013
2382
  resolved.initialAllowed,
2014
2383
  this.mode,
2015
- () => this.handle.offsetHeight,
2384
+ () => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
2016
2385
  (maxAxisSize) => {
2017
2386
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2018
2387
  }
@@ -2222,11 +2591,6 @@ var BottomSheetEngine = class {
2222
2591
  }
2223
2592
  setAllowed(ids, snap) {
2224
2593
  if (this.destroyed) return;
2225
- const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
2226
- if (!willSnap) {
2227
- this.animation.cancel();
2228
- this.newCycle();
2229
- }
2230
2594
  this.snaps.setAllowedIds(ids);
2231
2595
  this.updateAriaSlider();
2232
2596
  if (snap && ids.includes(snap)) {
@@ -2266,6 +2630,79 @@ var BottomSheetEngine = class {
2266
2630
  setScrimOverlay(opts) {
2267
2631
  return this.scrim.setScrimOverlay(opts);
2268
2632
  }
2633
+ addAnchor(opts) {
2634
+ if (this.destroyed || typeof document === "undefined") return () => {
2635
+ };
2636
+ if (!this.anchorHost) {
2637
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2638
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2639
+ this.anchorHost.style.setProperty(
2640
+ "--bs-progress",
2641
+ String(this.computeProgress(this.size))
2642
+ );
2643
+ }
2644
+ const handle = attachAnchor(
2645
+ {
2646
+ mode: this.mode,
2647
+ host: this.anchorHost,
2648
+ getState: () => ({
2649
+ activeId: this.activeId,
2650
+ size: this.size,
2651
+ progress: this.computeProgress(this.size)
2652
+ }),
2653
+ on: (event, fn) => this.on(event, fn),
2654
+ isDestroyed: () => this.destroyed
2655
+ },
2656
+ opts
2657
+ );
2658
+ handle.syncZ(this.stackZ + 1);
2659
+ this.anchors.push(handle);
2660
+ const detach = () => {
2661
+ const idx = this.anchors.indexOf(handle);
2662
+ if (idx === -1) return;
2663
+ this.anchors.splice(idx, 1);
2664
+ handle.detach();
2665
+ };
2666
+ this.teardowns.add(detach);
2667
+ return detach;
2668
+ }
2669
+ setScrimStages(opts) {
2670
+ if (this.destroyed || typeof document === "undefined") return () => {
2671
+ };
2672
+ this.detachScrimStages?.();
2673
+ this.detachScrimStages = null;
2674
+ if (!opts) return () => {
2675
+ };
2676
+ const host = this.screenComponent?.parentElement;
2677
+ if (!host) {
2678
+ console.warn(
2679
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2680
+ );
2681
+ return () => {
2682
+ };
2683
+ }
2684
+ const detachInstalled = installScrimStages(
2685
+ {
2686
+ mode: this.mode,
2687
+ host,
2688
+ getState: () => ({
2689
+ activeId: this.activeId,
2690
+ size: this.size,
2691
+ progress: this.computeProgress(this.size)
2692
+ }),
2693
+ on: (event, fn) => this.on(event, fn),
2694
+ isDestroyed: () => this.destroyed
2695
+ },
2696
+ opts
2697
+ );
2698
+ const detach = () => {
2699
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2700
+ detachInstalled();
2701
+ };
2702
+ this.detachScrimStages = detach;
2703
+ this.teardowns.add(() => this.detachScrimStages?.());
2704
+ return detach;
2705
+ }
2269
2706
  getScrimState() {
2270
2707
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2271
2708
  }
@@ -2352,24 +2789,45 @@ var BottomSheetEngine = class {
2352
2789
  getAllowedRange() {
2353
2790
  return this.snaps.getAllowedRange();
2354
2791
  }
2792
+ allowedIdsBySize() {
2793
+ return this.snaps.getAllowedIds().slice().sort(
2794
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
2795
+ );
2796
+ }
2355
2797
  updateAriaSlider() {
2356
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
2798
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2357
2799
  }
2358
2800
  registerInStack() {
2359
2801
  this.teardowns.add(sheetStack.push({
2360
2802
  id: this.id,
2361
2803
  setZIndex: (z) => {
2804
+ this.stackZ = z;
2362
2805
  this.element.style.zIndex = String(z);
2363
2806
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
2807
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2364
2808
  },
2365
2809
  setIsTop: (isTop) => {
2366
2810
  this.isTopSheet = isTop;
2367
2811
  if (this.backdrop) {
2368
2812
  this.backdrop.style.display = isTop ? "" : "none";
2369
2813
  }
2370
- }
2814
+ },
2815
+ isOpen: () => this.size > 0,
2816
+ setDepth: (depth) => this.applyStackDepth(depth)
2371
2817
  }));
2372
2818
  }
2819
+ applyStackDepth(depth) {
2820
+ if (!this.stackEffectEnabled) return;
2821
+ this.element.setAttribute("data-stack-depth", String(depth));
2822
+ if (!this.stackEffectPrimed) {
2823
+ this.stackEffectPrimed = true;
2824
+ const isVertical = this.mode === "bottom" || this.mode === "top";
2825
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
2826
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
2827
+ }
2828
+ const scale = Math.max(1 - depth * 0.04, 0.86);
2829
+ this.element.style.scale = scale === 1 ? "" : String(scale);
2830
+ }
2373
2831
  attach() {
2374
2832
  this.gesture = new GestureController({
2375
2833
  handle: this.handle,
@@ -2429,7 +2887,7 @@ var BottomSheetEngine = class {
2429
2887
  container: this.scrollContainer,
2430
2888
  isDragging: () => this.gesture?.isDragging ?? false,
2431
2889
  isAnimating: () => this.animation.isAnimating,
2432
- getAllowedIds: () => this.snaps.getAllowedIds(),
2890
+ getAllowedIds: () => this.allowedIdsBySize(),
2433
2891
  getActiveId: () => this.activeId,
2434
2892
  snapTo: (id) => {
2435
2893
  void this.snapTo(id);
@@ -2458,11 +2916,13 @@ var BottomSheetEngine = class {
2458
2916
  },
2459
2917
  newCycle: () => {
2460
2918
  this.newCycle();
2461
- }
2919
+ },
2920
+ isAnimating: () => this.animation.isAnimating,
2921
+ resyncAfterCancel: () => this.resyncAfterResize()
2462
2922
  }));
2463
2923
  if (this.lifecycle.closeOnEscape) {
2464
2924
  const onKey = (e) => {
2465
- if (this.destroyed) return;
2925
+ if (this.destroyed || e.defaultPrevented) return;
2466
2926
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2467
2927
  void this.close();
2468
2928
  }
@@ -2494,7 +2954,7 @@ var BottomSheetEngine = class {
2494
2954
  handle: this.handle,
2495
2955
  mode: this.mode,
2496
2956
  isDestroyed: () => this.destroyed,
2497
- getAllowedIds: () => this.snaps.getAllowedIds(),
2957
+ getAllowedIds: () => this.allowedIdsBySize(),
2498
2958
  getActiveId: () => this.activeId,
2499
2959
  snapTo: (id) => {
2500
2960
  void this.snapTo(id);
@@ -2515,6 +2975,7 @@ var BottomSheetEngine = class {
2515
2975
  });
2516
2976
  if (!target) return;
2517
2977
  const previousId = this.activeId;
2978
+ if (target.id === previousId && target.size === this.size) return;
2518
2979
  if (this.emitBeforeSnap(target, previousId)) {
2519
2980
  const restore = this.snaps.findById(previousId);
2520
2981
  if (restore) {
@@ -2523,6 +2984,7 @@ var BottomSheetEngine = class {
2523
2984
  return;
2524
2985
  }
2525
2986
  const previousSize = this.size;
2987
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2526
2988
  this.scrollCache.cache(previousId, previousSize, target.size);
2527
2989
  this.activeId = target.id;
2528
2990
  void this.animation.animateTo(target.size, velocity).then(() => {
@@ -2531,9 +2993,10 @@ var BottomSheetEngine = class {
2531
2993
  this.updateAriaSlider();
2532
2994
  this.emit("snap", { id: target.id, size: target.size });
2533
2995
  this.haptic();
2534
- if (previousId === "closed" && target.size > 0) {
2996
+ if (previousSnapSize === 0 && target.size > 0) {
2535
2997
  this.emit("open", { id: target.id });
2536
2998
  this.handleOpen();
2999
+ notifyLinkedSheets(this.linkedSheets, this);
2537
3000
  }
2538
3001
  if (target.size === 0) {
2539
3002
  this.emit("close", void 0);
@@ -2559,6 +3022,9 @@ var BottomSheetEngine = class {
2559
3022
  if (this.scrimParent) {
2560
3023
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2561
3024
  }
3025
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3026
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3027
+ }
2562
3028
  }
2563
3029
  const progress = this.computeProgress(size);
2564
3030
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2567,6 +3033,9 @@ var BottomSheetEngine = class {
2567
3033
  );
2568
3034
  if (progressChanged) {
2569
3035
  style.setProperty("--bs-progress", String(progress));
3036
+ if (this.anchorHost) {
3037
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3038
+ }
2570
3039
  }
2571
3040
  this.scrim.applyOpacity(progress, progressChanged);
2572
3041
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2581,6 +3050,7 @@ var BottomSheetEngine = class {
2581
3050
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2582
3051
  }
2583
3052
  handleOpen() {
3053
+ sheetStack.promote(this.id);
2584
3054
  try {
2585
3055
  this.lifecycle.install();
2586
3056
  } catch (err) {
@@ -2594,8 +3064,24 @@ var BottomSheetEngine = class {
2594
3064
  });
2595
3065
  }
2596
3066
  }
3067
+ resyncAfterResize() {
3068
+ if (this.destroyed) return;
3069
+ const target = this.snaps.findById(this.activeId);
3070
+ if (!target) return;
3071
+ this.updateAriaSlider();
3072
+ this.emit("snap", { id: target.id, size: target.size });
3073
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3074
+ this.emit("open", { id: target.id });
3075
+ this.handleOpen();
3076
+ notifyLinkedSheets(this.linkedSheets, this);
3077
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3078
+ this.emit("close", void 0);
3079
+ this.handleClose();
3080
+ }
3081
+ }
2597
3082
  handleClose() {
2598
3083
  this.lifecycle.release();
3084
+ sheetStack.update();
2599
3085
  }
2600
3086
  };
2601
3087
 
@@ -2689,6 +3175,16 @@ function useBottomSheet(opts) {
2689
3175
  engineRef.current?.setAllowed(ids, snap);
2690
3176
  }, []);
2691
3177
  const getEngine = useCallback(() => engineRef.current, []);
3178
+ const addAnchor = useCallback(
3179
+ (anchorOpts) => engineRef.current?.addAnchor(anchorOpts) ?? (() => {
3180
+ }),
3181
+ []
3182
+ );
3183
+ const setScrimStages = useCallback(
3184
+ (stagesOpts) => engineRef.current?.setScrimStages(stagesOpts) ?? (() => {
3185
+ }),
3186
+ []
3187
+ );
2692
3188
  return useMemo(
2693
3189
  () => ({
2694
3190
  sheetRef,
@@ -2701,10 +3197,12 @@ function useBottomSheet(opts) {
2701
3197
  open,
2702
3198
  close,
2703
3199
  setAllowed,
3200
+ addAnchor,
3201
+ setScrimStages,
2704
3202
  engine: engineRef.current,
2705
3203
  getEngine
2706
3204
  }),
2707
- [state, snapTo, open, close, setAllowed, getEngine]
3205
+ [state, snapTo, open, close, setAllowed, addAnchor, setScrimStages, getEngine]
2708
3206
  );
2709
3207
  }
2710
3208
  var useHasMounted = () => {
@@ -2724,6 +3222,8 @@ var BottomSheet = forwardRef(
2724
3222
  leftButton,
2725
3223
  rightButton,
2726
3224
  screen,
3225
+ anchors,
3226
+ scrimStages,
2727
3227
  onChange,
2728
3228
  ariaLabel = "Bottom sheet",
2729
3229
  ariaLabelledBy,
@@ -2744,10 +3244,7 @@ var BottomSheet = forwardRef(
2744
3244
  setAllowed,
2745
3245
  getEngine
2746
3246
  } = useBottomSheet(engineOpts);
2747
- const isVerticalAxis = (engineOpts.mode ?? "bottom") === "bottom" || (engineOpts.mode ?? "bottom") === "top";
2748
3247
  const ariaModal = engineOpts.focusTrap === true;
2749
- const allowedIds = engineOpts.allowed ?? engineOpts.snapPoints.map((p) => p.id);
2750
- const activeIdx = allowedIds.indexOf(state.activeId);
2751
3248
  const lastAnnouncedRef = useRef("");
2752
3249
  const [announce, setAnnounce] = useState("");
2753
3250
  useEffect(() => {
@@ -2767,6 +3264,10 @@ var BottomSheet = forwardRef(
2767
3264
  setScrim: (opts) => getEngine()?.setScrim(opts),
2768
3265
  setScrimOverlay: (opts) => getEngine()?.setScrimOverlay(opts) ?? (() => {
2769
3266
  }),
3267
+ addAnchor: (opts) => getEngine()?.addAnchor(opts) ?? (() => {
3268
+ }),
3269
+ setScrimStages: (opts) => getEngine()?.setScrimStages(opts) ?? (() => {
3270
+ }),
2770
3271
  getEngine,
2771
3272
  get state() {
2772
3273
  return getEngine()?.state ?? state;
@@ -2777,6 +3278,61 @@ var BottomSheet = forwardRef(
2777
3278
  useEffect(() => {
2778
3279
  onChange?.(state);
2779
3280
  }, [state, onChange]);
3281
+ const [anchorHosts, setAnchorHosts] = useState([]);
3282
+ const anchorsKey = anchors ? JSON.stringify(
3283
+ anchors.map((a) => [
3284
+ a.position,
3285
+ a.inset,
3286
+ Array.isArray(a.showOn) ? a.showOn : a.showOn ? "fn" : null,
3287
+ a.fadeRange,
3288
+ a.interactive,
3289
+ typeof a.animation === "string" ? a.animation : a.animation ? "custom" : null
3290
+ ])
3291
+ ) : "";
3292
+ useEffect(() => {
3293
+ const engine = getEngine();
3294
+ if (!engine || !anchors || anchors.length === 0) return;
3295
+ const hosts = [];
3296
+ const detachers = anchors.map((a) => {
3297
+ const host = document.createElement("div");
3298
+ hosts.push(host);
3299
+ const { node: _node, ...rest } = a;
3300
+ return engine.addAnchor({ ...rest, element: host });
3301
+ });
3302
+ setAnchorHosts(hosts);
3303
+ return () => {
3304
+ detachers.forEach((d) => d());
3305
+ setAnchorHosts([]);
3306
+ };
3307
+ }, [getEngine, anchorsKey]);
3308
+ const [stageHosts, setStageHosts] = useState([]);
3309
+ const stagesKey = scrimStages ? JSON.stringify(
3310
+ scrimStages.stages.map((s) => [
3311
+ s.for,
3312
+ s.forRange,
3313
+ s.position,
3314
+ s.inset,
3315
+ s.interactive,
3316
+ typeof s.animation === "string" ? s.animation : s.animation ? "custom" : null
3317
+ ])
3318
+ ) : "";
3319
+ useEffect(() => {
3320
+ const engine = getEngine();
3321
+ if (!engine || !scrimStages || scrimStages.stages.length === 0) return;
3322
+ const hosts = [];
3323
+ const defs = scrimStages.stages.map((s) => {
3324
+ const host = document.createElement("div");
3325
+ hosts.push(host);
3326
+ const { node: _node, ...rest } = s;
3327
+ return { ...rest, element: host };
3328
+ });
3329
+ const detach = engine.setScrimStages({ ...scrimStages, stages: defs });
3330
+ setStageHosts(hosts);
3331
+ return () => {
3332
+ detach();
3333
+ setStageHosts([]);
3334
+ };
3335
+ }, [getEngine, stagesKey]);
2780
3336
  if (noSSR && !mounted) return null;
2781
3337
  return /* @__PURE__ */ jsxs("div", { className: "bs-root", children: [
2782
3338
  backdrop && /* @__PURE__ */ jsx(
@@ -2817,11 +3373,6 @@ var BottomSheet = forwardRef(
2817
3373
  className: "bs-handle",
2818
3374
  role: "slider",
2819
3375
  tabIndex: 0,
2820
- "aria-orientation": isVerticalAxis ? "vertical" : "horizontal",
2821
- "aria-valuemin": 0,
2822
- "aria-valuemax": Math.max(0, allowedIds.length - 1),
2823
- "aria-valuenow": Math.max(0, activeIdx),
2824
- "aria-valuetext": state.activeId,
2825
3376
  "aria-label": "Resize sheet",
2826
3377
  children: typeof header === "function" ? header(state) : header
2827
3378
  }
@@ -2840,6 +3391,12 @@ var BottomSheet = forwardRef(
2840
3391
  /* @__PURE__ */ jsx("span", { className: "bs-sr-only", role: "status", "aria-live": "polite", children: announce })
2841
3392
  ]
2842
3393
  }
3394
+ ),
3395
+ anchorHosts.map(
3396
+ (host, i) => anchors?.[i] ? createPortal(anchors[i].node, host) : null
3397
+ ),
3398
+ stageHosts.map(
3399
+ (host, i) => scrimStages?.stages[i] ? createPortal(scrimStages.stages[i].node, host) : null
2843
3400
  )
2844
3401
  ] });
2845
3402
  }
@@ -2924,6 +3481,10 @@ var OverlayEngine = class {
2924
3481
  __publicField(this, "closeOnOutsidePointerEnabled");
2925
3482
  __publicField(this, "detachOutsidePointer", null);
2926
3483
  __publicField(this, "detachSwipe", null);
3484
+ __publicField(this, "respectReducedMotionEnabled");
3485
+ __publicField(this, "isTop", true);
3486
+ __publicField(this, "backPushed", false);
3487
+ __publicField(this, "movedToTarget", false);
2927
3488
  __publicField(this, "onOpenCb");
2928
3489
  __publicField(this, "onCloseCb");
2929
3490
  __publicField(this, "isOpen_", false);
@@ -2960,6 +3521,7 @@ var OverlayEngine = class {
2960
3521
  this.mountTarget = opts.mountTo;
2961
3522
  this.peekRaw = opts.peek;
2962
3523
  this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
3524
+ this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
2963
3525
  this.onOpenCb = opts.onOpen;
2964
3526
  this.onCloseCb = opts.onClose;
2965
3527
  if (this.peekRaw !== void 0 && this.edge !== "bottom") {
@@ -3007,15 +3569,17 @@ var OverlayEngine = class {
3007
3569
  this.emit("before-open", void 0);
3008
3570
  this.isOpen_ = true;
3009
3571
  this.isAnimating_ = true;
3572
+ sheetStack.promote(this.id);
3010
3573
  this.element.removeAttribute("hidden");
3011
3574
  this.element.setAttribute("data-state", "entering");
3012
3575
  if (this.focusTrapEnabled) {
3013
3576
  this.element.setAttribute("aria-modal", "true");
3014
3577
  }
3015
- const transitionStr = `transform ${this.duration}ms ${this.enterEasing}, opacity ${this.duration}ms ease-out`;
3578
+ const dur = this.effectiveDuration();
3579
+ const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
3016
3580
  this.element.style.transition = transitionStr;
3017
3581
  if (this.backdrop) {
3018
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-out`;
3582
+ this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
3019
3583
  }
3020
3584
  const cycle = ++this.cycleNonce;
3021
3585
  return new Promise((resolve) => {
@@ -3027,7 +3591,7 @@ var OverlayEngine = class {
3027
3591
  this.element.style.opacity = "1";
3028
3592
  if (this.backdrop) {
3029
3593
  this.backdrop.style.opacity = String(this.backdropOpacity);
3030
- this.backdrop.style.pointerEvents = "auto";
3594
+ this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
3031
3595
  if (this.backdropFilter) {
3032
3596
  this.backdrop.style.backdropFilter = this.backdropFilter;
3033
3597
  this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
@@ -3063,7 +3627,10 @@ var OverlayEngine = class {
3063
3627
  });
3064
3628
  return resolve();
3065
3629
  }
3066
- this.awaitTransition(cycle).then(() => {
3630
+ this.awaitTransition(
3631
+ cycle,
3632
+ this.enterAnimation === "fade" ? "opacity" : "transform"
3633
+ ).then(() => {
3067
3634
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
3068
3635
  this.isAnimating_ = false;
3069
3636
  this.element.setAttribute("data-state", "open");
@@ -3080,11 +3647,14 @@ var OverlayEngine = class {
3080
3647
  this.emit("before-close", void 0);
3081
3648
  this.isOpen_ = false;
3082
3649
  this.isAnimating_ = true;
3650
+ sheetStack.update();
3651
+ this.popBackMarker(reason);
3083
3652
  this.element.setAttribute("data-state", "exiting");
3084
- const transitionStr = `transform ${this.duration}ms ${this.exitEasing}, opacity ${this.duration}ms ease-in`;
3653
+ const dur = this.effectiveDuration();
3654
+ const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
3085
3655
  this.element.style.transition = transitionStr;
3086
3656
  if (this.backdrop) {
3087
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-in`;
3657
+ this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
3088
3658
  }
3089
3659
  const cycle = ++this.cycleNonce;
3090
3660
  return new Promise((resolve) => {
@@ -3095,7 +3665,7 @@ var OverlayEngine = class {
3095
3665
  this.exitAnimation,
3096
3666
  this.peekPx
3097
3667
  );
3098
- this.element.style.opacity = "0";
3668
+ this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
3099
3669
  if (this.backdrop) {
3100
3670
  this.backdrop.style.opacity = "0";
3101
3671
  this.backdrop.style.pointerEvents = "none";
@@ -3105,7 +3675,10 @@ var OverlayEngine = class {
3105
3675
  }
3106
3676
  }
3107
3677
  this.releaseInteractiveListeners();
3108
- this.awaitTransition(cycle).then(() => {
3678
+ this.awaitTransition(
3679
+ cycle,
3680
+ this.exitAnimation === "fade" ? "opacity" : "transform"
3681
+ ).then(() => {
3109
3682
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
3110
3683
  this.isAnimating_ = false;
3111
3684
  if (this.peekPx === void 0) {
@@ -3133,7 +3706,11 @@ var OverlayEngine = class {
3133
3706
  this.releaseInteractiveListeners();
3134
3707
  this.releaseStackEntry?.();
3135
3708
  this.releaseStackEntry = null;
3709
+ const hadOriginalParent = this.originalParent !== null;
3136
3710
  this.restoreMount();
3711
+ if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
3712
+ this.element.parentNode.removeChild(this.element);
3713
+ }
3137
3714
  }
3138
3715
  setBackdropOpacity(opacity) {
3139
3716
  if (this.destroyed) return;
@@ -3234,6 +3811,36 @@ var OverlayEngine = class {
3234
3811
  }
3235
3812
  }
3236
3813
  }
3814
+ if (opts.duration !== void 0) this.duration = opts.duration;
3815
+ if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
3816
+ if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
3817
+ if (opts.closeOnEscape !== void 0) {
3818
+ this.closeOnEscape = opts.closeOnEscape;
3819
+ if (!opts.closeOnEscape) {
3820
+ this.detachEscape?.();
3821
+ this.detachEscape = null;
3822
+ } else if (this.isOpen_) {
3823
+ this.installInteractiveListeners();
3824
+ }
3825
+ }
3826
+ if (opts.closeOnBackdrop !== void 0) {
3827
+ this.closeOnBackdropEnabled = opts.closeOnBackdrop;
3828
+ if (!opts.closeOnBackdrop) {
3829
+ this.detachBackdrop?.();
3830
+ this.detachBackdrop = null;
3831
+ } else if (this.isOpen_) {
3832
+ this.installInteractiveListeners();
3833
+ }
3834
+ }
3835
+ if (opts.closeOnOutsidePointer !== void 0) {
3836
+ this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
3837
+ if (!opts.closeOnOutsidePointer) {
3838
+ this.detachOutsidePointer?.();
3839
+ this.detachOutsidePointer = null;
3840
+ } else if (this.isOpen_) {
3841
+ this.installInteractiveListeners();
3842
+ }
3843
+ }
3237
3844
  if (opts.children !== void 0) {
3238
3845
  if (opts.children === null) {
3239
3846
  this.clearOverlayChildren();
@@ -3267,6 +3874,22 @@ var OverlayEngine = class {
3267
3874
  emit(event, payload) {
3268
3875
  this.bus.emit(event, payload);
3269
3876
  }
3877
+ effectiveDuration() {
3878
+ return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
3879
+ }
3880
+ popBackMarker(reason) {
3881
+ if (!this.backPushed) return;
3882
+ this.backPushed = false;
3883
+ if (reason === "back") return;
3884
+ const state = history.state;
3885
+ if (state && state.__bsOverlay === this.id) {
3886
+ try {
3887
+ history.back();
3888
+ } catch {
3889
+ return;
3890
+ }
3891
+ }
3892
+ }
3270
3893
  resolvePeekPx(raw) {
3271
3894
  if (typeof raw === "number") return Math.max(0, raw);
3272
3895
  try {
@@ -3288,6 +3911,7 @@ var OverlayEngine = class {
3288
3911
  this.originalParent = this.element.parentNode;
3289
3912
  this.originalNextSibling = this.element.nextSibling;
3290
3913
  target.appendChild(this.element);
3914
+ this.movedToTarget = true;
3291
3915
  this.mounted = true;
3292
3916
  }
3293
3917
  restoreMount() {
@@ -3321,7 +3945,7 @@ var OverlayEngine = class {
3321
3945
  this.backdrop.style.willChange = "opacity";
3322
3946
  }
3323
3947
  }
3324
- awaitTransition(cycle) {
3948
+ awaitTransition(cycle, property = "transform") {
3325
3949
  return new Promise((resolve) => {
3326
3950
  let done = false;
3327
3951
  const finish = () => {
@@ -3336,10 +3960,10 @@ var OverlayEngine = class {
3336
3960
  finish();
3337
3961
  return;
3338
3962
  }
3339
- if (e.propertyName === "transform") finish();
3963
+ if (e.target === this.element && e.propertyName === property) finish();
3340
3964
  };
3341
3965
  this.element.addEventListener("transitionend", onEnd);
3342
- const timer = window.setTimeout(finish, this.duration + 50);
3966
+ const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
3343
3967
  });
3344
3968
  }
3345
3969
  installInteractiveListeners() {
@@ -3349,7 +3973,10 @@ var OverlayEngine = class {
3349
3973
  }
3350
3974
  if (this.closeOnEscape && !this.detachEscape) {
3351
3975
  const handler = (e) => {
3352
- if (e.key === "Escape" && this.isOpen_) void this.close("escape");
3976
+ if (e.defaultPrevented) return;
3977
+ if (e.key === "Escape" && this.isOpen_ && this.isTop) {
3978
+ void this.close("escape");
3979
+ }
3353
3980
  };
3354
3981
  document.addEventListener("keydown", handler);
3355
3982
  this.detachEscape = () => document.removeEventListener("keydown", handler);
@@ -3362,10 +3989,14 @@ var OverlayEngine = class {
3362
3989
  if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
3363
3990
  try {
3364
3991
  history.pushState({ __bsOverlay: this.id }, "");
3992
+ this.backPushed = true;
3365
3993
  } catch {
3366
3994
  }
3367
3995
  const onPop = () => {
3368
- if (this.isOpen_) void this.close("back");
3996
+ if (this.isOpen_ && this.isTop) {
3997
+ this.backPushed = false;
3998
+ void this.close("back");
3999
+ }
3369
4000
  };
3370
4001
  window.addEventListener("popstate", onPop);
3371
4002
  this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
@@ -3401,43 +4032,52 @@ var OverlayEngine = class {
3401
4032
  const edge = this.edge;
3402
4033
  const isVertical = edge === "bottom" || edge === "top";
3403
4034
  const sign = edge === "bottom" || edge === "right" ? 1 : -1;
3404
- const threshold = this.swipeThreshold;
3405
- const velocityThreshold = this.swipeVelocityThreshold;
4035
+ const SLOP = 8;
3406
4036
  let startX = 0;
3407
4037
  let startY = 0;
3408
4038
  let startT = 0;
4039
+ let tracking = false;
3409
4040
  let dragging = false;
3410
4041
  let pointerId = null;
4042
+ const previousTouchAction = el.style.touchAction;
4043
+ el.style.touchAction = isVertical ? "pan-x" : "pan-y";
3411
4044
  const onDown = (e) => {
3412
4045
  if (e.button !== void 0 && e.button !== 0) return;
3413
4046
  pointerId = e.pointerId;
3414
4047
  startX = e.clientX;
3415
4048
  startY = e.clientY;
3416
4049
  startT = performance.now();
3417
- dragging = true;
3418
- el.style.transition = "none";
3419
- try {
3420
- el.setPointerCapture(e.pointerId);
3421
- } catch {
3422
- }
4050
+ tracking = true;
4051
+ dragging = false;
3423
4052
  };
3424
4053
  const onMove = (e) => {
3425
- if (!dragging || e.pointerId !== pointerId) return;
4054
+ if (!tracking || e.pointerId !== pointerId) return;
3426
4055
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
4056
+ if (!dragging) {
4057
+ if (Math.abs(delta) < SLOP) return;
4058
+ dragging = true;
4059
+ el.style.transition = "none";
4060
+ try {
4061
+ el.setPointerCapture(e.pointerId);
4062
+ } catch {
4063
+ }
4064
+ }
3427
4065
  const offset = Math.max(0, sign * delta);
3428
4066
  el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
3429
4067
  };
3430
4068
  const finish = (e) => {
3431
- if (!dragging || e.pointerId !== pointerId) return;
3432
- dragging = false;
4069
+ if (!tracking || e.pointerId !== pointerId) return;
4070
+ tracking = false;
3433
4071
  pointerId = null;
4072
+ if (!dragging) return;
4073
+ dragging = false;
3434
4074
  const dt = performance.now() - startT || 1;
3435
4075
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3436
4076
  const offset = Math.max(0, sign * delta);
3437
4077
  const velocity = sign * delta / dt;
3438
4078
  const size = isVertical ? el.offsetHeight : el.offsetWidth;
3439
- const past = offset > size * threshold || velocity > velocityThreshold;
3440
- el.style.transition = `transform ${this.duration}ms ${this.exitEasing}`;
4079
+ const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
4080
+ el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
3441
4081
  if (past) {
3442
4082
  void this.close("swipe");
3443
4083
  } else {
@@ -3449,6 +4089,7 @@ var OverlayEngine = class {
3449
4089
  el.addEventListener("pointerup", finish);
3450
4090
  el.addEventListener("pointercancel", finish);
3451
4091
  return () => {
4092
+ el.style.touchAction = previousTouchAction;
3452
4093
  el.removeEventListener("pointerdown", onDown);
3453
4094
  el.removeEventListener("pointermove", onMove);
3454
4095
  el.removeEventListener("pointerup", finish);
@@ -3463,10 +4104,12 @@ var OverlayEngine = class {
3463
4104
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3464
4105
  },
3465
4106
  setIsTop: (isTop) => {
4107
+ this.isTop = isTop;
3466
4108
  if (this.backdrop) {
3467
4109
  this.backdrop.style.display = isTop ? "" : "none";
3468
4110
  }
3469
- }
4111
+ },
4112
+ isOpen: () => this.isOpen_
3470
4113
  });
3471
4114
  }
3472
4115
  };
@@ -3552,7 +4195,7 @@ var Overlay = forwardRef(function Overlay2(props, ref) {
3552
4195
  if (openProp === void 0) return;
3553
4196
  if (openProp && !state.isOpen) void open();
3554
4197
  else if (!openProp && state.isOpen) void close();
3555
- }, [openProp]);
4198
+ }, [openProp, state.isOpen]);
3556
4199
  useEffect(() => {
3557
4200
  onChange?.(state);
3558
4201
  }, [state, onChange]);
@@ -3684,46 +4327,34 @@ var BottomSheetFromConfig = forwardRef(function BottomSheetFromConfig2(props, re
3684
4327
  () => sheetRef.current,
3685
4328
  []
3686
4329
  );
3687
- const lastActiveIdRef = useRef(null);
3688
- const handleChange = (state) => {
3689
- const prev = lastActiveIdRef.current;
3690
- const next = state.activeId;
3691
- lastActiveIdRef.current = next;
3692
- const evs = eventsRef.current;
3693
- const hs = handlersRef.current;
3694
- if (evs.onSnap) {
3695
- const fn = hs[evs.onSnap];
3696
- if (typeof fn === "function") {
3697
- fn(next);
3698
- } else {
3699
- console.warn(
3700
- `[BottomSheetFromConfig] eventHandlers["${evs.onSnap}"] is not a function; onSnap is a no-op.`
3701
- );
3702
- }
3703
- }
3704
- const isClosedNext = next === "" || next === "closed";
3705
- const isClosedPrev = prev === null || prev === "" || prev === "closed";
3706
- if (isClosedPrev && !isClosedNext && evs.onOpen) {
3707
- const fn = hs[evs.onOpen];
3708
- if (typeof fn === "function") {
3709
- fn(next);
3710
- } else {
3711
- console.warn(
3712
- `[BottomSheetFromConfig] eventHandlers["${evs.onOpen}"] is not a function; onOpen is a no-op.`
3713
- );
3714
- }
3715
- }
3716
- if (!isClosedPrev && isClosedNext && evs.onClose) {
3717
- const fn = hs[evs.onClose];
3718
- if (typeof fn === "function") {
4330
+ const invoke = (key, arg) => {
4331
+ const evName = eventsRef.current[key];
4332
+ if (!evName) return;
4333
+ const fn = handlersRef.current[evName];
4334
+ if (typeof fn === "function") {
4335
+ if (arg === void 0) {
3719
4336
  fn();
3720
4337
  } else {
3721
- console.warn(
3722
- `[BottomSheetFromConfig] eventHandlers["${evs.onClose}"] is not a function; onClose is a no-op.`
3723
- );
4338
+ fn(arg);
3724
4339
  }
4340
+ } else {
4341
+ console.warn(
4342
+ `[BottomSheetFromConfig] eventHandlers["${evName}"] is not a function; ${key} is a no-op.`
4343
+ );
3725
4344
  }
3726
4345
  };
4346
+ const invokeRef = useRef(invoke);
4347
+ invokeRef.current = invoke;
4348
+ useEffect(() => {
4349
+ const engine = sheetRef.current?.getEngine();
4350
+ if (!engine) return;
4351
+ const offs = [
4352
+ engine.on("snap", (p) => invokeRef.current("onSnap", p.id)),
4353
+ engine.on("open", (p) => invokeRef.current("onOpen", p.id)),
4354
+ engine.on("close", () => invokeRef.current("onClose"))
4355
+ ];
4356
+ return () => offs.forEach((off) => off());
4357
+ }, []);
3727
4358
  return /* @__PURE__ */ jsx(
3728
4359
  BottomSheet,
3729
4360
  {
@@ -3739,7 +4370,6 @@ var BottomSheetFromConfig = forwardRef(function BottomSheetFromConfig2(props, re
3739
4370
  closeOnBackdrop: derived.behavior.closeOnBackdrop,
3740
4371
  lockBodyScroll: derived.behavior.lockBodyScroll,
3741
4372
  rubberBand: derived.behavior.rubberBand,
3742
- onChange: handleChange,
3743
4373
  header: slotContent?.header,
3744
4374
  children: slotContent?.body
3745
4375
  }