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