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