@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/index.cjs CHANGED
@@ -22,6 +22,18 @@ var SheetStack = class {
22
22
  this.entries.splice(idx, 1);
23
23
  this.recompute();
24
24
  }
25
+ promote(id) {
26
+ const idx = this.entries.findIndex((e) => e.id === id);
27
+ if (idx === -1) return;
28
+ if (idx !== this.entries.length - 1) {
29
+ const [entry] = this.entries.splice(idx, 1);
30
+ this.entries.push(entry);
31
+ }
32
+ this.recompute();
33
+ }
34
+ update() {
35
+ this.recompute();
36
+ }
25
37
  size() {
26
38
  return this.entries.length;
27
39
  }
@@ -29,9 +41,24 @@ var SheetStack = class {
29
41
  this.entries = [];
30
42
  }
31
43
  recompute() {
44
+ let top = null;
45
+ const open = [];
46
+ for (const entry of this.entries) {
47
+ if (!entry.isOpen || entry.isOpen()) {
48
+ top = entry;
49
+ open.push(entry);
50
+ }
51
+ }
52
+ if (!top && this.entries.length > 0) {
53
+ top = this.entries[this.entries.length - 1];
54
+ }
32
55
  this.entries.forEach((entry, i) => {
33
56
  entry.setZIndex(BASE_Z + i * STEP);
34
- entry.setIsTop(i === this.entries.length - 1);
57
+ entry.setIsTop(entry === top);
58
+ if (entry.setDepth) {
59
+ const openIdx = open.indexOf(entry);
60
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
61
+ }
35
62
  });
36
63
  }
37
64
  };
@@ -322,14 +349,17 @@ function installAutoCollapse(deps) {
322
349
  const fire = () => {
323
350
  if (deps.isDestroyed()) return;
324
351
  if (deps.isDragging()) return;
325
- const allowed = deps.getAllowedIds();
326
- const target = allowed.find((id) => {
352
+ const active = deps.resolveSnap(deps.getActiveId());
353
+ if (!active || active.size === 0) return;
354
+ let target = null;
355
+ for (const id of deps.getAllowedIds()) {
327
356
  const snap = deps.resolveSnap(id);
328
- return snap !== null && snap.size > 0;
329
- });
357
+ if (!snap || snap.size <= 0) continue;
358
+ if (!target || snap.size < target.size) target = snap;
359
+ }
330
360
  if (!target) return;
331
- if (target === deps.getActiveId()) return;
332
- deps.snapTo(target);
361
+ if (target.id === active.id || target.size >= active.size) return;
362
+ deps.snapTo(target.id);
333
363
  };
334
364
  const reset = () => {
335
365
  clearTimer();
@@ -340,6 +370,7 @@ function installAutoCollapse(deps) {
340
370
  }, ms);
341
371
  };
342
372
  const unsubscribe = deps.on("snap", () => reset());
