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