@surdeddd/bottom-sheet 0.2.0 → 0.4.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-BJrH7qY3.d.cts} +105 -3
  3. package/dist/{BottomSheetEngine-Df9MwBbm.d.ts → BottomSheetEngine-L05jGZvU.d.ts} +105 -3
  4. package/dist/element.cjs +1170 -196
  5. package/dist/element.d.cts +29 -5
  6. package/dist/element.d.ts +29 -5
  7. package/dist/element.iife.global.js +2 -2
  8. package/dist/element.js +1170 -196
  9. package/dist/index.cjs +1051 -220
  10. package/dist/index.d.cts +18 -3
  11. package/dist/index.d.ts +18 -3
  12. package/dist/index.js +1049 -221
  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 +181 -37
  18. package/dist/overlay.d.cts +13 -0
  19. package/dist/overlay.d.ts +13 -0
  20. package/dist/overlay.js +181 -37
  21. package/dist/preact.d.cts +1 -1
  22. package/dist/preact.d.ts +1 -1
  23. package/dist/qwik.cjs +966 -195
  24. package/dist/qwik.d.cts +4 -2
  25. package/dist/qwik.d.ts +4 -2
  26. package/dist/qwik.js +967 -196
  27. package/dist/react.cjs +1242 -272
  28. package/dist/react.d.cts +47 -5
  29. package/dist/react.d.ts +47 -5
  30. package/dist/react.js +1242 -272
  31. package/dist/solid.cjs +992 -236
  32. package/dist/solid.d.cts +299 -4
  33. package/dist/solid.d.ts +299 -4
  34. package/dist/solid.js +993 -237
  35. package/dist/solid.source.jsx +3476 -0
  36. package/dist/styles.css +67 -66
  37. package/dist/svelte-core.cjs +3415 -0
  38. package/dist/svelte-core.js +3413 -0
  39. package/dist/svelte-src/BottomSheet.svelte +308 -0
  40. package/dist/svelte-src/index.js +2 -0
  41. package/dist/svelte.cjs +1034 -207
  42. package/dist/svelte.d.cts +121 -0
  43. package/dist/svelte.d.ts +28 -3
  44. package/dist/svelte.js +1035 -208
  45. package/dist/vue.cjs +1191 -205
  46. package/dist/vue.d.cts +127 -0
  47. package/dist/vue.d.ts +57 -4
  48. package/dist/vue.js +1192 -206
  49. package/package.json +105 -45
package/dist/index.cjs CHANGED
@@ -22,16 +22,52 @@ 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
  }
40
+ depthOf(id) {
41
+ const open = [];
42
+ for (const entry of this.entries) {
43
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
44
+ }
45
+ const idx = open.findIndex((e) => e.id === id);
46
+ if (idx === -1) return 0;
47
+ return open.length - 1 - idx;
48
+ }
28
49
  clear() {
29
50
  this.entries = [];
30
51
  }
31
52
  recompute() {
53
+ let top = null;
54
+ const open = [];
55
+ for (const entry of this.entries) {
56
+ if (!entry.isOpen || entry.isOpen()) {
57
+ top = entry;
58
+ open.push(entry);
59
+ }
60
+ }
61
+ if (!top && this.entries.length > 0) {
62
+ top = this.entries[this.entries.length - 1];
63
+ }
32
64
  this.entries.forEach((entry, i) => {
33
65
  entry.setZIndex(BASE_Z + i * STEP);
34
- entry.setIsTop(i === this.entries.length - 1);
66
+ entry.setIsTop(entry === top);
67
+ if (entry.setDepth) {
68
+ const openIdx = open.indexOf(entry);
69
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
70
+ }
35
71
  });
36
72
  }
37
73
  };