373
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
343
374
  return () => {
344
375
  clearTimer();
345
376
  unsubscribe();
@@ -352,6 +383,7 @@ function notifyLinkedSheets(sheets, self) {
352
383
  for (const linked of sheets) {
353
384
  if (linked === self) continue;
354
385
  try {
386
+ if (linked.state.size === 0) continue;
355
387
  const ids = linked.getAllowedIds();
356
388
  const target = ids.find((id) => id !== "closed");
357
389
  if (target && target !== linked.state.activeId) {
@@ -398,7 +430,7 @@ function installRoute(deps) {
398
430
  const onPop = (event) => {
399
431
  if (deps.isDestroyed()) return;
400
432
  const state = event.state;
401
- if (state && state.__bsRouted === target && deps.getSize() > 0) {
433
+ if ((!state || state.__bsRouted !== target) && deps.getSize() > 0) {
402
434
  routePushed = false;
403
435
  void deps.close();
404
436
  }
@@ -552,13 +584,17 @@ function installResizeObserver(deps) {
552
584
  if (viewportSize2 > 0 && viewportSize2 < max) {
553
585
  deps.setMaxAxisSize(viewportSize2);
554
586
  }
587
+ const wasAnimating = deps.isAnimating?.() ?? false;
555
588
  deps.cancelInFlight();
556
589
  deps.newCycle();
557
590
  const current = deps.resolveActiveSnap();
558
591
  const newMax = deps.getMaxAxisSize();
559
592
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
560
593
  deps.setSize(targetSize);
561
- if (current && !deps.isDragging()) deps.applySize(targetSize);
594
+ if (current && !deps.isDragging()) {
595
+ deps.applySize(targetSize);
596
+ if (wasAnimating) deps.resyncAfterCancel?.();
597
+ }
562
598
  };
563
599
  let resizeObserver = null;
564
600
  if (typeof ResizeObserver !== "undefined") {
@@ -673,6 +709,85 @@ function devWarn(...args) {
673
709
  }
674
710
  }
675
711
 
712
+ // src/core/primitives/overlay-position.ts
713
+ function resolveSheetAnchoredStyle(mode, position, inset) {
714
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
715
+ switch (mode) {
716
+ case "bottom":
717
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
718
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
719
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
720
+ case "top":
721
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
722
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
723
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
724
+ case "left":
725
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
726
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
727
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
728
+ case "right":
729
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
730
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
731
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
732
+ }
733
+ }
734
+ function applyOverlayPosition(ws, mode, position, inset) {
735
+ switch (position) {
736
+ case "top-left":
737
+ ws.top = inset;
738
+ ws.left = inset;
739
+ break;
740
+ case "top-center":
741
+ ws.top = inset;
742
+ ws.left = "50%";
743
+ ws.transform = "translateX(-50%)";
744
+ break;
745
+ case "top-right":
746
+ ws.top = inset;
747
+ ws.right = inset;
748
+ break;
749
+ case "center-left":
750
+ ws.top = "50%";
751
+ ws.left = inset;
752
+ ws.transform = "translateY(-50%)";
753
+ break;
754
+ case "center":
755
+ ws.top = "50%";
756
+ ws.left = "50%";
757
+ ws.transform = "translate(-50%, -50%)";
758
+ break;
759
+ case "center-right":
760
+ ws.top = "50%";
761
+ ws.right = inset;
762
+ ws.transform = "translateY(-50%)";
763
+ break;
764
+ case "bottom-left":
765
+ ws.bottom = inset;
766
+ ws.left = inset;
767
+ break;
768
+ case "bottom-center":
769
+ ws.bottom = inset;
770
+ ws.left = "50%";
771
+ ws.transform = "translateX(-50%)";
772
+ break;
773
+ case "bottom-right":
774
+ ws.bottom = inset;
775
+ ws.right = inset;
776
+ break;
777
+ case "sheet-top-left":
778
+ case "sheet-top-center":
779
+ case "sheet-top-right": {
780
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
781
+ if (style.top !== void 0) ws.top = style.top;
782
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
783
+ if (style.left !== void 0) ws.left = style.left;
784
+ if (style.right !== void 0) ws.right = style.right;
785
+ if (style.transform !== void 0) ws.transform = style.transform;
786
+ break;
787
+ }
788
+ }
789
+ }
790
+
676
791
  // src/core/controllers/scrim-controller.ts
677
792
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
678
793
  var ScrimController = class {
@@ -685,6 +800,8 @@ var ScrimController = class {
685
800
  __publicField(this, "getAllowedIds");
686
801
  __publicField(this, "getResolvedSnaps");
687
802
  __publicField(this, "snapToFn");
803
+ __publicField(this, "closeFn");
804
+ __publicField(this, "scrimInteractive", false);
688
805
  __publicField(this, "scrimMode");
689
806
  __publicField(this, "backdropRange");
690
807
  __publicField(this, "screenRange");
@@ -705,6 +822,7 @@ var ScrimController = class {
705
822
  this.getAllowedIds = deps.getAllowedIds;
706
823
  this.getResolvedSnaps = deps.getResolvedSnaps;
707
824
  this.snapToFn = deps.snapTo;
825
+ this.closeFn = deps.close;
708
826
  this.scrimMode = opts.scrimMode ?? "full";
709
827
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
710
828
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
@@ -846,11 +964,15 @@ var ScrimController = class {
846
964
  }
847
965
  const onClick = () => {
848
966
  if (this.isDestroyed()) return;
967
+ if (this.closeFn) {
968
+ this.closeFn();
969
+ return;
970
+ }
849
971
  const allowed = this.getAllowedIds();
850
972
  const snaps = this.getResolvedSnaps();
851
973
  const fallback = allowed.find((id) => {
852
974
  const snap = findById(id, snaps);
853
- return snap !== null && snap.size > 0;
975
+ return snap !== null && snap.size === 0;
854
976
  });
855
977
  if (fallback) this.snapToFn(fallback);
856
978
  };
@@ -867,6 +989,7 @@ var ScrimController = class {
867
989
  if (this.isDestroyed()) return;
868
990
  if (enabled === this.scrimEnabled) return;
869
991
  this.scrimEnabled = enabled;
992
+ this.invalidateOpacityCache();
870
993
  if (!enabled) {
871
994
  this.savedScrimRanges = {
872
995
  screen: this.screenRange,
@@ -890,7 +1013,12 @@ var ScrimController = class {
890
1013
  blur: cfg.blur ?? null,
891
1014
  interactive: cfg.interactive
892
1015
  });
893
- this.screenRange = cfg.range;
1016
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1017
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1018
+ } else {
1019
+ this.screenRange = cfg.range;
1020
+ this.backdropRange = cfg.range;
1021
+ }
894
1022
  }
895
1023
  const noop = () => {
896
1024
  };
@@ -940,59 +1068,7 @@ var ScrimController = class {
940
1068
  wrapper.className = "bs-scrim-overlay";
941
1069
  const ws = wrapper.style;
942
1070
  ws.position = "absolute";
943
- switch (position) {
944
- case "top-left":
945
- ws.top = inset;
946
- ws.left = inset;
947
- break;
948
- case "top-center":
949
- ws.top = inset;
950
- ws.left = "50%";
951
- ws.transform = "translateX(-50%)";
952
- break;
953
- case "top-right":
954
- ws.top = inset;
955
- ws.right = inset;
956
- break;
957
- case "center-left":
958
- ws.top = "50%";
959
- ws.left = inset;
960
- ws.transform = "translateY(-50%)";
961
- break;
962
- case "center":
963
- ws.top = "50%";
964
- ws.left = "50%";
965
- ws.transform = "translate(-50%, -50%)";
966
- break;
967
- case "center-right":
968
- ws.top = "50%";
969
- ws.right = inset;
970
- ws.transform = "translateY(-50%)";
971
- break;
972
- case "bottom-left":
973
- ws.bottom = inset;
974
- ws.left = inset;
975
- break;
976
- case "bottom-center":
977
- ws.bottom = inset;
978
- ws.left = "50%";
979
- ws.transform = "translateX(-50%)";
980
- break;
981
- case "bottom-right":
982
- ws.bottom = inset;
983
- ws.right = inset;
984
- break;
985
- case "sheet-top-left":
986
- case "sheet-top-center":
987
- case "sheet-top-right":
988
- applySheetAnchoredPosition(
989
- ws,
990
- this.mode,
991
- position,
992
- inset
993
- );
994
- break;
995
- }
1071
+ applyOverlayPosition(ws, this.mode, position, inset);
996
1072
  if (interactive) {
997
1073
  ws.pointerEvents = "auto";
998
1074
  }
@@ -1051,6 +1127,7 @@ var ScrimController = class {
1051
1127
  }
1052
1128
  }
1053
1129
  if (opts.interactive !== void 0) {
1130
+ this.scrimInteractive = opts.interactive;
1054
1131
  s.pointerEvents = opts.interactive ? "auto" : "none";
1055
1132
  }
1056
1133
  }
@@ -1065,38 +1142,9 @@ var ScrimController = class {
1065
1142
  const s = this.screenComponent.style;
1066
1143
  s.position = "fixed";
1067
1144
  s.inset = insetByMode[this.mode];
1068
- s.pointerEvents = "none";
1145
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1069
1146
  }
1070
1147
  };
1071
- function resolveSheetAnchoredStyle(mode, position, inset) {
1072
- const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1073
- switch (mode) {
1074
- case "bottom":
1075
- if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1076
- if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1077
- return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1078
- case "top":
1079
- if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1080
- if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1081
- return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1082
- case "left":
1083
- if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1084
- if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1085
- return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1086
- case "right":
1087
- if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1088
- if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1089
- return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1090
- }
1091
- }
1092
- function applySheetAnchoredPosition(ws, mode, position, inset) {
1093
- const style = resolveSheetAnchoredStyle(mode, position, inset);
1094
- if (style.top !== void 0) ws.top = style.top;
1095
- if (style.bottom !== void 0) ws.bottom = style.bottom;
1096
- if (style.left !== void 0) ws.left = style.left;
1097
- if (style.right !== void 0) ws.right = style.right;
1098
- if (style.transform !== void 0) ws.transform = style.transform;
1099
- }
1100
1148
 
1101
1149
  // src/core/animation/animation.ts
1102
1150
  var easeOutBack = (t) => {
@@ -1182,14 +1230,20 @@ var runSpring = ({
1182
1230
  if (cancelled) return;
1183
1231
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1184
1232
  lastTime = now;
1185
- const subDt = Math.min(dt, 1 / 240);
1186
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1233
+ const steps = Math.max(1, Math.ceil(dt * 240));
1234
+ const subDt = dt / steps;
1235
+ let stepsLeft = steps;
1187
1236
  while (stepsLeft-- > 0) {
1188
1237
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1189
1238
  const accel = force / cfg.mass;
1190
1239
  v += accel * subDt;
1191
1240
  x += v * subDt;
1192
1241
  }
1242
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1243
+ onUpdate(to, 0);
1244
+ resolve();
1245
+ return;
1246
+ }
1193
1247
  onUpdate(x, v);
1194
1248
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1195
1249
  if (isAtRest) {
@@ -1364,9 +1418,13 @@ var isVisible = (el) => {
1364
1418
  var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1365
1419
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1366
1420
  );
1421
+ var trapStack = [];
1367
1422
  var installFocusTrap = (container, options = {}) => {
1368
1423
  if (typeof document === "undefined") return () => {
1369
1424
  };
1425
+ const token = {};
1426
+ trapStack.push(token);
1427
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1370
1428
  const previouslyFocused = document.activeElement;
1371
1429
  const focusInitial = () => {
1372
1430
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1374,6 +1432,7 @@ var installFocusTrap = (container, options = {}) => {
1374
1432
  };
1375
1433
  focusInitial();
1376
1434
  const handleKey = (e) => {
1435
+ if (!isActive()) return;
1377
1436
  if (e.key === "Escape" && options.onEscape) {
1378
1437
  options.onEscape();
1379
1438
  return;
@@ -1396,6 +1455,7 @@ var installFocusTrap = (container, options = {}) => {
1396
1455
  }
1397
1456
  };
1398
1457
  const handleFocusIn = (e) => {
1458
+ if (!isActive()) return;
1399
1459
  const target = e.target;
1400
1460
  if (target && !container.contains(target)) {
1401
1461
  const list = focusables(container);
@@ -1408,6 +1468,8 @@ var installFocusTrap = (container, options = {}) => {
1408
1468
  return () => {
1409
1469
  if (released) return;
1410
1470
  released = true;
1471
+ const idx = trapStack.indexOf(token);
1472
+ if (idx !== -1) trapStack.splice(idx, 1);
1411
1473
  document.removeEventListener("keydown", handleKey, true);
1412
1474
  document.removeEventListener("focusin", handleFocusIn, true);
1413
1475
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1463,11 +1525,17 @@ function createInertSiblings(rootProvider) {
1463
1525
  return {
1464
1526
  apply() {
1465
1527
  if (typeof document === "undefined") return;
1466
- const root = rootProvider();
1467
- const parent = root.parentElement ?? document.body;
1528
+ let top = rootProvider();
1529
+ while (top.parentElement && top.parentElement !== document.body) {
1530
+ top = top.parentElement;
1531
+ }
1532
+ const parent = top.parentElement ?? document.body;
1468
1533
  for (const child of Array.from(parent.children)) {
1469
- if (child === root) continue;
1534
+ if (child === top) continue;
1470
1535
  if (child.hasAttribute("inert")) continue;
1536
+ if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
1537
+ continue;
1538
+ }
1471
1539
  child.setAttribute("inert", "");
1472
1540
  tracked.push(child);
1473
1541
  }
@@ -1483,7 +1551,6 @@ function createInertSiblings(rootProvider) {
1483
1551
  var LifecycleController = class {
1484
1552
  constructor(deps, opts) {
1485
1553
  __publicField(this, "element");
1486
- __publicField(this, "closeFn");
1487
1554
  __publicField(this, "focusTrapEnabled");
1488
1555
  __publicField(this, "initialFocus");
1489
1556
  __publicField(this, "closeOnEscape");
@@ -1497,7 +1564,6 @@ var LifecycleController = class {
1497
1564
  __publicField(this, "installed", false);
1498
1565
  __publicField(this, "destroyed", false);
1499
1566
  this.element = deps.element;
1500
- this.closeFn = deps.close;
1501
1567
  this.focusTrapEnabled = opts.focusTrap ?? false;
1502
1568
  this.initialFocus = opts.initialFocus;
1503
1569
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1507,6 +1573,9 @@ var LifecycleController = class {
1507
1573
  this.returnFocus = opts.returnFocus;
1508
1574
  this.inertSiblings = createInertSiblings(() => this.element);
1509
1575
  }
1576
+ get isInstalled() {
1577
+ return this.installed;
1578
+ }
1510
1579
  install() {
1511
1580
  if (this.destroyed) return;
1512
1581
  this.installed = true;
@@ -1515,8 +1584,7 @@ var LifecycleController = class {
1515
1584
  }
1516
1585
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1517
1586
  this.releaseFocusTrap = installFocusTrap(this.element, {
1518
- initialFocus: this.initialFocus,
1519
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1587
+ initialFocus: this.initialFocus
1520
1588
  });
1521
1589
  }
1522
1590
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1671,10 +1739,11 @@ var installGestures = (handle, mode, callbacks) => {
1671
1739
  var attachGestures = installGestures;
1672
1740
 
1673
1741
  // src/core/primitives/rubber-band.ts
1674
- var RUBBER_BAND_FACTOR = 0.55;
1675
1742
  function rubberBand(overshoot, maxAxisSize) {
1676
1743
  const cap = Math.min(maxAxisSize * 0.15, 80);
1677
- return Math.sign(overshoot) * Math.pow(Math.abs(overshoot), RUBBER_BAND_FACTOR) * Math.min(6, cap / Math.max(Math.abs(overshoot), 1));
1744
+ if (cap <= 0) return 0;
1745
+ const abs = Math.abs(overshoot);
1746
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1678
1747
  }
1679
1748
 
1680
1749
  // src/core/features/soft-keyboard.ts
@@ -1759,6 +1828,10 @@ var GestureController = class {
1759
1828
  this.isDragging_ = false;
1760
1829
  this.keyboardDismissed = false;
1761
1830
  this.deps.getRoot()?.removeAttribute("data-dragging");
1831
+ this.deps.emit("dragend", {
1832
+ size: this.deps.getDragContext().size,
1833
+ velocity: 0
1834
+ });
1762
1835
  const restoreTo = this.dragStartSize;
1763
1836
  void this.deps.animateTo(restoreTo, 0);
1764
1837
  }
@@ -1832,6 +1905,287 @@ function createScrollCache(deps) {
1832
1905
  };
1833
1906
  }
1834
1907
 
1908
+ // src/core/primitives/anchor-animations.ts
1909
+ var DEFAULT_DURATION3 = 200;
1910
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1911
+ var PRESET_FRAMES = {
1912
+ fade: {
1913
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1914
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1915
+ },
1916
+ scale: {
1917
+ enter: [
1918
+ { opacity: 0, transform: "scale(0.85)" },
1919
+ { opacity: 1, transform: "scale(1)" }
1920
+ ],
1921
+ exit: [
1922
+ { opacity: 1, transform: "scale(1)" },
1923
+ { opacity: 0, transform: "scale(0.85)" }
1924
+ ]
1925
+ },
1926
+ slide: {
1927
+ enter: [
1928
+ { opacity: 0, transform: "translateY(10px)" },
1929
+ { opacity: 1, transform: "translateY(0)" }
1930
+ ],
1931
+ exit: [
1932
+ { opacity: 1, transform: "translateY(0)" },
1933
+ { opacity: 0, transform: "translateY(10px)" }
1934
+ ]
1935
+ },
1936
+ pop: {
1937
+ enter: [
1938
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
1939
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
1940
+ { opacity: 1, transform: "scale(1)", offset: 1 }
1941
+ ],
1942
+ exit: [
1943
+ { opacity: 1, transform: "scale(1)" },
1944
+ { opacity: 0, transform: "scale(0.6)" }
1945
+ ]
1946
+ }
1947
+ };
1948
+ var INSTANT = {
1949
+ finished: Promise.resolve(),
1950
+ cancel: () => {
1951
+ }
1952
+ };
1953
+ function resolveTransitionFrames(spec, phase) {
1954
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
1955
+ if (spec === "none") return fallback;
1956
+ if (spec === void 0 || typeof spec === "string") {
1957
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
1958
+ return {
1959
+ frames: preset2[phase],
1960
+ duration: DEFAULT_DURATION3,
1961
+ easing: DEFAULT_EASING
1962
+ };
1963
+ }
1964
+ const custom = phase === "enter" ? spec.enter : spec.exit;
1965
+ if (custom) {
1966
+ return {
1967
+ frames: custom,
1968
+ duration: spec.duration ?? DEFAULT_DURATION3,
1969
+ easing: spec.easing ?? DEFAULT_EASING
1970
+ };
1971
+ }
1972
+ if (spec.preset === "none") return fallback;
1973
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
1974
+ return {
1975
+ frames: preset[phase],
1976
+ duration: spec.duration ?? DEFAULT_DURATION3,
1977
+ easing: spec.easing ?? DEFAULT_EASING
1978
+ };
1979
+ }
1980
+ function runAnchorTransition(el, spec, phase) {
1981
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
1982
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
1983
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
1984
+ if (!frames || duration <= 0) return INSTANT;
1985
+ if (typeof el.animate !== "function") return INSTANT;
1986
+ const animation = el.animate(frames, {
1987
+ duration,
1988
+ easing,
1989
+ fill: "both"
1990
+ });
1991
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
1992
+ return {
1993
+ finished,
1994
+ cancel: () => {
1995
+ try {
1996
+ animation.cancel();
1997
+ } catch {
1998
+ return;
1999
+ }
2000
+ }
2001
+ };
2002
+ }
2003
+
2004
+ // src/core/features/sheet-anchors.ts
2005
+ function attachAnchor(deps, opts) {
2006
+ const doc = deps.host.ownerDocument;
2007
+ const wrapper = doc.createElement("div");
2008
+ wrapper.className = "bs-anchor";
2009
+ const interactive = opts.interactive ?? true;
2010
+ const position = opts.position ?? "sheet-top-right";
2011
+ const isDock = position === "dock-bottom" || position === "dock-top";
2012
+ const ws = wrapper.style;
2013
+ ws.position = "fixed";
2014
+ if (isDock) {
2015
+ ws.left = "0";
2016
+ ws.right = "0";
2017
+ if (position === "dock-bottom") {
2018
+ ws.bottom = "0";
2019
+ } else {
2020
+ ws.top = "0";
2021
+ }
2022
+ } else {
2023
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2024
+ }
2025
+ if (opts.fadeRange) {
2026
+ const [r0, r1] = opts.fadeRange;
2027
+ const span = Math.max(r1 - r0, 1e-4);
2028
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2029
+ }
2030
+ wrapper.appendChild(opts.element);
2031
+ deps.host.appendChild(wrapper);
2032
+ let visible = null;
2033
+ let inFlight = null;
2034
+ const shouldShow = (state) => {
2035
+ if (typeof opts.showOn === "function") return opts.showOn(state);
2036
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
2037
+ return isDock || state.size > 0;
2038
+ };
2039
+ const applyVisibility = (next, animate) => {
2040
+ if (next === visible) return;
2041
+ visible = next;
2042
+ inFlight?.cancel();
2043
+ inFlight = null;
2044
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2045
+ if (next) {
2046
+ wrapper.style.visibility = "";
2047
+ if (animate) {
2048
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2049
+ }
2050
+ return;
2051
+ }
2052
+ if (!animate) {
2053
+ wrapper.style.visibility = "hidden";
2054
+ return;
2055
+ }
2056
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2057
+ inFlight = handle;
2058
+ void handle.finished.then(() => {
2059
+ if (inFlight === handle && visible === false) {
2060
+ wrapper.style.visibility = "hidden";
2061
+ }
2062
+ });
2063
+ };
2064
+ const evaluate = (animate) => {
2065
+ if (deps.isDestroyed()) return;
2066
+ applyVisibility(shouldShow(deps.getState()), animate);
2067
+ };
2068
+ const offs = [
2069
+ deps.on("snap", () => evaluate(true)),
2070
+ deps.on("open", () => evaluate(true)),
2071
+ deps.on("close", () => evaluate(true))
2072
+ ];
2073
+ evaluate(false);
2074
+ return {
2075
+ wrapper,
2076
+ detach: () => {
2077
+ offs.forEach((off) => off());
2078
+ inFlight?.cancel();
2079
+ inFlight = null;
2080
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2081
+ },
2082
+ syncZ: (z) => {
2083
+ wrapper.style.zIndex = String(z);
2084
+ }
2085
+ };
2086
+ }
2087
+
2088
+ // src/core/features/scrim-stages.ts
2089
+ var matchesId = (def, activeId) => {
2090
+ if (def.for === void 0) return false;
2091
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2092
+ };
2093
+ var matchesRange = (def, progress) => {
2094
+ if (!def.forRange) return false;
2095
+ const [from, to] = def.forRange;
2096
+ return progress >= from && progress <= to;
2097
+ };
2098
+ function installScrimStages(deps, opts) {
2099
+ const doc = deps.host.ownerDocument;
2100
+ const entries = opts.stages.map((def) => {
2101
+ const wrapper = doc.createElement("div");
2102
+ wrapper.className = "bs-scrim-stage";
2103
+ const ws = wrapper.style;
2104
+ ws.position = "absolute";
2105
+ applyOverlayPosition(
2106
+ ws,
2107
+ deps.mode,
2108
+ def.position ?? opts.position ?? "center",
2109
+ def.inset ?? opts.inset ?? "16px"
2110
+ );
2111
+ ws.visibility = "hidden";
2112
+ ws.pointerEvents = "none";
2113
+ wrapper.appendChild(def.element);
2114
+ deps.host.appendChild(wrapper);
2115
+ return { def, wrapper, inFlight: null };
2116
+ });
2117
+ let active = null;
2118
+ const resolveActive = () => {
2119
+ const state = deps.getState();
2120
+ if (state.size === 0) {
2121
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2122
+ }
2123
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2124
+ };
2125
+ const hide = (entry, animate) => {
2126
+ entry.inFlight?.cancel();
2127
+ entry.wrapper.style.pointerEvents = "none";
2128
+ if (!animate) {
2129
+ entry.inFlight = null;
2130
+ entry.wrapper.style.visibility = "hidden";
2131
+ return;
2132
+ }
2133
+ const handle = runAnchorTransition(
2134
+ entry.def.element,
2135
+ entry.def.animation ?? opts.animation,
2136
+ "exit"
2137
+ );
2138
+ entry.inFlight = handle;
2139
+ void handle.finished.then(() => {
2140
+ if (entry.inFlight === handle && active !== entry) {
2141
+ entry.wrapper.style.visibility = "hidden";
2142
+ }
2143
+ });
2144
+ };
2145
+ const show = (entry, animate) => {
2146
+ entry.inFlight?.cancel();
2147
+ entry.inFlight = null;
2148
+ entry.wrapper.style.visibility = "";
2149
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2150
+ if (animate) {
2151
+ entry.inFlight = runAnchorTransition(
2152
+ entry.def.element,
2153
+ entry.def.animation ?? opts.animation,
2154
+ "enter"
2155
+ );
2156
+ }
2157
+ };
2158
+ const update = (animate) => {
2159
+ if (deps.isDestroyed()) return;
2160
+ const next = resolveActive();
2161
+ if (next === active) return;
2162
+ const prev = active;
2163
+ active = next;
2164
+ if (prev) hide(prev, animate);
2165
+ if (next) show(next, animate);
2166
+ };
2167
+ const offs = [
2168
+ deps.on("snap", () => update(true)),
2169
+ deps.on("open", () => update(true)),
2170
+ deps.on("close", () => update(true))
2171
+ ];
2172
+ if (entries.some((e) => e.def.forRange)) {
2173
+ offs.push(deps.on("progress", () => update(true)));
2174
+ }
2175
+ update(false);
2176
+ return () => {
2177
+ offs.forEach((off) => off());
2178
+ for (const entry of entries) {
2179
+ entry.inFlight?.cancel();
2180
+ entry.inFlight = null;
2181
+ if (entry.wrapper.parentNode) {
2182
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2183
+ }
2184
+ }
2185
+ active = null;
2186
+ };
2187
+ }
2188
+
1835
2189
  // src/core/primitives/engine-options.ts
1836
2190
  var DEFAULT_FLICK_VELOCITY = 0.65;
1837
2191
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1948,6 +2302,12 @@ var BottomSheetEngine = class {
1948
2302
  __publicField(this, "linkedSheets", []);
1949
2303
  __publicField(this, "scrollCache");
1950
2304
  __publicField(this, "teardowns", new TeardownStack());
2305
+ __publicField(this, "anchors", []);
2306
+ __publicField(this, "anchorHost", null);
2307
+ __publicField(this, "stackZ", 100);
2308
+ __publicField(this, "detachScrimStages", null);
2309
+ __publicField(this, "stackEffectEnabled", false);
2310
+ __publicField(this, "stackEffectPrimed", false);
1951
2311
  __publicField(this, "transformTemplate");
1952
2312
  const resolved = resolveEngineOptions(opts, {
1953
2313
  shouldApplyInertSiblings: () => {
@@ -1981,6 +2341,9 @@ var BottomSheetEngine = class {
1981
2341
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1982
2342
  snapTo: (id) => {
1983
2343
  void this.snapTo(id);
2344
+ },
2345
+ close: () => {
2346
+ void this.close();
1984
2347
  }
1985
2348
  },
1986
2349
  resolved.scrim
@@ -2004,6 +2367,7 @@ var BottomSheetEngine = class {
2004
2367
  );
2005
2368
  this.closeOnBack = resolved.closeOnBack;
2006
2369
  this.routedTo = opts.routedTo;
2370
+ this.stackEffectEnabled = opts.stackEffect ?? false;
2007
2371
  this.snapPointsRaw = opts.snapPoints;
2008
2372
  this.persistKey = opts.persistKey;
2009
2373
  if (opts.linkedSheets) {
@@ -2016,7 +2380,7 @@ var BottomSheetEngine = class {
2016
2380
  this.snapPointsRaw,
2017
2381
  resolved.initialAllowed,
2018
2382
  this.mode,
2019
- () => this.handle.offsetHeight,
2383
+ () => layoutAxis(this.mode) === "width" ? this.handle.offsetWidth : this.handle.offsetHeight,
2020
2384
  (maxAxisSize) => {
2021
2385
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2022
2386
  }
@@ -2226,11 +2590,6 @@ var BottomSheetEngine = class {
2226
2590
  }
2227
2591
  setAllowed(ids, snap) {
2228
2592
  if (this.destroyed) return;
2229
- const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
2230
- if (!willSnap) {
2231
- this.animation.cancel();
2232
- this.newCycle();
2233
- }
2234
2593
  this.snaps.setAllowedIds(ids);
2235
2594
  this.updateAriaSlider();
2236
2595
  if (snap && ids.includes(snap)) {
@@ -2270,6 +2629,79 @@ var BottomSheetEngine = class {
2270
2629
  setScrimOverlay(opts) {
2271
2630
  return this.scrim.setScrimOverlay(opts);
2272
2631
  }
2632
+ addAnchor(opts) {
2633
+ if (this.destroyed || typeof document === "undefined") return () => {
2634
+ };
2635
+ if (!this.anchorHost) {
2636
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2637
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2638
+ this.anchorHost.style.setProperty(
2639
+ "--bs-progress",
2640
+ String(this.computeProgress(this.size))
2641
+ );
2642
+ }
2643
+ const handle = attachAnchor(
2644
+ {
2645
+ mode: this.mode,
2646
+ host: this.anchorHost,
2647
+ getState: () => ({
2648
+ activeId: this.activeId,
2649
+ size: this.size,
2650
+ progress: this.computeProgress(this.size)
2651
+ }),
2652
+ on: (event, fn) => this.on(event, fn),
2653
+ isDestroyed: () => this.destroyed
2654
+ },
2655
+ opts
2656
+ );
2657
+ handle.syncZ(this.stackZ + 1);
2658
+ this.anchors.push(handle);
2659
+ const detach = () => {
2660
+ const idx = this.anchors.indexOf(handle);
2661
+ if (idx === -1) return;
2662
+ this.anchors.splice(idx, 1);
2663
+ handle.detach();
2664
+ };
2665
+ this.teardowns.add(detach);
2666
+ return detach;
2667
+ }
2668
+ setScrimStages(opts) {
2669
+ if (this.destroyed || typeof document === "undefined") return () => {
2670
+ };
2671
+ this.detachScrimStages?.();
2672
+ this.detachScrimStages = null;
2673
+ if (!opts) return () => {
2674
+ };
2675
+ const host = this.screenComponent?.parentElement;
2676
+ if (!host) {
2677
+ console.warn(
2678
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2679
+ );
2680
+ return () => {
2681
+ };
2682
+ }
2683
+ const detachInstalled = installScrimStages(
2684
+ {
2685
+ mode: this.mode,
2686
+ host,
2687
+ getState: () => ({
2688
+ activeId: this.activeId,
2689
+ size: this.size,
2690
+ progress: this.computeProgress(this.size)
2691
+ }),
2692
+ on: (event, fn) => this.on(event, fn),
2693
+ isDestroyed: () => this.destroyed
2694
+ },
2695
+ opts
2696
+ );
2697
+ const detach = () => {
2698
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2699
+ detachInstalled();
2700
+ };
2701
+ this.detachScrimStages = detach;
2702
+ this.teardowns.add(() => this.detachScrimStages?.());
2703
+ return detach;
2704
+ }
2273
2705
  getScrimState() {
2274
2706
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2275
2707
  }
@@ -2356,24 +2788,45 @@ var BottomSheetEngine = class {
2356
2788
  getAllowedRange() {
2357
2789
  return this.snaps.getAllowedRange();
2358
2790
  }
2791
+ allowedIdsBySize() {
2792
+ return this.snaps.getAllowedIds().slice().sort(
2793
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
2794
+ );
2795
+ }
2359
2796
  updateAriaSlider() {
2360
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
2797
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2361
2798
  }
2362
2799
  registerInStack() {
2363
2800
  this.teardowns.add(sheetStack.push({
2364
2801
  id: this.id,
2365
2802
  setZIndex: (z) => {
2803
+ this.stackZ = z;
2366
2804
  this.element.style.zIndex = String(z);
2367
2805
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
2806
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2368
2807
  },
2369
2808
  setIsTop: (isTop) => {
2370
2809
  this.isTopSheet = isTop;
2371
2810
  if (this.backdrop) {
2372
2811
  this.backdrop.style.display = isTop ? "" : "none";
2373
2812
  }
2374
- }
2813
+ },
2814
+ isOpen: () => this.size > 0,
2815
+ setDepth: (depth) => this.applyStackDepth(depth)
2375
2816
  }));
2376
2817
  }
2818
+ applyStackDepth(depth) {
2819
+ if (!this.stackEffectEnabled) return;
2820
+ this.element.setAttribute("data-stack-depth", String(depth));
2821
+ if (!this.stackEffectPrimed) {
2822
+ this.stackEffectPrimed = true;
2823
+ const isVertical = this.mode === "bottom" || this.mode === "top";
2824
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
2825
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
2826
+ }
2827
+ const scale = Math.max(1 - depth * 0.04, 0.86);
2828
+ this.element.style.scale = scale === 1 ? "" : String(scale);
2829
+ }
2377
2830
  attach() {
2378
2831
  this.gesture = new GestureController({
2379
2832
  handle: this.handle,
@@ -2433,7 +2886,7 @@ var BottomSheetEngine = class {
2433
2886
  container: this.scrollContainer,
2434
2887
  isDragging: () => this.gesture?.isDragging ?? false,
2435
2888
  isAnimating: () => this.animation.isAnimating,
2436
- getAllowedIds: () => this.snaps.getAllowedIds(),
2889
+ getAllowedIds: () => this.allowedIdsBySize(),
2437
2890
  getActiveId: () => this.activeId,
2438
2891
  snapTo: (id) => {
2439
2892
  void this.snapTo(id);
@@ -2462,11 +2915,13 @@ var BottomSheetEngine = class {
2462
2915
  },
2463
2916
  newCycle: () => {
2464
2917
  this.newCycle();
2465
- }
2918
+ },
2919
+ isAnimating: () => this.animation.isAnimating,
2920
+ resyncAfterCancel: () => this.resyncAfterResize()
2466
2921
  }));
2467
2922
  if (this.lifecycle.closeOnEscape) {
2468
2923
  const onKey = (e) => {
2469
- if (this.destroyed) return;
2924
+ if (this.destroyed || e.defaultPrevented) return;
2470
2925
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2471
2926
  void this.close();
2472
2927
  }
@@ -2498,7 +2953,7 @@ var BottomSheetEngine = class {
2498
2953
  handle: this.handle,
2499
2954
  mode: this.mode,
2500
2955
  isDestroyed: () => this.destroyed,
2501
- getAllowedIds: () => this.snaps.getAllowedIds(),
2956
+ getAllowedIds: () => this.allowedIdsBySize(),
2502
2957
  getActiveId: () => this.activeId,
2503
2958
  snapTo: (id) => {
2504
2959
  void this.snapTo(id);
@@ -2519,6 +2974,7 @@ var BottomSheetEngine = class {
2519
2974
  });
2520
2975
  if (!target) return;
2521
2976
  const previousId = this.activeId;
2977
+ if (target.id === previousId && target.size === this.size) return;
2522
2978
  if (this.emitBeforeSnap(target, previousId)) {
2523
2979
  const restore = this.snaps.findById(previousId);
2524
2980
  if (restore) {
@@ -2527,6 +2983,7 @@ var BottomSheetEngine = class {
2527
2983
  return;
2528
2984
  }
2529
2985
  const previousSize = this.size;
2986
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2530
2987
  this.scrollCache.cache(previousId, previousSize, target.size);
2531
2988
  this.activeId = target.id;
2532
2989
  void this.animation.animateTo(target.size, velocity).then(() => {
@@ -2535,9 +2992,10 @@ var BottomSheetEngine = class {
2535
2992
  this.updateAriaSlider();
2536
2993
  this.emit("snap", { id: target.id, size: target.size });
2537
2994
  this.haptic();
2538
- if (previousId === "closed" && target.size > 0) {
2995
+ if (previousSnapSize === 0 && target.size > 0) {
2539
2996
  this.emit("open", { id: target.id });
2540
2997
  this.handleOpen();
2998
+ notifyLinkedSheets(this.linkedSheets, this);
2541
2999
  }
2542
3000
  if (target.size === 0) {
2543
3001
  this.emit("close", void 0);
@@ -2563,6 +3021,9 @@ var BottomSheetEngine = class {
2563
3021
  if (this.scrimParent) {
2564
3022
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2565
3023
  }
3024
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3025
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3026
+ }
2566
3027
  }
2567
3028
  const progress = this.computeProgress(size);
2568
3029
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2571,6 +3032,9 @@ var BottomSheetEngine = class {
2571
3032
  );
2572
3033
  if (progressChanged) {
2573
3034
  style.setProperty("--bs-progress", String(progress));
3035
+ if (this.anchorHost) {
3036
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3037
+ }
2574
3038
  }
2575
3039
  this.scrim.applyOpacity(progress, progressChanged);
2576
3040
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2585,6 +3049,7 @@ var BottomSheetEngine = class {
2585
3049
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2586
3050
  }
2587
3051
  handleOpen() {
3052
+ sheetStack.promote(this.id);
2588
3053
  try {
2589
3054
  this.lifecycle.install();
2590
3055
  } catch (err) {
@@ -2598,8 +3063,24 @@ var BottomSheetEngine = class {
2598
3063
  });
2599
3064
  }
2600
3065
  }
3066
+ resyncAfterResize() {
3067
+ if (this.destroyed) return;
3068
+ const target = this.snaps.findById(this.activeId);
3069
+ if (!target) return;
3070
+ this.updateAriaSlider();
3071
+ this.emit("snap", { id: target.id, size: target.size });
3072
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3073
+ this.emit("open", { id: target.id });
3074
+ this.handleOpen();
3075
+ notifyLinkedSheets(this.linkedSheets, this);
3076
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3077
+ this.emit("close", void 0);
3078
+ this.handleClose();
3079
+ }
3080
+ }
2601
3081
  handleClose() {
2602
3082
  this.lifecycle.release();
3083
+ sheetStack.update();
2603
3084
  }
2604
3085
  };
2605
3086
 
@@ -2708,6 +3189,10 @@ var OverlayEngine = class {
2708
3189
  __publicField(this, "closeOnOutsidePointerEnabled");
2709
3190
  __publicField(this, "detachOutsidePointer", null);
2710
3191
  __publicField(this, "detachSwipe", null);
3192
+ __publicField(this, "respectReducedMotionEnabled");
3193
+ __publicField(this, "isTop", true);
3194
+ __publicField(this, "backPushed", false);
3195
+ __publicField(this, "movedToTarget", false);
2711
3196
  __publicField(this, "onOpenCb");
2712
3197
  __publicField(this, "onCloseCb");
2713
3198
  __publicField(this, "isOpen_", false);
@@ -2744,6 +3229,7 @@ var OverlayEngine = class {
2744
3229
  this.mountTarget = opts.mountTo;
2745
3230
  this.peekRaw = opts.peek;
2746
3231
  this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
3232
+ this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
2747
3233
  this.onOpenCb = opts.onOpen;
2748
3234
  this.onCloseCb = opts.onClose;
2749
3235
  if (this.peekRaw !== void 0 && this.edge !== "bottom") {
@@ -2791,15 +3277,17 @@ var OverlayEngine = class {
2791
3277
  this.emit("before-open", void 0);
2792
3278
  this.isOpen_ = true;
2793
3279
  this.isAnimating_ = true;
3280
+ sheetStack.promote(this.id);
2794
3281
  this.element.removeAttribute("hidden");
2795
3282
  this.element.setAttribute("data-state", "entering");
2796
3283
  if (this.focusTrapEnabled) {
2797
3284
  this.element.setAttribute("aria-modal", "true");
2798
3285
  }
2799
- const transitionStr = `transform ${this.duration}ms ${this.enterEasing}, opacity ${this.duration}ms ease-out`;
3286
+ const dur = this.effectiveDuration();
3287
+ const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
2800
3288
  this.element.style.transition = transitionStr;
2801
3289
  if (this.backdrop) {
2802
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-out`;
3290
+ this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
2803
3291
  }
2804
3292
  const cycle = ++this.cycleNonce;
2805
3293
  return new Promise((resolve) => {
@@ -2811,7 +3299,7 @@ var OverlayEngine = class {
2811
3299
  this.element.style.opacity = "1";
2812
3300
  if (this.backdrop) {
2813
3301
  this.backdrop.style.opacity = String(this.backdropOpacity);
2814
- this.backdrop.style.pointerEvents = "auto";
3302
+ this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
2815
3303
  if (this.backdropFilter) {
2816
3304
  this.backdrop.style.backdropFilter = this.backdropFilter;
2817
3305
  this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
@@ -2847,7 +3335,10 @@ var OverlayEngine = class {
2847
3335
  });
2848
3336
  return resolve();
2849
3337
  }
2850
- this.awaitTransition(cycle).then(() => {
3338
+ this.awaitTransition(
3339
+ cycle,
3340
+ this.enterAnimation === "fade" ? "opacity" : "transform"
3341
+ ).then(() => {
2851
3342
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
2852
3343
  this.isAnimating_ = false;
2853
3344
  this.element.setAttribute("data-state", "open");
@@ -2864,11 +3355,14 @@ var OverlayEngine = class {
2864
3355
  this.emit("before-close", void 0);
2865
3356
  this.isOpen_ = false;
2866
3357
  this.isAnimating_ = true;
3358
+ sheetStack.update();
3359
+ this.popBackMarker(reason);
2867
3360
  this.element.setAttribute("data-state", "exiting");
2868
- const transitionStr = `transform ${this.duration}ms ${this.exitEasing}, opacity ${this.duration}ms ease-in`;
3361
+ const dur = this.effectiveDuration();
3362
+ const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
2869
3363
  this.element.style.transition = transitionStr;
2870
3364
  if (this.backdrop) {
2871
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-in`;
3365
+ this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
2872
3366
  }
2873
3367
  const cycle = ++this.cycleNonce;
2874
3368
  return new Promise((resolve) => {
@@ -2879,7 +3373,7 @@ var OverlayEngine = class {
2879
3373
  this.exitAnimation,
2880
3374
  this.peekPx
2881
3375
  );
2882
- this.element.style.opacity = "0";
3376
+ this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
2883
3377
  if (this.backdrop) {
2884
3378
  this.backdrop.style.opacity = "0";
2885
3379
  this.backdrop.style.pointerEvents = "none";
@@ -2889,7 +3383,10 @@ var OverlayEngine = class {
2889
3383
  }
2890
3384
  }
2891
3385
  this.releaseInteractiveListeners();
2892
- this.awaitTransition(cycle).then(() => {
3386
+ this.awaitTransition(
3387
+ cycle,
3388
+ this.exitAnimation === "fade" ? "opacity" : "transform"
3389
+ ).then(() => {
2893
3390
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
2894
3391
  this.isAnimating_ = false;
2895
3392
  if (this.peekPx === void 0) {
@@ -2917,7 +3414,11 @@ var OverlayEngine = class {
2917
3414
  this.releaseInteractiveListeners();
2918
3415
  this.releaseStackEntry?.();
2919
3416
  this.releaseStackEntry = null;
3417
+ const hadOriginalParent = this.originalParent !== null;
2920
3418
  this.restoreMount();
3419
+ if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
3420
+ this.element.parentNode.removeChild(this.element);
3421
+ }
2921
3422
  }
2922
3423
  setBackdropOpacity(opacity) {
2923
3424
  if (this.destroyed) return;
@@ -3018,6 +3519,36 @@ var OverlayEngine = class {
3018
3519
  }
3019
3520
  }
3020
3521
  }
3522
+ if (opts.duration !== void 0) this.duration = opts.duration;
3523
+ if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
3524
+ if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
3525
+ if (opts.closeOnEscape !== void 0) {
3526
+ this.closeOnEscape = opts.closeOnEscape;
3527
+ if (!opts.closeOnEscape) {
3528
+ this.detachEscape?.();
3529
+ this.detachEscape = null;
3530
+ } else if (this.isOpen_) {
3531
+ this.installInteractiveListeners();
3532
+ }
3533
+ }
3534
+ if (opts.closeOnBackdrop !== void 0) {
3535
+ this.closeOnBackdropEnabled = opts.closeOnBackdrop;
3536
+ if (!opts.closeOnBackdrop) {
3537
+ this.detachBackdrop?.();
3538
+ this.detachBackdrop = null;
3539
+ } else if (this.isOpen_) {
3540
+ this.installInteractiveListeners();
3541
+ }
3542
+ }
3543
+ if (opts.closeOnOutsidePointer !== void 0) {
3544
+ this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
3545
+ if (!opts.closeOnOutsidePointer) {
3546
+ this.detachOutsidePointer?.();
3547
+ this.detachOutsidePointer = null;
3548
+ } else if (this.isOpen_) {
3549
+ this.installInteractiveListeners();
3550
+ }
3551
+ }
3021
3552
  if (opts.children !== void 0) {
3022
3553
  if (opts.children === null) {
3023
3554
  this.clearOverlayChildren();
@@ -3051,6 +3582,22 @@ var OverlayEngine = class {
3051
3582
  emit(event, payload) {
3052
3583
  this.bus.emit(event, payload);
3053
3584
  }
3585
+ effectiveDuration() {
3586
+ return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
3587
+ }
3588
+ popBackMarker(reason) {
3589
+ if (!this.backPushed) return;
3590
+ this.backPushed = false;
3591
+ if (reason === "back") return;
3592
+ const state = history.state;
3593
+ if (state && state.__bsOverlay === this.id) {
3594
+ try {
3595
+ history.back();
3596
+ } catch {
3597
+ return;
3598
+ }
3599
+ }
3600
+ }
3054
3601
  resolvePeekPx(raw) {
3055
3602
  if (typeof raw === "number") return Math.max(0, raw);
3056
3603
  try {
@@ -3072,6 +3619,7 @@ var OverlayEngine = class {
3072
3619
  this.originalParent = this.element.parentNode;
3073
3620
  this.originalNextSibling = this.element.nextSibling;
3074
3621
  target.appendChild(this.element);
3622
+ this.movedToTarget = true;
3075
3623
  this.mounted = true;
3076
3624
  }
3077
3625
  restoreMount() {
@@ -3105,7 +3653,7 @@ var OverlayEngine = class {
3105
3653
  this.backdrop.style.willChange = "opacity";
3106
3654
  }
3107
3655
  }
3108
- awaitTransition(cycle) {
3656
+ awaitTransition(cycle, property = "transform") {
3109
3657
  return new Promise((resolve) => {
3110
3658
  let done = false;
3111
3659
  const finish = () => {
@@ -3120,10 +3668,10 @@ var OverlayEngine = class {
3120
3668
  finish();
3121
3669
  return;
3122
3670
  }
3123
- if (e.propertyName === "transform") finish();
3671
+ if (e.target === this.element && e.propertyName === property) finish();
3124
3672
  };
3125
3673
  this.element.addEventListener("transitionend", onEnd);
3126
- const timer = window.setTimeout(finish, this.duration + 50);
3674
+ const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
3127
3675
  });
3128
3676
  }
3129
3677
  installInteractiveListeners() {
@@ -3133,7 +3681,10 @@ var OverlayEngine = class {
3133
3681
  }
3134
3682
  if (this.closeOnEscape && !this.detachEscape) {
3135
3683
  const handler = (e) => {
3136
- if (e.key === "Escape" && this.isOpen_) void this.close("escape");
3684
+ if (e.defaultPrevented) return;
3685
+ if (e.key === "Escape" && this.isOpen_ && this.isTop) {
3686
+ void this.close("escape");
3687
+ }
3137
3688
  };
3138
3689
  document.addEventListener("keydown", handler);
3139
3690
  this.detachEscape = () => document.removeEventListener("keydown", handler);
@@ -3146,10 +3697,14 @@ var OverlayEngine = class {
3146
3697
  if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
3147
3698
  try {
3148
3699
  history.pushState({ __bsOverlay: this.id }, "");
3700
+ this.backPushed = true;
3149
3701
  } catch {
3150
3702
  }
3151
3703
  const onPop = () => {
3152
- if (this.isOpen_) void this.close("back");
3704
+ if (this.isOpen_ && this.isTop) {
3705
+ this.backPushed = false;
3706
+ void this.close("back");
3707
+ }
3153
3708
  };
3154
3709
  window.addEventListener("popstate", onPop);
3155
3710
  this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
@@ -3185,43 +3740,52 @@ var OverlayEngine = class {
3185
3740
  const edge = this.edge;
3186
3741
  const isVertical = edge === "bottom" || edge === "top";
3187
3742
  const sign = edge === "bottom" || edge === "right" ? 1 : -1;
3188
- const threshold = this.swipeThreshold;
3189
- const velocityThreshold = this.swipeVelocityThreshold;
3743
+ const SLOP = 8;
3190
3744
  let startX = 0;
3191
3745
  let startY = 0;
3192
3746
  let startT = 0;
3747
+ let tracking = false;
3193
3748
  let dragging = false;
3194
3749
  let pointerId = null;
3750
+ const previousTouchAction = el.style.touchAction;
3751
+ el.style.touchAction = isVertical ? "pan-x" : "pan-y";
3195
3752
  const onDown = (e) => {
3196
3753
  if (e.button !== void 0 && e.button !== 0) return;
3197
3754
  pointerId = e.pointerId;
3198
3755
  startX = e.clientX;
3199
3756
  startY = e.clientY;
3200
3757
  startT = performance.now();
3201
- dragging = true;
3202
- el.style.transition = "none";
3203
- try {
3204
- el.setPointerCapture(e.pointerId);
3205
- } catch {
3206
- }
3758
+ tracking = true;
3759
+ dragging = false;
3207
3760
  };
3208
3761
  const onMove = (e) => {
3209
- if (!dragging || e.pointerId !== pointerId) return;
3762
+ if (!tracking || e.pointerId !== pointerId) return;
3210
3763
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3764
+ if (!dragging) {
3765
+ if (Math.abs(delta) < SLOP) return;
3766
+ dragging = true;
3767
+ el.style.transition = "none";
3768
+ try {
3769
+ el.setPointerCapture(e.pointerId);
3770
+ } catch {
3771
+ }
3772
+ }
3211
3773
  const offset = Math.max(0, sign * delta);
3212
3774
  el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
3213
3775
  };
3214
3776
  const finish = (e) => {
3215
- if (!dragging || e.pointerId !== pointerId) return;
3216
- dragging = false;
3777
+ if (!tracking || e.pointerId !== pointerId) return;
3778
+ tracking = false;
3217
3779
  pointerId = null;
3780
+ if (!dragging) return;
3781
+ dragging = false;
3218
3782
  const dt = performance.now() - startT || 1;
3219
3783
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3220
3784
  const offset = Math.max(0, sign * delta);
3221
3785
  const velocity = sign * delta / dt;
3222
3786
  const size = isVertical ? el.offsetHeight : el.offsetWidth;
3223
- const past = offset > size * threshold || velocity > velocityThreshold;
3224
- el.style.transition = `transform ${this.duration}ms ${this.exitEasing}`;
3787
+ const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
3788
+ el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
3225
3789
  if (past) {
3226
3790
  void this.close("swipe");
3227
3791
  } else {
@@ -3233,6 +3797,7 @@ var OverlayEngine = class {
3233
3797
  el.addEventListener("pointerup", finish);
3234
3798
  el.addEventListener("pointercancel", finish);
3235
3799
  return () => {
3800
+ el.style.touchAction = previousTouchAction;
3236
3801
  el.removeEventListener("pointerdown", onDown);
3237
3802
  el.removeEventListener("pointermove", onMove);
3238
3803
  el.removeEventListener("pointerup", finish);
@@ -3247,10 +3812,12 @@ var OverlayEngine = class {
3247
3812
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3248
3813
  },
3249
3814
  setIsTop: (isTop) => {
3815
+ this.isTop = isTop;
3250
3816
  if (this.backdrop) {
3251
3817
  this.backdrop.style.display = isTop ? "" : "none";
3252
3818
  }
3253
- }
3819
+ },
3820
+ isOpen: () => this.isOpen_
3254
3821
  });
3255
3822
  }
3256
3823
  };
@@ -3263,6 +3830,7 @@ exports.Overlay = OverlayEngine;
3263
3830
  exports.OverlayEngine = OverlayEngine;
3264
3831
  exports.SCRIM_PRESETS = SCRIM_PRESETS;
3265
3832
  exports.allowedRange = allowedRange;
3833
+ exports.applyOverlayPosition = applyOverlayPosition;
3266
3834
  exports.attachGestures = attachGestures;
3267
3835
  exports.createOverlay = createOverlay;
3268
3836
  exports.createSheetManager = createSheetManager;
@@ -3274,8 +3842,10 @@ exports.installFocusTrap = installFocusTrap;
3274
3842
  exports.installGestures = installGestures;
3275
3843
  exports.lockBodyScroll = lockBodyScroll;
3276
3844
  exports.prefersReducedMotion = prefersReducedMotion;
3845
+ exports.resolveSheetAnchoredStyle = resolveSheetAnchoredStyle;
3277
3846
  exports.resolveSnap = resolveSnap;
3278
3847
  exports.resolveSnapList = resolveSnapList;
3848
+ exports.runAnchorTransition = runAnchorTransition;
3279
3849
  exports.runSpring = runSpring;
3280
3850
  exports.sheetStack = sheetStack;
3281
3851
  exports.tween = tween;