@@ -134,7 +170,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
134
170
  var resolveSnap = (point, mode, measureFit) => {
135
171
  if (typeof point === "number") return Math.max(0, point);
136
172
  if (point === "full") return viewportSize(mode);
137
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
173
+ if (point === "fit" || point === "content") {
174
+ return Math.max(0, measureFit?.() ?? 0);
175
+ }
138
176
  const pctMatch = point.match(PERCENT);
139
177
  if (pctMatch) {
140
178
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -322,14 +360,17 @@ function installAutoCollapse(deps) {
322
360
  const fire = () => {
323
361
  if (deps.isDestroyed()) return;
324
362
  if (deps.isDragging()) return;
325
- const allowed = deps.getAllowedIds();
326
- const target = allowed.find((id) => {
363
+ const active = deps.resolveSnap(deps.getActiveId());
364
+ if (!active || active.size === 0) return;
365
+ let target = null;
366
+ for (const id of deps.getAllowedIds()) {
327
367
  const snap = deps.resolveSnap(id);
328
- return snap !== null && snap.size > 0;
329
- });
368
+ if (!snap || snap.size <= 0) continue;
369
+ if (!target || snap.size < target.size) target = snap;
370
+ }
330
371
  if (!target) return;
331
- if (target === deps.getActiveId()) return;
332
- deps.snapTo(target);
372
+ if (target.id === active.id || target.size >= active.size) return;
373
+ deps.snapTo(target.id);
333
374
  };
334
375
  const reset = () => {
335
376
  clearTimer();
@@ -340,6 +381,7 @@ function installAutoCollapse(deps) {
340
381
  }, ms);
341
382
  };
342
383
  const unsubscribe = deps.on("snap", () => reset());
384
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
343
385
  return () => {
344
386
  clearTimer();
345
387
  unsubscribe();
@@ -352,6 +394,7 @@ function notifyLinkedSheets(sheets, self) {
352
394
  for (const linked of sheets) {
353
395
  if (linked === self) continue;
354
396
  try {
397
+ if (linked.state.size === 0) continue;
355
398
  const ids = linked.getAllowedIds();
356
399
  const target = ids.find((id) => id !== "closed");
357
400
  if (target && target !== linked.state.activeId) {
@@ -366,6 +409,42 @@ function notifyLinkedSheets(sheets, self) {
366
409
  }
367
410
 
368
411
  // src/core/features/route.ts
412
+ var closers = [];
413
+ var handlerInstalled = false;
414
+ var suppress = 0;
415
+ var ensureHandler = () => {
416
+ if (handlerInstalled || typeof window === "undefined") return;
417
+ handlerInstalled = true;
418
+ window.addEventListener("popstate", () => {
419
+ if (suppress > 0) {
420
+ suppress -= 1;
421
+ return;
422
+ }
423
+ let target = null;
424
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
425
+ const c = closers[i];
426
+ if (!c.isOpen()) continue;
427
+ if (!target) target = c;
428
+ if (c.isTop()) {
429
+ target = c;
430
+ break;
431
+ }
432
+ }
433
+ if (!target) return;
434
+ target.consumed = true;
435
+ const idx = closers.indexOf(target);
436
+ if (idx !== -1) closers.splice(idx, 1);
437
+ target.close();
438
+ });
439
+ };
440
+ var programmaticBack = () => {
441
+ suppress += 1;
442
+ try {
443
+ history.back();
444
+ } catch {
445
+ suppress = Math.max(0, suppress - 1);
446
+ }
447
+ };
369
448
  function installRoute(deps) {
370
449
  if (typeof window === "undefined") {
371
450
  return () => {
@@ -375,82 +454,81 @@ function installRoute(deps) {
375
454
  return () => {
376
455
  };
377
456
  }
378
- let routePushed = false;
379
- let closeOnBackPushed = false;
380
- let detachPopstate = null;
381
- const popOurMarker = (key, expected) => {
382
- const state = history.state;
383
- if (state && state[key] === expected) {
384
- try {
385
- history.back();
386
- } catch {
387
- }
457
+ let pushed = false;
458
+ let closer = null;
459
+ const teardownEntry = () => {
460
+ if (closer) {
461
+ const idx = closers.indexOf(closer);
462
+ if (idx !== -1) closers.splice(idx, 1);
463
+ }
464
+ const wasConsumed = closer?.consumed ?? false;
465
+ closer = null;
466
+ if (pushed && !wasConsumed) {
467
+ pushed = false;
468
+ programmaticBack();
469
+ } else {
470
+ pushed = false;
388
471
  }
389
472
  };
390
473
  const onOpen = () => {
391
- if (deps.routedTo && !detachPopstate) {
392
- const target = deps.routedTo;
393
- try {
394
- history.pushState({ __bsRouted: target }, "", target);
395
- routePushed = true;
396
- } catch {
397
- }
398
- const onPop = (event) => {
399
- if (deps.isDestroyed()) return;
400
- const state = event.state;
401
- if (state && state.__bsRouted === target && deps.getSize() > 0) {
402
- routePushed = false;
403
- void deps.close();
404
- }
405
- };
406
- window.addEventListener("popstate", onPop);
407
- detachPopstate = () => window.removeEventListener("popstate", onPop);
408
- } else if (deps.closeOnBack && !detachPopstate) {
409
- try {
474
+ if (closer) return;
475
+ closer = {
476
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
477
+ isTop: () => deps.isTopSheet(),
478
+ close: () => void deps.close(),
479
+ consumed: false
480
+ };
481
+ ensureHandler();
482
+ closers.push(closer);
483
+ try {
484
+ if (deps.routedTo !== void 0) {
485
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
486
+ } else {
410
487
  history.pushState({ __bsSheet: deps.sheetId }, "");
411
- closeOnBackPushed = true;
412
- } catch {
413
488
  }
414
- const onPop = () => {
415
- if (deps.isDestroyed()) return;
416
- if (deps.getSize() > 0 && deps.isTopSheet()) {
417
- const state = history.state;
418
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
419
- void deps.close();
420
- }
421
- };
422
- window.addEventListener("popstate", onPop);
423
- detachPopstate = () => window.removeEventListener("popstate", onPop);
424
- }
425
- };
426
- const onClose = () => {
427
- detachPopstate?.();
428
- detachPopstate = null;
429
- if (routePushed) {
430
- routePushed = false;
431
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
432
- }
433
- if (closeOnBackPushed) {
434
- closeOnBackPushed = false;
435
- popOurMarker("__bsSheet", deps.sheetId);
489
+ pushed = true;
490
+ } catch {
491
+ pushed = false;
436
492
  }
437
493
  };
438
494
  const unsubscribeOpen = deps.on("open", onOpen);
439
- const unsubscribeClose = deps.on("close", onClose);
495
+ const unsubscribeClose = deps.on("close", teardownEntry);
440
496
  if (deps.getSize() > 0) onOpen();
441
497
  return () => {
442
498
  unsubscribeOpen();
443
499
  unsubscribeClose();
444
- detachPopstate?.();
445
- detachPopstate = null;
446
- if (routePushed) {
447
- routePushed = false;
448
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
449
- }
450
- if (closeOnBackPushed) {
451
- closeOnBackPushed = false;
452
- popOurMarker("__bsSheet", deps.sheetId);
453
- }
500
+ teardownEntry();
501
+ };
502
+ }
503
+ function installRouteChange(deps) {
504
+ if (typeof window === "undefined" || typeof history === "undefined") {
505
+ return () => {
506
+ };
507
+ }
508
+ const onChange = () => {
509
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
510
+ void deps.close();
511
+ };
512
+ const wrap = (key) => {
513
+ const original = history[key];
514
+ const patched = function(...args) {
515
+ original.apply(this, args);
516
+ onChange();
517
+ };
518
+ history[key] = patched;
519
+ return () => {
520
+ if (history[key] === patched) {
521
+ history[key] = original;
522
+ }
523
+ };
524
+ };
525
+ window.addEventListener("popstate", onChange);
526
+ const restorePush = wrap("pushState");
527
+ const restoreReplace = wrap("replaceState");
528
+ return () => {
529
+ window.removeEventListener("popstate", onChange);
530
+ restorePush();
531
+ restoreReplace();
454
532
  };
455
533
  }
456
534
 
@@ -552,13 +630,17 @@ function installResizeObserver(deps) {
552
630
  if (viewportSize2 > 0 && viewportSize2 < max) {
553
631
  deps.setMaxAxisSize(viewportSize2);
554
632
  }
633
+ const wasAnimating = deps.isAnimating?.() ?? false;
555
634
  deps.cancelInFlight();
556
635
  deps.newCycle();
557
636
  const current = deps.resolveActiveSnap();
558
637
  const newMax = deps.getMaxAxisSize();
559
638
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
560
639
  deps.setSize(targetSize);
561
- if (current && !deps.isDragging()) deps.applySize(targetSize);
640
+ if (current && !deps.isDragging()) {
641
+ deps.applySize(targetSize);
642
+ if (wasAnimating) deps.resyncAfterCancel?.();
643
+ }
562
644
  };
563
645
  let resizeObserver = null;
564
646
  if (typeof ResizeObserver !== "undefined") {
@@ -673,8 +755,88 @@ function devWarn(...args) {
673
755
  }
674
756
  }
675
757
 
758
+ // src/core/primitives/overlay-position.ts
759
+ function resolveSheetAnchoredStyle(mode, position, inset) {
760
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
761
+ switch (mode) {
762
+ case "bottom":
763
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
764
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
765
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
766
+ case "top":
767
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
768
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
769
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
770
+ case "left":
771
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
772
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
773
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
774
+ case "right":
775
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
776
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
777
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
778
+ }
779
+ }
780
+ function applyOverlayPosition(ws, mode, position, inset) {
781
+ switch (position) {
782
+ case "top-left":
783
+ ws.top = inset;
784
+ ws.left = inset;
785
+ break;
786
+ case "top-center":
787
+ ws.top = inset;
788
+ ws.left = "50%";
789
+ ws.transform = "translateX(-50%)";
790
+ break;
791
+ case "top-right":
792
+ ws.top = inset;
793
+ ws.right = inset;
794
+ break;
795
+ case "center-left":
796
+ ws.top = "50%";
797
+ ws.left = inset;
798
+ ws.transform = "translateY(-50%)";
799
+ break;
800
+ case "center":
801
+ ws.top = "50%";
802
+ ws.left = "50%";
803
+ ws.transform = "translate(-50%, -50%)";
804
+ break;
805
+ case "center-right":
806
+ ws.top = "50%";
807
+ ws.right = inset;
808
+ ws.transform = "translateY(-50%)";
809
+ break;
810
+ case "bottom-left":
811
+ ws.bottom = inset;
812
+ ws.left = inset;
813
+ break;
814
+ case "bottom-center":
815
+ ws.bottom = inset;
816
+ ws.left = "50%";
817
+ ws.transform = "translateX(-50%)";
818
+ break;
819
+ case "bottom-right":
820
+ ws.bottom = inset;
821
+ ws.right = inset;
822
+ break;
823
+ case "sheet-top-left":
824
+ case "sheet-top-center":
825
+ case "sheet-top-right": {
826
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
827
+ if (style.top !== void 0) ws.top = style.top;
828
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
829
+ if (style.left !== void 0) ws.left = style.left;
830
+ if (style.right !== void 0) ws.right = style.right;
831
+ if (style.transform !== void 0) ws.transform = style.transform;
832
+ break;
833
+ }
834
+ }
835
+ }
836
+
676
837
  // src/core/controllers/scrim-controller.ts
677
838
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
839
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
678
840
  var ScrimController = class {
679
841
  constructor(deps, opts) {
680
842
  __publicField(this, "screenComponent");
@@ -685,6 +847,8 @@ var ScrimController = class {
685
847
  __publicField(this, "getAllowedIds");
686
848
  __publicField(this, "getResolvedSnaps");
687
849
  __publicField(this, "snapToFn");
850
+ __publicField(this, "closeFn");
851
+ __publicField(this, "scrimInteractive", false);
688
852
  __publicField(this, "scrimMode");
689
853
  __publicField(this, "backdropRange");
690
854
  __publicField(this, "screenRange");
@@ -705,14 +869,17 @@ var ScrimController = class {
705
869
  this.getAllowedIds = deps.getAllowedIds;
706
870
  this.getResolvedSnaps = deps.getResolvedSnaps;
707
871
  this.snapToFn = deps.snapTo;
872
+ this.closeFn = deps.close;
708
873
  this.scrimMode = opts.scrimMode ?? "full";
709
874
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
710
875
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
711
876
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
712
877
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
878
+ const explicitColor = opts.scrimColor ?? preset?.color;
879
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
713
880
  if (this.screenComponent) {
714
881
  this.applyScrimStyles({
715
- color: opts.scrimColor ?? preset?.color,
882
+ color: effectiveColor,
716
883
  blur: opts.scrimBlur ?? preset?.blur,
717
884
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
718
885
  });
@@ -846,11 +1013,15 @@ var ScrimController = class {
846
1013
  }
847
1014
  const onClick = () => {
848
1015
  if (this.isDestroyed()) return;
1016
+ if (this.closeFn) {
1017
+ this.closeFn();
1018
+ return;
1019
+ }
849
1020
  const allowed = this.getAllowedIds();
850
1021
  const snaps = this.getResolvedSnaps();
851
1022
  const fallback = allowed.find((id) => {
852
1023
  const snap = findById(id, snaps);
853
- return snap !== null && snap.size > 0;
1024
+ return snap !== null && snap.size === 0;
854
1025
  });
855
1026
  if (fallback) this.snapToFn(fallback);
856
1027
  };
@@ -867,6 +1038,7 @@ var ScrimController = class {
867
1038
  if (this.isDestroyed()) return;
868
1039
  if (enabled === this.scrimEnabled) return;
869
1040
  this.scrimEnabled = enabled;
1041
+ this.invalidateOpacityCache();
870
1042
  if (!enabled) {
871
1043
  this.savedScrimRanges = {
872
1044
  screen: this.screenRange,
@@ -890,7 +1062,12 @@ var ScrimController = class {
890
1062
  blur: cfg.blur ?? null,
891
1063
  interactive: cfg.interactive
892
1064
  });
893
- this.screenRange = cfg.range;
1065
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1066
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1067
+ } else {
1068
+ this.screenRange = cfg.range;
1069
+ this.backdropRange = cfg.range;
1070
+ }
894
1071
  }
895
1072
  const noop = () => {
896
1073
  };
@@ -940,59 +1117,7 @@ var ScrimController = class {
940
1117
  wrapper.className = "bs-scrim-overlay";
941
1118
  const ws = wrapper.style;
942
1119
  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
- }
1120
+ applyOverlayPosition(ws, this.mode, position, inset);
996
1121
  if (interactive) {
997
1122
  ws.pointerEvents = "auto";
998
1123
  }
@@ -1051,6 +1176,7 @@ var ScrimController = class {
1051
1176
  }
1052
1177
  }
1053
1178
  if (opts.interactive !== void 0) {
1179
+ this.scrimInteractive = opts.interactive;
1054
1180
  s.pointerEvents = opts.interactive ? "auto" : "none";
1055
1181
  }
1056
1182
  }
@@ -1065,38 +1191,9 @@ var ScrimController = class {
1065
1191
  const s = this.screenComponent.style;
1066
1192
  s.position = "fixed";
1067
1193
  s.inset = insetByMode[this.mode];
1068
- s.pointerEvents = "none";
1194
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1069
1195
  }
1070
1196
  };
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
1197
 
1101
1198
  // src/core/animation/animation.ts
1102
1199
  var easeOutBack = (t) => {
@@ -1182,14 +1279,20 @@ var runSpring = ({
1182
1279
  if (cancelled) return;
1183
1280
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1184
1281
  lastTime = now;
1185
- const subDt = Math.min(dt, 1 / 240);
1186
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1282
+ const steps = Math.max(1, Math.ceil(dt * 240));
1283
+ const subDt = dt / steps;
1284
+ let stepsLeft = steps;
1187
1285
  while (stepsLeft-- > 0) {
1188
1286
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1189
1287
  const accel = force / cfg.mass;
1190
1288
  v += accel * subDt;
1191
1289
  x += v * subDt;
1192
1290
  }
1291
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1292
+ onUpdate(to, 0);
1293
+ resolve();
1294
+ return;
1295
+ }
1193
1296
  onUpdate(x, v);
1194
1297
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1195
1298
  if (isAtRest) {
@@ -1364,9 +1467,13 @@ var isVisible = (el) => {
1364
1467
  var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1365
1468
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1366
1469
  );
1470
+ var trapStack = [];
1367
1471
  var installFocusTrap = (container, options = {}) => {
1368
1472
  if (typeof document === "undefined") return () => {
1369
1473
  };
1474
+ const token = {};
1475
+ trapStack.push(token);
1476
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1370
1477
  const previouslyFocused = document.activeElement;
1371
1478
  const focusInitial = () => {
1372
1479
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1374,6 +1481,7 @@ var installFocusTrap = (container, options = {}) => {
1374
1481
  };
1375
1482
  focusInitial();
1376
1483
  const handleKey = (e) => {
1484
+ if (!isActive()) return;
1377
1485
  if (e.key === "Escape" && options.onEscape) {
1378
1486
  options.onEscape();
1379
1487
  return;
@@ -1396,6 +1504,7 @@ var installFocusTrap = (container, options = {}) => {
1396
1504
  }
1397
1505
  };
1398
1506
  const handleFocusIn = (e) => {
1507
+ if (!isActive()) return;
1399
1508
  const target = e.target;
1400
1509
  if (target && !container.contains(target)) {
1401
1510
  const list = focusables(container);
@@ -1408,6 +1517,8 @@ var installFocusTrap = (container, options = {}) => {
1408
1517
  return () => {
1409
1518
  if (released) return;
1410
1519
  released = true;
1520
+ const idx = trapStack.indexOf(token);
1521
+ if (idx !== -1) trapStack.splice(idx, 1);
1411
1522
  document.removeEventListener("keydown", handleKey, true);
1412
1523
  document.removeEventListener("focusin", handleFocusIn, true);
1413
1524
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1463,11 +1574,17 @@ function createInertSiblings(rootProvider) {
1463
1574
  return {
1464
1575
  apply() {
1465
1576
  if (typeof document === "undefined") return;
1466
- const root = rootProvider();
1467
- const parent = root.parentElement ?? document.body;
1577
+ let top = rootProvider();
1578
+ while (top.parentElement && top.parentElement !== document.body) {
1579
+ top = top.parentElement;
1580
+ }
1581
+ const parent = top.parentElement ?? document.body;
1468
1582
  for (const child of Array.from(parent.children)) {
1469
- if (child === root) continue;
1583
+ if (child === top) continue;
1470
1584
  if (child.hasAttribute("inert")) continue;
1585
+ if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
1586
+ continue;
1587
+ }
1471
1588
  child.setAttribute("inert", "");
1472
1589
  tracked.push(child);
1473
1590
  }
@@ -1483,7 +1600,6 @@ function createInertSiblings(rootProvider) {
1483
1600
  var LifecycleController = class {
1484
1601
  constructor(deps, opts) {
1485
1602
  __publicField(this, "element");
1486
- __publicField(this, "closeFn");
1487
1603
  __publicField(this, "focusTrapEnabled");
1488
1604
  __publicField(this, "initialFocus");
1489
1605
  __publicField(this, "closeOnEscape");
@@ -1497,7 +1613,6 @@ var LifecycleController = class {
1497
1613
  __publicField(this, "installed", false);
1498
1614
  __publicField(this, "destroyed", false);
1499
1615
  this.element = deps.element;
1500
- this.closeFn = deps.close;
1501
1616
  this.focusTrapEnabled = opts.focusTrap ?? false;
1502
1617
  this.initialFocus = opts.initialFocus;
1503
1618
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1507,6 +1622,9 @@ var LifecycleController = class {
1507
1622
  this.returnFocus = opts.returnFocus;
1508
1623
  this.inertSiblings = createInertSiblings(() => this.element);
1509
1624
  }
1625
+ get isInstalled() {
1626
+ return this.installed;
1627
+ }
1510
1628
  install() {
1511
1629
  if (this.destroyed) return;
1512
1630
  this.installed = true;
@@ -1515,8 +1633,7 @@ var LifecycleController = class {
1515
1633
  }
1516
1634
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1517
1635
  this.releaseFocusTrap = installFocusTrap(this.element, {
1518
- initialFocus: this.initialFocus,
1519
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1636
+ initialFocus: this.initialFocus
1520
1637
  });
1521
1638
  }
1522
1639
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1536,7 +1653,7 @@ var LifecycleController = class {
1536
1653
  this.releaseScrollLock = null;
1537
1654
  this.inertSiblings.remove();
1538
1655
  if (this.returnFocus) {
1539
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1656
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1540
1657
  target?.focus?.();
1541
1658
  }
1542
1659
  }
@@ -1671,10 +1788,11 @@ var installGestures = (handle, mode, callbacks) => {
1671
1788
  var attachGestures = installGestures;
1672
1789
 
1673
1790
  // src/core/primitives/rubber-band.ts
1674
- var RUBBER_BAND_FACTOR = 0.55;
1675
1791
  function rubberBand(overshoot, maxAxisSize) {
1676
1792
  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));
1793
+ if (cap <= 0) return 0;
1794
+ const abs = Math.abs(overshoot);
1795
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1678
1796
  }
1679
1797
 
1680
1798
  // src/core/features/soft-keyboard.ts
@@ -1703,6 +1821,7 @@ var GestureController = class {
1703
1821
  __publicField(this, "dragStartSize", 0);
1704
1822
  __publicField(this, "currentPointerKind", "touch");
1705
1823
  __publicField(this, "keyboardDismissed", false);
1824
+ __publicField(this, "dragSuppressed", false);
1706
1825
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1707
1826
  this.deps = deps;
1708
1827
  }
@@ -1713,6 +1832,11 @@ var GestureController = class {
1713
1832
  if (this.detach) return this.detach;
1714
1833
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1715
1834
  onStart: (_coord, kind) => {
1835
+ if (this.deps.getDisableDrag?.()) {
1836
+ this.dragSuppressed = true;
1837
+ return;
1838
+ }
1839
+ this.dragSuppressed = false;
1716
1840
  this.deps.cancelAnimation();
1717
1841
  this.isDragging_ = true;
1718
1842
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1723,6 +1847,7 @@ var GestureController = class {
1723
1847
  this.deps.emit("dragstart", { size: this.dragStartSize });
1724
1848
  },
1725
1849
  onMove: (delta) => {
1850
+ if (this.dragSuppressed) return;
1726
1851
  const ctx = this.deps.getDragContext();
1727
1852
  const { min, max } = ctx.range;
1728
1853
  const maxAxis = ctx.maxAxisSize;
@@ -1746,6 +1871,10 @@ var GestureController = class {
1746
1871
  }
1747
1872
  },
1748
1873
  onEnd: (delta, velocity, kind) => {
1874
+ if (this.dragSuppressed) {
1875
+ this.dragSuppressed = false;
1876
+ return;
1877
+ }
1749
1878
  this.isDragging_ = false;
1750
1879
  this.keyboardDismissed = false;
1751
1880
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1756,9 +1885,17 @@ var GestureController = class {
1756
1885
  this.deps.settleAfterDrag(delta, velocity, kind);
1757
1886
  },
1758
1887
  onCancel: () => {
1888
+ if (this.dragSuppressed) {
1889
+ this.dragSuppressed = false;
1890
+ return;
1891
+ }
1759
1892
  this.isDragging_ = false;
1760
1893
  this.keyboardDismissed = false;
1761
1894
  this.deps.getRoot()?.removeAttribute("data-dragging");
1895
+ this.deps.emit("dragend", {
1896
+ size: this.deps.getDragContext().size,
1897
+ velocity: 0
1898
+ });
1762
1899
  const restoreTo = this.dragStartSize;
1763
1900
  void this.deps.animateTo(restoreTo, 0);
1764
1901
  }
@@ -1832,6 +1969,287 @@ function createScrollCache(deps) {
1832
1969
  };
1833
1970
  }
1834
1971
 
1972
+ // src/core/primitives/anchor-animations.ts
1973
+ var DEFAULT_DURATION3 = 200;
1974
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1975
+ var PRESET_FRAMES = {
1976
+ fade: {
1977
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1978
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1979
+ },
1980
+ scale: {
1981
+ enter: [
1982
+ { opacity: 0, transform: "scale(0.85)" },
1983
+ { opacity: 1, transform: "scale(1)" }
1984
+ ],
1985
+ exit: [
1986
+ { opacity: 1, transform: "scale(1)" },
1987
+ { opacity: 0, transform: "scale(0.85)" }
1988
+ ]
1989
+ },
1990
+ slide: {
1991
+ enter: [
1992
+ { opacity: 0, transform: "translateY(10px)" },
1993
+ { opacity: 1, transform: "translateY(0)" }
1994
+ ],
1995
+ exit: [
1996
+ { opacity: 1, transform: "translateY(0)" },
1997
+ { opacity: 0, transform: "translateY(10px)" }
1998
+ ]
1999
+ },
2000
+ pop: {
2001
+ enter: [
2002
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
2003
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
2004
+ { opacity: 1, transform: "scale(1)", offset: 1 }
2005
+ ],
2006
+ exit: [
2007
+ { opacity: 1, transform: "scale(1)" },
2008
+ { opacity: 0, transform: "scale(0.6)" }
2009
+ ]
2010
+ }
2011
+ };
2012
+ var INSTANT = {
2013
+ finished: Promise.resolve(),
2014
+ cancel: () => {
2015
+ }
2016
+ };
2017
+ function resolveTransitionFrames(spec, phase) {
2018
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
2019
+ if (spec === "none") return fallback;
2020
+ if (spec === void 0 || typeof spec === "string") {
2021
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
2022
+ return {
2023
+ frames: preset2[phase],
2024
+ duration: DEFAULT_DURATION3,
2025
+ easing: DEFAULT_EASING
2026
+ };
2027
+ }
2028
+ const custom = phase === "enter" ? spec.enter : spec.exit;
2029
+ if (custom) {
2030
+ return {
2031
+ frames: custom,
2032
+ duration: spec.duration ?? DEFAULT_DURATION3,
2033
+ easing: spec.easing ?? DEFAULT_EASING
2034
+ };
2035
+ }
2036
+ if (spec.preset === "none") return fallback;
2037
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
2038
+ return {
2039
+ frames: preset[phase],
2040
+ duration: spec.duration ?? DEFAULT_DURATION3,
2041
+ easing: spec.easing ?? DEFAULT_EASING
2042
+ };
2043
+ }
2044
+ function runAnchorTransition(el, spec, phase) {
2045
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
2046
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
2047
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
2048
+ if (!frames || duration <= 0) return INSTANT;
2049
+ if (typeof el.animate !== "function") return INSTANT;
2050
+ const animation = el.animate(frames, {
2051
+ duration,
2052
+ easing,
2053
+ fill: "both"
2054
+ });
2055
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
2056
+ return {
2057
+ finished,
2058
+ cancel: () => {
2059
+ try {
2060
+ animation.cancel();
2061
+ } catch {
2062
+ return;
2063
+ }
2064
+ }
2065
+ };
2066
+ }
2067
+
2068
+ // src/core/features/sheet-anchors.ts
2069
+ function attachAnchor(deps, opts) {
2070
+ const doc = deps.host.ownerDocument;
2071
+ const wrapper = doc.createElement("div");
2072
+ wrapper.className = "bs-anchor";
2073
+ const interactive = opts.interactive ?? true;
2074
+ const position = opts.position ?? "sheet-top-right";
2075
+ const isDock = position === "dock-bottom" || position === "dock-top";
2076
+ const ws = wrapper.style;
2077
+ ws.position = "fixed";
2078
+ if (isDock) {
2079
+ ws.left = "0";
2080
+ ws.right = "0";
2081
+ if (position === "dock-bottom") {
2082
+ ws.bottom = "0";
2083
+ } else {
2084
+ ws.top = "0";
2085
+ }
2086
+ } else {
2087
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2088
+ }
2089
+ if (opts.fadeRange) {
2090
+ const [r0, r1] = opts.fadeRange;
2091
+ const span = Math.max(r1 - r0, 1e-4);
2092
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2093
+ }
2094
+ wrapper.appendChild(opts.element);
2095
+ deps.host.appendChild(wrapper);
2096
+ let visible = null;
2097
+ let inFlight = null;
2098
+ const shouldShow = (state) => {
2099
+ if (typeof opts.showOn === "function") return opts.showOn(state);
2100
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
2101
+ return isDock || state.size > 0;
2102
+ };
2103
+ const applyVisibility = (next, animate) => {
2104
+ if (next === visible) return;
2105
+ visible = next;
2106
+ inFlight?.cancel();
2107
+ inFlight = null;
2108
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2109
+ if (next) {
2110
+ wrapper.style.visibility = "";
2111
+ if (animate) {
2112
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2113
+ }
2114
+ return;
2115
+ }
2116
+ if (!animate) {
2117
+ wrapper.style.visibility = "hidden";
2118
+ return;
2119
+ }
2120
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2121
+ inFlight = handle;
2122
+ void handle.finished.then(() => {
2123
+ if (inFlight === handle && visible === false) {
2124
+ wrapper.style.visibility = "hidden";
2125
+ }
2126
+ });
2127
+ };
2128
+ const evaluate = (animate) => {
2129
+ if (deps.isDestroyed()) return;
2130
+ applyVisibility(shouldShow(deps.getState()), animate);
2131
+ };
2132
+ const offs = [
2133
+ deps.on("snap", () => evaluate(true)),
2134
+ deps.on("open", () => evaluate(true)),
2135
+ deps.on("close", () => evaluate(true))
2136
+ ];
2137
+ evaluate(false);
2138
+ return {
2139
+ wrapper,
2140
+ detach: () => {
2141
+ offs.forEach((off) => off());
2142
+ inFlight?.cancel();
2143
+ inFlight = null;
2144
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2145
+ },
2146
+ syncZ: (z) => {
2147
+ wrapper.style.zIndex = String(z);
2148
+ }
2149
+ };
2150
+ }
2151
+
2152
+ // src/core/features/scrim-stages.ts
2153
+ var matchesId = (def, activeId) => {
2154
+ if (def.for === void 0) return false;
2155
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2156
+ };
2157
+ var matchesRange = (def, progress) => {
2158
+ if (!def.forRange) return false;
2159
+ const [from, to] = def.forRange;
2160
+ return progress >= from && progress <= to;
2161
+ };
2162
+ function installScrimStages(deps, opts) {
2163
+ const doc = deps.host.ownerDocument;
2164
+ const entries = opts.stages.map((def) => {
2165
+ const wrapper = doc.createElement("div");
2166
+ wrapper.className = "bs-scrim-stage";
2167
+ const ws = wrapper.style;
2168
+ ws.position = "absolute";
2169
+ applyOverlayPosition(
2170
+ ws,
2171
+ deps.mode,
2172
+ def.position ?? opts.position ?? "center",
2173
+ def.inset ?? opts.inset ?? "16px"
2174
+ );
2175
+ ws.visibility = "hidden";
2176
+ ws.pointerEvents = "none";
2177
+ wrapper.appendChild(def.element);
2178
+ deps.host.appendChild(wrapper);
2179
+ return { def, wrapper, inFlight: null };
2180
+ });
2181
+ let active = null;
2182
+ const resolveActive = () => {
2183
+ const state = deps.getState();
2184
+ if (state.size === 0) {
2185
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2186
+ }
2187
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2188
+ };
2189
+ const hide = (entry, animate) => {
2190
+ entry.inFlight?.cancel();
2191
+ entry.wrapper.style.pointerEvents = "none";
2192
+ if (!animate) {
2193
+ entry.inFlight = null;
2194
+ entry.wrapper.style.visibility = "hidden";
2195
+ return;
2196
+ }
2197
+ const handle = runAnchorTransition(
2198
+ entry.def.element,
2199
+ entry.def.animation ?? opts.animation,
2200
+ "exit"
2201
+ );
2202
+ entry.inFlight = handle;
2203
+ void handle.finished.then(() => {
2204
+ if (entry.inFlight === handle && active !== entry) {
2205
+ entry.wrapper.style.visibility = "hidden";
2206
+ }
2207
+ });
2208
+ };
2209
+ const show = (entry, animate) => {
2210
+ entry.inFlight?.cancel();
2211
+ entry.inFlight = null;
2212
+ entry.wrapper.style.visibility = "";
2213
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2214
+ if (animate) {
2215
+ entry.inFlight = runAnchorTransition(
2216
+ entry.def.element,
2217
+ entry.def.animation ?? opts.animation,
2218
+ "enter"
2219
+ );
2220
+ }
2221
+ };
2222
+ const update = (animate) => {
2223
+ if (deps.isDestroyed()) return;
2224
+ const next = resolveActive();
2225
+ if (next === active) return;
2226
+ const prev = active;
2227
+ active = next;
2228
+ if (prev) hide(prev, animate);
2229
+ if (next) show(next, animate);
2230
+ };
2231
+ const offs = [
2232
+ deps.on("snap", () => update(true)),
2233
+ deps.on("open", () => update(true)),
2234
+ deps.on("close", () => update(true))
2235
+ ];
2236
+ if (entries.some((e) => e.def.forRange)) {
2237
+ offs.push(deps.on("progress", () => update(true)));
2238
+ }
2239
+ update(false);
2240
+ return () => {
2241
+ offs.forEach((off) => off());
2242
+ for (const entry of entries) {
2243
+ entry.inFlight?.cancel();
2244
+ entry.inFlight = null;
2245
+ if (entry.wrapper.parentNode) {
2246
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2247
+ }
2248
+ }
2249
+ active = null;
2250
+ };
2251
+ }
2252
+
1835
2253
  // src/core/primitives/engine-options.ts
1836
2254
  var DEFAULT_FLICK_VELOCITY = 0.65;
1837
2255
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1850,6 +2268,12 @@ function resolveEngineOptions(opts, extras) {
1850
2268
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
1851
2269
  rubberBandEnabled: opts.rubberBand ?? true,
1852
2270
  closeOnBack: opts.closeOnBack ?? false,
2271
+ persistent: opts.persistent ?? false,
2272
+ disableClose: opts.disableClose ?? false,
2273
+ disableDrag: opts.disableDrag ?? false,
2274
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2275
+ radius: opts.radius,
2276
+ maxHeight: opts.maxHeight,
1853
2277
  initialAllowed,
1854
2278
  initialId,
1855
2279
  scrim: {
@@ -1876,7 +2300,8 @@ function resolveEngineOptions(opts, extras) {
1876
2300
  closeOnEscape: opts.closeOnEscape,
1877
2301
  lockBodyScroll: opts.lockBodyScroll,
1878
2302
  inertSiblings: opts.inertSiblings,
1879
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2303
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2304
+ returnFocus: opts.returnFocusTo
1880
2305
  }
1881
2306
  };
1882
2307
  }
@@ -1921,6 +2346,11 @@ var BottomSheetEngine = class {
1921
2346
  __publicField(this, "lifecycle");
1922
2347
  __publicField(this, "closeOnBack");
1923
2348
  __publicField(this, "routedTo");
2349
+ __publicField(this, "persistent");
2350
+ __publicField(this, "disableCloseFlag");
2351
+ __publicField(this, "disableDragFlag");
2352
+ __publicField(this, "closeOnRouteChange");
2353
+ __publicField(this, "maxHeightCap");
1924
2354
  __publicField(this, "snapPointsRaw");
1925
2355
  __publicField(this, "snaps");
1926
2356
  __publicField(this, "activeId");
@@ -1933,6 +2363,7 @@ var BottomSheetEngine = class {
1933
2363
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
1934
2364
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
1935
2365
  __publicField(this, "isTopSheet", true);
2366
+ __publicField(this, "opening", false);
1936
2367
  __publicField(this, "progressPayload", {
1937
2368
  value: 0,
1938
2369
  size: 0
@@ -1948,6 +2379,12 @@ var BottomSheetEngine = class {
1948
2379
  __publicField(this, "linkedSheets", []);
1949
2380
  __publicField(this, "scrollCache");
1950
2381
  __publicField(this, "teardowns", new TeardownStack());
2382
+ __publicField(this, "anchors", []);
2383
+ __publicField(this, "anchorHost", null);
2384
+ __publicField(this, "stackZ", 100);
2385
+ __publicField(this, "detachScrimStages", null);
2386
+ __publicField(this, "stackEffectEnabled", false);
2387
+ __publicField(this, "stackEffectPrimed", false);
1951
2388
  __publicField(this, "transformTemplate");
1952
2389
  const resolved = resolveEngineOptions(opts, {
1953
2390
  shouldApplyInertSiblings: () => {
@@ -1981,6 +2418,10 @@ var BottomSheetEngine = class {
1981
2418
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1982
2419
  snapTo: (id) => {
1983
2420
  void this.snapTo(id);
2421
+ },
2422
+ close: () => {
2423
+ if (!this.canDismiss()) return;
2424
+ void this.close("backdrop");
1984
2425
  }
1985
2426
  },
1986
2427
  resolved.scrim
@@ -2004,6 +2445,11 @@ var BottomSheetEngine = class {
2004
2445
  );
2005
2446
  this.closeOnBack = resolved.closeOnBack;
2006
2447
  this.routedTo = opts.routedTo;
2448
+ this.persistent = resolved.persistent;
2449
+ this.disableCloseFlag = resolved.disableClose;
2450
+ this.disableDragFlag = resolved.disableDrag;
2451
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2452
+ this.stackEffectEnabled = opts.stackEffect ?? false;
2007
2453
  this.snapPointsRaw = opts.snapPoints;
2008
2454
  this.persistKey = opts.persistKey;
2009
2455
  if (opts.linkedSheets) {
@@ -2016,7 +2462,7 @@ var BottomSheetEngine = class {
2016
2462
  this.snapPointsRaw,
2017
2463
  resolved.initialAllowed,
2018
2464
  this.mode,
2019
- () => this.handle.offsetHeight,
2465
+ () => this.measureFitSize(),
2020
2466
  (maxAxisSize) => {
2021
2467
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2022
2468
  }
@@ -2050,6 +2496,19 @@ var BottomSheetEngine = class {
2050
2496
  io.observe(this.element);
2051
2497
  this.teardowns.add(() => io.disconnect());
2052
2498
  }
2499
+ this.installFitObserver();
2500
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2501
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2502
+ this.applyAutoAriaLabelledBy();
2503
+ if (this.closeOnRouteChange) {
2504
+ this.teardowns.add(
2505
+ installRouteChange({
2506
+ isDestroyed: () => this.destroyed,
2507
+ getSize: () => this.size,
2508
+ close: () => this.close()
2509
+ })
2510
+ );
2511
+ }
2053
2512
  if (this.persistKey) {
2054
2513
  this.teardowns.add(installPersist(this, this.persistKey));
2055
2514
  }
@@ -2074,7 +2533,7 @@ var BottomSheetEngine = class {
2074
2533
  isTopSheet: () => this.isTopSheet,
2075
2534
  getSize: () => this.size,
2076
2535
  isDestroyed: () => this.destroyed,
2077
- close: () => this.close(),
2536
+ close: () => this.close("back"),
2078
2537
  on: (event, fn) => this.on(event, fn),
2079
2538
  sheetId: this.id
2080
2539
  })
@@ -2168,6 +2627,10 @@ var BottomSheetEngine = class {
2168
2627
  const previousSize = this.size;
2169
2628
  this.scrollCache.cache(previousId, previousSize, target.size);
2170
2629
  this.activeId = id;
2630
+ if (wasClosed && target.size > 0) {
2631
+ this.opening = true;
2632
+ sheetStack.promote(this.id);
2633
+ }
2171
2634
  if (this.animation.viewTransitionsAvailable) {
2172
2635
  this.currentViewTransition?.skipTransition?.();
2173
2636
  const vt = document.startViewTransition(() => {
@@ -2182,19 +2645,26 @@ var BottomSheetEngine = class {
2182
2645
  } else {
2183
2646
  await this.animation.animateTo(target.size, velocityPxPerMs);
2184
2647
  }
2648
+ this.opening = false;
2185
2649
  externalSignal?.removeEventListener("abort", onExternalAbort);
2186
2650
  if (signal.aborted) return;
2187
2651
  this.scrollCache.restore(id, previousSize, target.size);
2188
2652
  this.updateAriaSlider();
2189
- this.emit("snap", { id, size: target.size });
2653
+ this.emit("snap", {
2654
+ id,
2655
+ size: target.size,
2656
+ progress: this.computeProgress(target.size)
2657
+ });
2190
2658
  if (wasClosed && target.size > 0) {
2191
2659
  this.emit("open", { id });
2192
2660
  this.handleOpen();
2193
2661
  notifyLinkedSheets(this.linkedSheets, this);
2662
+ this.emit("opened", { id });
2194
2663
  }
2195
2664
  if (target.size === 0) {
2196
2665
  this.emit("close", void 0);
2197
2666
  this.handleClose();
2667
+ this.emit("closed", void 0);
2198
2668
  }
2199
2669
  }
2200
2670
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2219,18 +2689,76 @@ var BottomSheetEngine = class {
2219
2689
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2220
2690
  return this.snapTo(target);
2221
2691
  }
2222
- close() {
2692
+ close(reason = "programmatic") {
2223
2693
  if (this.destroyed) return Promise.resolve();
2694
+ if (this.disableCloseFlag) return Promise.resolve();
2695
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2224
2696
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2225
2697
  return this.snapTo(closedId ?? this.activeId);
2226
2698
  }
2227
- setAllowed(ids, snap) {
2699
+ canDismiss() {
2700
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2701
+ }
2702
+ isTop() {
2703
+ return this.isTopSheet;
2704
+ }
2705
+ depth() {
2706
+ return sheetStack.depthOf(this.id);
2707
+ }
2708
+ expand() {
2709
+ if (this.destroyed) return Promise.resolve();
2710
+ const ids = this.allowedIdsBySize();
2711
+ const target = ids[ids.length - 1];
2712
+ if (!target) return Promise.resolve();
2713
+ return this.snapTo(target);
2714
+ }
2715
+ collapse() {
2716
+ if (this.destroyed) return Promise.resolve();
2717
+ const ids = this.allowedIdsBySize();
2718
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2719
+ const target = nonZero ?? ids[0];
2720
+ if (!target) return Promise.resolve();
2721
+ return this.snapTo(target);
2722
+ }
2723
+ setRadius(r) {
2228
2724
  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();
2725
+ this.element.style.setProperty(
2726
+ "--bs-radius",
2727
+ typeof r === "number" ? `${r}px` : r
2728
+ );
2729
+ }
2730
+ setMaxHeight(h) {
2731
+ if (this.destroyed) return;
2732
+ const value = typeof h === "number" ? `${h}px` : h;
2733
+ const axis = layoutAxis(this.mode);
2734
+ this.element.style.setProperty(
2735
+ axis === "height" ? "maxHeight" : "maxWidth",
2736
+ value
2737
+ );
2738
+ if (typeof h === "number") {
2739
+ this.maxHeightCap = h;
2740
+ this.clampToMaxHeight();
2741
+ } else if (typeof window !== "undefined") {
2742
+ const probe2 = document.createElement("div");
2743
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2744
+ document.body.appendChild(probe2);
2745
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2746
+ probe2.remove();
2747
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2748
+ this.clampToMaxHeight();
2749
+ }
2750
+ }
2751
+ clampToMaxHeight() {
2752
+ if (this.maxHeightCap === void 0) return;
2753
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2754
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2755
+ }
2756
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2757
+ this.applySize(this.maxHeightCap);
2233
2758
  }
2759
+ }
2760
+ setAllowed(ids, snap) {
2761
+ if (this.destroyed) return;
2234
2762
  this.snaps.setAllowedIds(ids);
2235
2763
  this.updateAriaSlider();
2236
2764
  if (snap && ids.includes(snap)) {
@@ -2270,6 +2798,79 @@ var BottomSheetEngine = class {
2270
2798
  setScrimOverlay(opts) {
2271
2799
  return this.scrim.setScrimOverlay(opts);
2272
2800
  }
2801
+ addAnchor(opts) {
2802
+ if (this.destroyed || typeof document === "undefined") return () => {
2803
+ };
2804
+ if (!this.anchorHost) {
2805
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2806
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2807
+ this.anchorHost.style.setProperty(
2808
+ "--bs-progress",
2809
+ String(this.computeProgress(this.size))
2810
+ );
2811
+ }
2812
+ const handle = attachAnchor(
2813
+ {
2814
+ mode: this.mode,
2815
+ host: this.anchorHost,
2816
+ getState: () => ({
2817
+ activeId: this.activeId,
2818
+ size: this.size,
2819
+ progress: this.computeProgress(this.size)
2820
+ }),
2821
+ on: (event, fn) => this.on(event, fn),
2822
+ isDestroyed: () => this.destroyed
2823
+ },
2824
+ opts
2825
+ );
2826
+ handle.syncZ(this.stackZ + 1);
2827
+ this.anchors.push(handle);
2828
+ const detach = () => {
2829
+ const idx = this.anchors.indexOf(handle);
2830
+ if (idx === -1) return;
2831
+ this.anchors.splice(idx, 1);
2832
+ handle.detach();
2833
+ };
2834
+ this.teardowns.add(detach);
2835
+ return detach;
2836
+ }
2837
+ setScrimStages(opts) {
2838
+ if (this.destroyed || typeof document === "undefined") return () => {
2839
+ };
2840
+ this.detachScrimStages?.();
2841
+ this.detachScrimStages = null;
2842
+ if (!opts) return () => {
2843
+ };
2844
+ const host = this.screenComponent?.parentElement;
2845
+ if (!host) {
2846
+ console.warn(
2847
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2848
+ );
2849
+ return () => {
2850
+ };
2851
+ }
2852
+ const detachInstalled = installScrimStages(
2853
+ {
2854
+ mode: this.mode,
2855
+ host,
2856
+ getState: () => ({
2857
+ activeId: this.activeId,
2858
+ size: this.size,
2859
+ progress: this.computeProgress(this.size)
2860
+ }),
2861
+ on: (event, fn) => this.on(event, fn),
2862
+ isDestroyed: () => this.destroyed
2863
+ },
2864
+ opts
2865
+ );
2866
+ const detach = () => {
2867
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2868
+ detachInstalled();
2869
+ };
2870
+ this.detachScrimStages = detach;
2871
+ this.teardowns.add(() => this.detachScrimStages?.());
2872
+ return detach;
2873
+ }
2273
2874
  getScrimState() {
2274
2875
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2275
2876
  }
@@ -2289,8 +2890,53 @@ var BottomSheetEngine = class {
2289
2890
  }
2290
2891
  this.updateAriaSlider();
2291
2892
  }
2893
+ recompute() {
2894
+ if (this.destroyed) return;
2895
+ this.snaps.recompute();
2896
+ this.scrim.invalidateOpacityCache();
2897
+ if (this.gesture?.isDragging) return;
2898
+ const current = this.snaps.findById(this.activeId);
2899
+ if (current) {
2900
+ this.size = current.size;
2901
+ this.applySize(this.size);
2902
+ this.updateAriaSlider();
2903
+ }
2904
+ }
2905
+ measureFitSize() {
2906
+ const vertical = layoutAxis(this.mode) === "height";
2907
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2908
+ const content = this.scrollContainer;
2909
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2910
+ if (typeof window === "undefined") return natural;
2911
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2912
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2913
+ }
2914
+ installFitObserver() {
2915
+ if (typeof ResizeObserver === "undefined") return;
2916
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2917
+ return;
2918
+ const targets = [this.handle];
2919
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2920
+ targets.push(this.scrollContainer);
2921
+ }
2922
+ let raf = 0;
2923
+ const ro = new ResizeObserver(() => {
2924
+ if (raf) return;
2925
+ raf = requestAnimationFrame(() => {
2926
+ raf = 0;
2927
+ if (this.destroyed || this.gesture?.isDragging) return;
2928
+ this.recompute();
2929
+ });
2930
+ });
2931
+ for (const t of targets) ro.observe(t);
2932
+ this.teardowns.add(() => {
2933
+ ro.disconnect();
2934
+ if (raf) cancelAnimationFrame(raf);
2935
+ });
2936
+ }
2292
2937
  destroy() {
2293
2938
  this.destroyed = true;
2939
+ this.opening = false;
2294
2940
  this.currentAbort.abort();
2295
2941
  this.currentViewTransition?.skipTransition?.();
2296
2942
  this.currentViewTransition = null;
@@ -2340,6 +2986,36 @@ var BottomSheetEngine = class {
2340
2986
  frozen = true;
2341
2987
  return cancelled;
2342
2988
  }
2989
+ emitBeforeClose(reason) {
2990
+ let cancelled = false;
2991
+ let frozen = false;
2992
+ this.emit("before-close", {
2993
+ reason,
2994
+ cancel: () => {
2995
+ if (frozen) {
2996
+ console.warn(
2997
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
2998
+ );
2999
+ return;
3000
+ }
3001
+ cancelled = true;
3002
+ }
3003
+ });
3004
+ frozen = true;
3005
+ return cancelled;
3006
+ }
3007
+ applyAutoAriaLabelledBy() {
3008
+ if (typeof document === "undefined") return;
3009
+ if (!this.lifecycle.focusTrapEnabled) return;
3010
+ if (this.element.getAttribute("aria-labelledby")) return;
3011
+ const scope = this.handle ?? this.element;
3012
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3013
+ if (!titled) return;
3014
+ if (!titled.id) {
3015
+ titled.id = nextInstanceId("bs-title");
3016
+ }
3017
+ this.element.setAttribute("aria-labelledby", titled.id);
3018
+ }
2343
3019
  recomputeSnaps() {
2344
3020
  const seen = /* @__PURE__ */ new Set();
2345
3021
  for (const p of this.snapPointsRaw) {
@@ -2356,24 +3032,42 @@ var BottomSheetEngine = class {
2356
3032
  getAllowedRange() {
2357
3033
  return this.snaps.getAllowedRange();
2358
3034
  }
3035
+ allowedIdsBySize() {
3036
+ return this.snaps.getAllowedIds().slice().sort(
3037
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
3038
+ );
3039
+ }
2359
3040
  updateAriaSlider() {
2360
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
3041
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2361
3042
  }
2362
3043
  registerInStack() {
2363
3044
  this.teardowns.add(sheetStack.push({
2364
3045
  id: this.id,
2365
3046
  setZIndex: (z) => {
3047
+ this.stackZ = z;
2366
3048
  this.element.style.zIndex = String(z);
2367
3049
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3050
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2368
3051
  },
2369
3052
  setIsTop: (isTop) => {
2370
3053
  this.isTopSheet = isTop;
2371
- if (this.backdrop) {
2372
- this.backdrop.style.display = isTop ? "" : "none";
2373
- }
2374
- }
3054
+ },
3055
+ isOpen: () => this.size > 0 || this.opening,
3056
+ setDepth: (depth) => this.applyStackDepth(depth)
2375
3057
  }));
2376
3058
  }
3059
+ applyStackDepth(depth) {
3060
+ if (!this.stackEffectEnabled) return;
3061
+ this.element.setAttribute("data-stack-depth", String(depth));
3062
+ if (!this.stackEffectPrimed) {
3063
+ this.stackEffectPrimed = true;
3064
+ const isVertical = this.mode === "bottom" || this.mode === "top";
3065
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
3066
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
3067
+ }
3068
+ const scale = Math.max(1 - depth * 0.04, 0.86);
3069
+ this.element.style.scale = scale === 1 ? "" : String(scale);
3070
+ }
2377
3071
  attach() {
2378
3072
  this.gesture = new GestureController({
2379
3073
  handle: this.handle,
@@ -2390,6 +3084,7 @@ var BottomSheetEngine = class {
2390
3084
  buf.rubberBandEnabled = this.rubberBandEnabled;
2391
3085
  return buf;
2392
3086
  },
3087
+ getDisableDrag: () => this.disableDragFlag,
2393
3088
  cancelAnimation: () => this.animation.cancel(),
2394
3089
  applySize: (size) => this.applySize(size),
2395
3090
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2433,7 +3128,7 @@ var BottomSheetEngine = class {
2433
3128
  container: this.scrollContainer,
2434
3129
  isDragging: () => this.gesture?.isDragging ?? false,
2435
3130
  isAnimating: () => this.animation.isAnimating,
2436
- getAllowedIds: () => this.snaps.getAllowedIds(),
3131
+ getAllowedIds: () => this.allowedIdsBySize(),
2437
3132
  getActiveId: () => this.activeId,
2438
3133
  snapTo: (id) => {
2439
3134
  void this.snapTo(id);
@@ -2462,13 +3157,16 @@ var BottomSheetEngine = class {
2462
3157
  },
2463
3158
  newCycle: () => {
2464
3159
  this.newCycle();
2465
- }
3160
+ },
3161
+ isAnimating: () => this.animation.isAnimating,
3162
+ resyncAfterCancel: () => this.resyncAfterResize()
2466
3163
  }));
2467
3164
  if (this.lifecycle.closeOnEscape) {
2468
3165
  const onKey = (e) => {
2469
- if (this.destroyed) return;
3166
+ if (this.destroyed || e.defaultPrevented) return;
3167
+ if (!this.canDismiss()) return;
2470
3168
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2471
- void this.close();
3169
+ void this.close("escape");
2472
3170
  }
2473
3171
  };
2474
3172
  document.addEventListener("keydown", onKey);
@@ -2498,7 +3196,7 @@ var BottomSheetEngine = class {
2498
3196
  handle: this.handle,
2499
3197
  mode: this.mode,
2500
3198
  isDestroyed: () => this.destroyed,
2501
- getAllowedIds: () => this.snaps.getAllowedIds(),
3199
+ getAllowedIds: () => this.allowedIdsBySize(),
2502
3200
  getActiveId: () => this.activeId,
2503
3201
  snapTo: (id) => {
2504
3202
  void this.snapTo(id);
@@ -2519,6 +3217,7 @@ var BottomSheetEngine = class {
2519
3217
  });
2520
3218
  if (!target) return;
2521
3219
  const previousId = this.activeId;
3220
+ if (target.id === previousId && target.size === this.size) return;
2522
3221
  if (this.emitBeforeSnap(target, previousId)) {
2523
3222
  const restore = this.snaps.findById(previousId);
2524
3223
  if (restore) {
@@ -2527,21 +3226,34 @@ var BottomSheetEngine = class {
2527
3226
  return;
2528
3227
  }
2529
3228
  const previousSize = this.size;
3229
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2530
3230
  this.scrollCache.cache(previousId, previousSize, target.size);
2531
3231
  this.activeId = target.id;
3232
+ if (previousSnapSize === 0 && target.size > 0) {
3233
+ this.opening = true;
3234
+ sheetStack.promote(this.id);
3235
+ }
2532
3236
  void this.animation.animateTo(target.size, velocity).then(() => {
3237
+ this.opening = false;
2533
3238
  if (signal.aborted) return;
2534
3239
  this.scrollCache.restore(target.id, previousSize, target.size);
2535
3240
  this.updateAriaSlider();
2536
- this.emit("snap", { id: target.id, size: target.size });
3241
+ this.emit("snap", {
3242
+ id: target.id,
3243
+ size: target.size,
3244
+ progress: this.computeProgress(target.size)
3245
+ });
2537
3246
  this.haptic();
2538
- if (previousId === "closed" && target.size > 0) {
3247
+ if (previousSnapSize === 0 && target.size > 0) {
2539
3248
  this.emit("open", { id: target.id });
2540
3249
  this.handleOpen();
3250
+ notifyLinkedSheets(this.linkedSheets, this);
3251
+ this.emit("opened", { id: target.id });
2541
3252
  }
2542
3253
  if (target.size === 0) {
2543
3254
  this.emit("close", void 0);
2544
3255
  this.handleClose();
3256
+ this.emit("closed", void 0);
2545
3257
  }
2546
3258
  });
2547
3259
  }
@@ -2563,6 +3275,9 @@ var BottomSheetEngine = class {
2563
3275
  if (this.scrimParent) {
2564
3276
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2565
3277
  }
3278
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3279
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3280
+ }
2566
3281
  }
2567
3282
  const progress = this.computeProgress(size);
2568
3283
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2571,6 +3286,9 @@ var BottomSheetEngine = class {
2571
3286
  );
2572
3287
  if (progressChanged) {
2573
3288
  style.setProperty("--bs-progress", String(progress));
3289
+ if (this.anchorHost) {
3290
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3291
+ }
2574
3292
  }
2575
3293
  this.scrim.applyOpacity(progress, progressChanged);
2576
3294
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2585,6 +3303,7 @@ var BottomSheetEngine = class {
2585
3303
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2586
3304
  }
2587
3305
  handleOpen() {
3306
+ sheetStack.promote(this.id);
2588
3307
  try {
2589
3308
  this.lifecycle.install();
2590
3309
  } catch (err) {
@@ -2598,8 +3317,31 @@ var BottomSheetEngine = class {
2598
3317
  });
2599
3318
  }
2600
3319
  }
3320
+ resyncAfterResize() {
3321
+ if (this.destroyed) return;
3322
+ const target = this.snaps.findById(this.activeId);
3323
+ if (!target) return;
3324
+ this.updateAriaSlider();
3325
+ this.emit("snap", {
3326
+ id: target.id,
3327
+ size: target.size,
3328
+ progress: this.computeProgress(target.size)
3329
+ });
3330
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3331
+ this.emit("open", { id: target.id });
3332
+ this.handleOpen();
3333
+ notifyLinkedSheets(this.linkedSheets, this);
3334
+ this.emit("opened", { id: target.id });
3335
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3336
+ this.emit("close", void 0);
3337
+ this.handleClose();
3338
+ this.emit("closed", void 0);
3339
+ }
3340
+ }
2601
3341
  handleClose() {
3342
+ this.opening = false;
2602
3343
  this.lifecycle.release();
3344
+ sheetStack.update();
2603
3345
  }
2604
3346
  };
2605
3347
 
@@ -2708,6 +3450,10 @@ var OverlayEngine = class {
2708
3450
  __publicField(this, "closeOnOutsidePointerEnabled");
2709
3451
  __publicField(this, "detachOutsidePointer", null);
2710
3452
  __publicField(this, "detachSwipe", null);
3453
+ __publicField(this, "respectReducedMotionEnabled");
3454
+ __publicField(this, "isTop", true);
3455
+ __publicField(this, "backPushed", false);
3456
+ __publicField(this, "movedToTarget", false);
2711
3457
  __publicField(this, "onOpenCb");
2712
3458
  __publicField(this, "onCloseCb");
2713
3459
  __publicField(this, "isOpen_", false);
@@ -2744,6 +3490,7 @@ var OverlayEngine = class {
2744
3490
  this.mountTarget = opts.mountTo;
2745
3491
  this.peekRaw = opts.peek;
2746
3492
  this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
3493
+ this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
2747
3494
  this.onOpenCb = opts.onOpen;
2748
3495
  this.onCloseCb = opts.onClose;
2749
3496
  if (this.peekRaw !== void 0 && this.edge !== "bottom") {
@@ -2791,15 +3538,17 @@ var OverlayEngine = class {
2791
3538
  this.emit("before-open", void 0);
2792
3539
  this.isOpen_ = true;
2793
3540
  this.isAnimating_ = true;
3541
+ sheetStack.promote(this.id);
2794
3542
  this.element.removeAttribute("hidden");
2795
3543
  this.element.setAttribute("data-state", "entering");
2796
3544
  if (this.focusTrapEnabled) {
2797
3545
  this.element.setAttribute("aria-modal", "true");
2798
3546
  }
2799
- const transitionStr = `transform ${this.duration}ms ${this.enterEasing}, opacity ${this.duration}ms ease-out`;
3547
+ const dur = this.effectiveDuration();
3548
+ const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
2800
3549
  this.element.style.transition = transitionStr;
2801
3550
  if (this.backdrop) {
2802
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-out`;
3551
+ this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
2803
3552
  }
2804
3553
  const cycle = ++this.cycleNonce;
2805
3554
  return new Promise((resolve) => {
@@ -2811,7 +3560,7 @@ var OverlayEngine = class {
2811
3560
  this.element.style.opacity = "1";
2812
3561
  if (this.backdrop) {
2813
3562
  this.backdrop.style.opacity = String(this.backdropOpacity);
2814
- this.backdrop.style.pointerEvents = "auto";
3563
+ this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
2815
3564
  if (this.backdropFilter) {
2816
3565
  this.backdrop.style.backdropFilter = this.backdropFilter;
2817
3566
  this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
@@ -2847,7 +3596,10 @@ var OverlayEngine = class {
2847
3596
  });
2848
3597
  return resolve();
2849
3598
  }
2850
- this.awaitTransition(cycle).then(() => {
3599
+ this.awaitTransition(
3600
+ cycle,
3601
+ this.enterAnimation === "fade" ? "opacity" : "transform"
3602
+ ).then(() => {
2851
3603
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
2852
3604
  this.isAnimating_ = false;
2853
3605
  this.element.setAttribute("data-state", "open");
@@ -2864,11 +3616,14 @@ var OverlayEngine = class {
2864
3616
  this.emit("before-close", void 0);
2865
3617
  this.isOpen_ = false;
2866
3618
  this.isAnimating_ = true;
3619
+ sheetStack.update();
3620
+ this.popBackMarker(reason);
2867
3621
  this.element.setAttribute("data-state", "exiting");
2868
- const transitionStr = `transform ${this.duration}ms ${this.exitEasing}, opacity ${this.duration}ms ease-in`;
3622
+ const dur = this.effectiveDuration();
3623
+ const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
2869
3624
  this.element.style.transition = transitionStr;
2870
3625
  if (this.backdrop) {
2871
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-in`;
3626
+ this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
2872
3627
  }
2873
3628
  const cycle = ++this.cycleNonce;
2874
3629
  return new Promise((resolve) => {
@@ -2879,7 +3634,7 @@ var OverlayEngine = class {
2879
3634
  this.exitAnimation,
2880
3635
  this.peekPx
2881
3636
  );
2882
- this.element.style.opacity = "0";
3637
+ this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
2883
3638
  if (this.backdrop) {
2884
3639
  this.backdrop.style.opacity = "0";
2885
3640
  this.backdrop.style.pointerEvents = "none";
@@ -2889,7 +3644,10 @@ var OverlayEngine = class {
2889
3644
  }
2890
3645
  }
2891
3646
  this.releaseInteractiveListeners();
2892
- this.awaitTransition(cycle).then(() => {
3647
+ this.awaitTransition(
3648
+ cycle,
3649
+ this.exitAnimation === "fade" ? "opacity" : "transform"
3650
+ ).then(() => {
2893
3651
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
2894
3652
  this.isAnimating_ = false;
2895
3653
  if (this.peekPx === void 0) {
@@ -2917,7 +3675,11 @@ var OverlayEngine = class {
2917
3675
  this.releaseInteractiveListeners();
2918
3676
  this.releaseStackEntry?.();
2919
3677
  this.releaseStackEntry = null;
3678
+ const hadOriginalParent = this.originalParent !== null;
2920
3679
  this.restoreMount();
3680
+ if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
3681
+ this.element.parentNode.removeChild(this.element);
3682
+ }
2921
3683
  }
2922
3684
  setBackdropOpacity(opacity) {
2923
3685
  if (this.destroyed) return;
@@ -3018,6 +3780,36 @@ var OverlayEngine = class {
3018
3780
  }
3019
3781
  }
3020
3782
  }
3783
+ if (opts.duration !== void 0) this.duration = opts.duration;
3784
+ if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
3785
+ if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
3786
+ if (opts.closeOnEscape !== void 0) {
3787
+ this.closeOnEscape = opts.closeOnEscape;
3788
+ if (!opts.closeOnEscape) {
3789
+ this.detachEscape?.();
3790
+ this.detachEscape = null;
3791
+ } else if (this.isOpen_) {
3792
+ this.installInteractiveListeners();
3793
+ }
3794
+ }
3795
+ if (opts.closeOnBackdrop !== void 0) {
3796
+ this.closeOnBackdropEnabled = opts.closeOnBackdrop;
3797
+ if (!opts.closeOnBackdrop) {
3798
+ this.detachBackdrop?.();
3799
+ this.detachBackdrop = null;
3800
+ } else if (this.isOpen_) {
3801
+ this.installInteractiveListeners();
3802
+ }
3803
+ }
3804
+ if (opts.closeOnOutsidePointer !== void 0) {
3805
+ this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
3806
+ if (!opts.closeOnOutsidePointer) {
3807
+ this.detachOutsidePointer?.();
3808
+ this.detachOutsidePointer = null;
3809
+ } else if (this.isOpen_) {
3810
+ this.installInteractiveListeners();
3811
+ }
3812
+ }
3021
3813
  if (opts.children !== void 0) {
3022
3814
  if (opts.children === null) {
3023
3815
  this.clearOverlayChildren();
@@ -3051,6 +3843,22 @@ var OverlayEngine = class {
3051
3843
  emit(event, payload) {
3052
3844
  this.bus.emit(event, payload);
3053
3845
  }
3846
+ effectiveDuration() {
3847
+ return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
3848
+ }
3849
+ popBackMarker(reason) {
3850
+ if (!this.backPushed) return;
3851
+ this.backPushed = false;
3852
+ if (reason === "back") return;
3853
+ const state = history.state;
3854
+ if (state && state.__bsOverlay === this.id) {
3855
+ try {
3856
+ history.back();
3857
+ } catch {
3858
+ return;
3859
+ }
3860
+ }
3861
+ }
3054
3862
  resolvePeekPx(raw) {
3055
3863
  if (typeof raw === "number") return Math.max(0, raw);
3056
3864
  try {
@@ -3072,6 +3880,7 @@ var OverlayEngine = class {
3072
3880
  this.originalParent = this.element.parentNode;
3073
3881
  this.originalNextSibling = this.element.nextSibling;
3074
3882
  target.appendChild(this.element);
3883
+ this.movedToTarget = true;
3075
3884
  this.mounted = true;
3076
3885
  }
3077
3886
  restoreMount() {
@@ -3105,7 +3914,7 @@ var OverlayEngine = class {
3105
3914
  this.backdrop.style.willChange = "opacity";
3106
3915
  }
3107
3916
  }
3108
- awaitTransition(cycle) {
3917
+ awaitTransition(cycle, property = "transform") {
3109
3918
  return new Promise((resolve) => {
3110
3919
  let done = false;
3111
3920
  const finish = () => {
@@ -3120,10 +3929,10 @@ var OverlayEngine = class {
3120
3929
  finish();
3121
3930
  return;
3122
3931
  }
3123
- if (e.propertyName === "transform") finish();
3932
+ if (e.target === this.element && e.propertyName === property) finish();
3124
3933
  };
3125
3934
  this.element.addEventListener("transitionend", onEnd);
3126
- const timer = window.setTimeout(finish, this.duration + 50);
3935
+ const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
3127
3936
  });
3128
3937
  }
3129
3938
  installInteractiveListeners() {
@@ -3133,7 +3942,10 @@ var OverlayEngine = class {
3133
3942
  }
3134
3943
  if (this.closeOnEscape && !this.detachEscape) {
3135
3944
  const handler = (e) => {
3136
- if (e.key === "Escape" && this.isOpen_) void this.close("escape");
3945
+ if (e.defaultPrevented) return;
3946
+ if (e.key === "Escape" && this.isOpen_ && this.isTop) {
3947
+ void this.close("escape");
3948
+ }
3137
3949
  };
3138
3950
  document.addEventListener("keydown", handler);
3139
3951
  this.detachEscape = () => document.removeEventListener("keydown", handler);
@@ -3146,10 +3958,14 @@ var OverlayEngine = class {
3146
3958
  if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
3147
3959
  try {
3148
3960
  history.pushState({ __bsOverlay: this.id }, "");
3961
+ this.backPushed = true;
3149
3962
  } catch {
3150
3963
  }
3151
3964
  const onPop = () => {
3152
- if (this.isOpen_) void this.close("back");
3965
+ if (this.isOpen_ && this.isTop) {
3966
+ this.backPushed = false;
3967
+ void this.close("back");
3968
+ }
3153
3969
  };
3154
3970
  window.addEventListener("popstate", onPop);
3155
3971
  this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
@@ -3185,43 +4001,52 @@ var OverlayEngine = class {
3185
4001
  const edge = this.edge;
3186
4002
  const isVertical = edge === "bottom" || edge === "top";
3187
4003
  const sign = edge === "bottom" || edge === "right" ? 1 : -1;
3188
- const threshold = this.swipeThreshold;
3189
- const velocityThreshold = this.swipeVelocityThreshold;
4004
+ const SLOP = 8;
3190
4005
  let startX = 0;
3191
4006
  let startY = 0;
3192
4007
  let startT = 0;
4008
+ let tracking = false;
3193
4009
  let dragging = false;
3194
4010
  let pointerId = null;
4011
+ const previousTouchAction = el.style.touchAction;
4012
+ el.style.touchAction = isVertical ? "pan-x" : "pan-y";
3195
4013
  const onDown = (e) => {
3196
4014
  if (e.button !== void 0 && e.button !== 0) return;
3197
4015
  pointerId = e.pointerId;
3198
4016
  startX = e.clientX;
3199
4017
  startY = e.clientY;
3200
4018
  startT = performance.now();
3201
- dragging = true;
3202
- el.style.transition = "none";
3203
- try {
3204
- el.setPointerCapture(e.pointerId);
3205
- } catch {
3206
- }
4019
+ tracking = true;
4020
+ dragging = false;
3207
4021
  };
3208
4022
  const onMove = (e) => {
3209
- if (!dragging || e.pointerId !== pointerId) return;
4023
+ if (!tracking || e.pointerId !== pointerId) return;
3210
4024
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
4025
+ if (!dragging) {
4026
+ if (Math.abs(delta) < SLOP) return;
4027
+ dragging = true;
4028
+ el.style.transition = "none";
4029
+ try {
4030
+ el.setPointerCapture(e.pointerId);
4031
+ } catch {
4032
+ }
4033
+ }
3211
4034
  const offset = Math.max(0, sign * delta);
3212
4035
  el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
3213
4036
  };
3214
4037
  const finish = (e) => {
3215
- if (!dragging || e.pointerId !== pointerId) return;
3216
- dragging = false;
4038
+ if (!tracking || e.pointerId !== pointerId) return;
4039
+ tracking = false;
3217
4040
  pointerId = null;
4041
+ if (!dragging) return;
4042
+ dragging = false;
3218
4043
  const dt = performance.now() - startT || 1;
3219
4044
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3220
4045
  const offset = Math.max(0, sign * delta);
3221
4046
  const velocity = sign * delta / dt;
3222
4047
  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}`;
4048
+ const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
4049
+ el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
3225
4050
  if (past) {
3226
4051
  void this.close("swipe");
3227
4052
  } else {
@@ -3233,6 +4058,7 @@ var OverlayEngine = class {
3233
4058
  el.addEventListener("pointerup", finish);
3234
4059
  el.addEventListener("pointercancel", finish);
3235
4060
  return () => {
4061
+ el.style.touchAction = previousTouchAction;
3236
4062
  el.removeEventListener("pointerdown", onDown);
3237
4063
  el.removeEventListener("pointermove", onMove);
3238
4064
  el.removeEventListener("pointerup", finish);
@@ -3247,10 +4073,12 @@ var OverlayEngine = class {
3247
4073
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3248
4074
  },
3249
4075
  setIsTop: (isTop) => {
4076
+ this.isTop = isTop;
3250
4077
  if (this.backdrop) {
3251
4078
  this.backdrop.style.display = isTop ? "" : "none";
3252
4079
  }
3253
- }
4080
+ },
4081
+ isOpen: () => this.isOpen_
3254
4082
  });
3255
4083
  }
3256
4084
  };
@@ -3263,6 +4091,7 @@ exports.Overlay = OverlayEngine;
3263
4091
  exports.OverlayEngine = OverlayEngine;
3264
4092
  exports.SCRIM_PRESETS = SCRIM_PRESETS;
3265
4093
  exports.allowedRange = allowedRange;
4094
+ exports.applyOverlayPosition = applyOverlayPosition;
3266
4095
  exports.attachGestures = attachGestures;
3267
4096
  exports.createOverlay = createOverlay;
3268
4097
  exports.createSheetManager = createSheetManager;
@@ -3274,8 +4103,10 @@ exports.installFocusTrap = installFocusTrap;
3274
4103
  exports.installGestures = installGestures;
3275
4104
  exports.lockBodyScroll = lockBodyScroll;
3276
4105
  exports.prefersReducedMotion = prefersReducedMotion;
4106
+ exports.resolveSheetAnchoredStyle = resolveSheetAnchoredStyle;
3277
4107
  exports.resolveSnap = resolveSnap;
3278
4108
  exports.resolveSnapList = resolveSnapList;
4109
+ exports.runAnchorTransition = runAnchorTransition;
3279
4110
  exports.runSpring = runSpring;
3280
4111
  exports.sheetStack = sheetStack;
3281
4112
  exports.tween = tween;