@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/svelte.cjs CHANGED
@@ -46,16 +46,52 @@ var SheetStack = class {
46
46
  this.entries.splice(idx, 1);
47
47
  this.recompute();
48
48
  }
49
+ promote(id) {
50
+ const idx = this.entries.findIndex((e) => e.id === id);
51
+ if (idx === -1) return;
52
+ if (idx !== this.entries.length - 1) {
53
+ const [entry] = this.entries.splice(idx, 1);
54
+ this.entries.push(entry);
55
+ }
56
+ this.recompute();
57
+ }
58
+ update() {
59
+ this.recompute();
60
+ }
49
61
  size() {
50
62
  return this.entries.length;
51
63
  }
64
+ depthOf(id) {
65
+ const open = [];
66
+ for (const entry of this.entries) {
67
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
68
+ }
69
+ const idx = open.findIndex((e) => e.id === id);
70
+ if (idx === -1) return 0;
71
+ return open.length - 1 - idx;
72
+ }
52
73
  clear() {
53
74
  this.entries = [];
54
75
  }
55
76
  recompute() {
77
+ let top = null;
78
+ const open = [];
79
+ for (const entry of this.entries) {
80
+ if (!entry.isOpen || entry.isOpen()) {
81
+ top = entry;
82
+ open.push(entry);
83
+ }
84
+ }
85
+ if (!top && this.entries.length > 0) {
86
+ top = this.entries[this.entries.length - 1];
87
+ }
56
88
  this.entries.forEach((entry, i) => {
57
89
  entry.setZIndex(BASE_Z + i * STEP);
58
- entry.setIsTop(i === this.entries.length - 1);
90
+ entry.setIsTop(entry === top);
91
+ if (entry.setDepth) {
92
+ const openIdx = open.indexOf(entry);
93
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
94
+ }
59
95
  });
60
96
  }
61
97
  };
@@ -152,7 +188,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
152
188
  var resolveSnap = (point, mode, measureFit) => {
153
189
  if (typeof point === "number") return Math.max(0, point);
154
190
  if (point === "full") return viewportSize(mode);
155
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
191
+ if (point === "fit" || point === "content") {
192
+ return Math.max(0, measureFit?.() ?? 0);
193
+ }
156
194
  const pctMatch = point.match(PERCENT);
157
195
  if (pctMatch) {
158
196
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -340,14 +378,17 @@ function installAutoCollapse(deps) {
340
378
  const fire = () => {
341
379
  if (deps.isDestroyed()) return;
342
380
  if (deps.isDragging()) return;
343
- const allowed = deps.getAllowedIds();
344
- const target = allowed.find((id) => {
381
+ const active = deps.resolveSnap(deps.getActiveId());
382
+ if (!active || active.size === 0) return;
383
+ let target = null;
384
+ for (const id of deps.getAllowedIds()) {
345
385
  const snap = deps.resolveSnap(id);
346
- return snap !== null && snap.size > 0;
347
- });
386
+ if (!snap || snap.size <= 0) continue;
387
+ if (!target || snap.size < target.size) target = snap;
388
+ }
348
389
  if (!target) return;
349
- if (target === deps.getActiveId()) return;
350
- deps.snapTo(target);
390
+ if (target.id === active.id || target.size >= active.size) return;
391
+ deps.snapTo(target.id);
351
392
  };
352
393
  const reset2 = () => {
353
394
  clearTimer();
@@ -358,6 +399,7 @@ function installAutoCollapse(deps) {
358
399
  }, ms);
359
400
  };
360
401
  const unsubscribe = deps.on("snap", () => reset2());
402
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset2();
361
403
  return () => {
362
404
  clearTimer();
363
405
  unsubscribe();
@@ -370,6 +412,7 @@ function notifyLinkedSheets(sheets, self) {
370
412
  for (const linked of sheets) {
371
413
  if (linked === self) continue;
372
414
  try {
415
+ if (linked.state.size === 0) continue;
373
416
  const ids = linked.getAllowedIds();
374
417
  const target = ids.find((id) => id !== "closed");
375
418
  if (target && target !== linked.state.activeId) {
@@ -384,6 +427,42 @@ function notifyLinkedSheets(sheets, self) {
384
427
  }
385
428
 
386
429
  // src/core/features/route.ts
430
+ var closers = [];
431
+ var handlerInstalled = false;
432
+ var suppress = 0;
433
+ var ensureHandler = () => {
434
+ if (handlerInstalled || typeof window === "undefined") return;
435
+ handlerInstalled = true;
436
+ window.addEventListener("popstate", () => {
437
+ if (suppress > 0) {
438
+ suppress -= 1;
439
+ return;
440
+ }
441
+ let target = null;
442
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
443
+ const c = closers[i];
444
+ if (!c.isOpen()) continue;
445
+ if (!target) target = c;
446
+ if (c.isTop()) {
447
+ target = c;
448
+ break;
449
+ }
450
+ }
451
+ if (!target) return;
452
+ target.consumed = true;
453
+ const idx = closers.indexOf(target);
454
+ if (idx !== -1) closers.splice(idx, 1);
455
+ target.close();
456
+ });
457
+ };
458
+ var programmaticBack = () => {
459
+ suppress += 1;
460
+ try {
461
+ history.back();
462
+ } catch {
463
+ suppress = Math.max(0, suppress - 1);
464
+ }
465
+ };
387
466
  function installRoute(deps) {
388
467
  if (typeof window === "undefined") {
389
468
  return () => {
@@ -393,82 +472,81 @@ function installRoute(deps) {
393
472
  return () => {
394
473
  };
395
474
  }
396
- let routePushed = false;
397
- let closeOnBackPushed = false;
398
- let detachPopstate = null;
399
- const popOurMarker = (key, expected) => {
400
- const state2 = history.state;
401
- if (state2 && state2[key] === expected) {
402
- try {
403
- history.back();
404
- } catch {
405
- }
475
+ let pushed = false;
476
+ let closer = null;
477
+ const teardownEntry = () => {
478
+ if (closer) {
479
+ const idx = closers.indexOf(closer);
480
+ if (idx !== -1) closers.splice(idx, 1);
481
+ }
482
+ const wasConsumed = closer?.consumed ?? false;
483
+ closer = null;
484
+ if (pushed && !wasConsumed) {
485
+ pushed = false;
486
+ programmaticBack();
487
+ } else {
488
+ pushed = false;
406
489
  }
407
490
  };
408
491
  const onOpen = () => {
409
- if (deps.routedTo && !detachPopstate) {
410
- const target = deps.routedTo;
411
- try {
412
- history.pushState({ __bsRouted: target }, "", target);
413
- routePushed = true;
414
- } catch {
415
- }
416
- const onPop = (event) => {
417
- if (deps.isDestroyed()) return;
418
- const state2 = event.state;
419
- if (state2 && state2.__bsRouted === target && deps.getSize() > 0) {
420
- routePushed = false;
421
- void deps.close();
422
- }
423
- };
424
- window.addEventListener("popstate", onPop);
425
- detachPopstate = () => window.removeEventListener("popstate", onPop);
426
- } else if (deps.closeOnBack && !detachPopstate) {
427
- try {
492
+ if (closer) return;
493
+ closer = {
494
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
495
+ isTop: () => deps.isTopSheet(),
496
+ close: () => void deps.close(),
497
+ consumed: false
498
+ };
499
+ ensureHandler();
500
+ closers.push(closer);
501
+ try {
502
+ if (deps.routedTo !== void 0) {
503
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
504
+ } else {
428
505
  history.pushState({ __bsSheet: deps.sheetId }, "");
429
- closeOnBackPushed = true;
430
- } catch {
431
506
  }
432
- const onPop = () => {
433
- if (deps.isDestroyed()) return;
434
- if (deps.getSize() > 0 && deps.isTopSheet()) {
435
- const state2 = history.state;
436
- if (state2?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
437
- void deps.close();
438
- }
439
- };
440
- window.addEventListener("popstate", onPop);
441
- detachPopstate = () => window.removeEventListener("popstate", onPop);
442
- }
443
- };
444
- const onClose = () => {
445
- detachPopstate?.();
446
- detachPopstate = null;
447
- if (routePushed) {
448
- routePushed = false;
449
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
450
- }
451
- if (closeOnBackPushed) {
452
- closeOnBackPushed = false;
453
- popOurMarker("__bsSheet", deps.sheetId);
507
+ pushed = true;
508
+ } catch {
509
+ pushed = false;
454
510
  }
455
511
  };
456
512
  const unsubscribeOpen = deps.on("open", onOpen);
457
- const unsubscribeClose = deps.on("close", onClose);
513
+ const unsubscribeClose = deps.on("close", teardownEntry);
458
514
  if (deps.getSize() > 0) onOpen();
459
515
  return () => {
460
516
  unsubscribeOpen();
461
517
  unsubscribeClose();
462
- detachPopstate?.();
463
- detachPopstate = null;
464
- if (routePushed) {
465
- routePushed = false;
466
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
467
- }
468
- if (closeOnBackPushed) {
469
- closeOnBackPushed = false;
470
- popOurMarker("__bsSheet", deps.sheetId);
471
- }
518
+ teardownEntry();
519
+ };
520
+ }
521
+ function installRouteChange(deps) {
522
+ if (typeof window === "undefined" || typeof history === "undefined") {
523
+ return () => {
524
+ };
525
+ }
526
+ const onChange = () => {
527
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
528
+ void deps.close();
529
+ };
530
+ const wrap = (key) => {
531
+ const original = history[key];
532
+ const patched = function(...args) {
533
+ original.apply(this, args);
534
+ onChange();
535
+ };
536
+ history[key] = patched;
537
+ return () => {
538
+ if (history[key] === patched) {
539
+ history[key] = original;
540
+ }
541
+ };
542
+ };
543
+ window.addEventListener("popstate", onChange);
544
+ const restorePush = wrap("pushState");
545
+ const restoreReplace = wrap("replaceState");
546
+ return () => {
547
+ window.removeEventListener("popstate", onChange);
548
+ restorePush();
549
+ restoreReplace();
472
550
  };
473
551
  }
474
552
 
@@ -570,13 +648,17 @@ function installResizeObserver(deps) {
570
648
  if (viewportSize2 > 0 && viewportSize2 < max) {
571
649
  deps.setMaxAxisSize(viewportSize2);
572
650
  }
651
+ const wasAnimating = deps.isAnimating?.() ?? false;
573
652
  deps.cancelInFlight();
574
653
  deps.newCycle();
575
654
  const current = deps.resolveActiveSnap();
576
655
  const newMax = deps.getMaxAxisSize();
577
656
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
578
657
  deps.setSize(targetSize);
579
- if (current && !deps.isDragging()) deps.applySize(targetSize);
658
+ if (current && !deps.isDragging()) {
659
+ deps.applySize(targetSize);
660
+ if (wasAnimating) deps.resyncAfterCancel?.();
661
+ }
580
662
  };
581
663
  let resizeObserver = null;
582
664
  if (typeof ResizeObserver !== "undefined") {
@@ -691,8 +773,88 @@ function devWarn(...args) {
691
773
  }
692
774
  }
693
775
 
776
+ // src/core/primitives/overlay-position.ts
777
+ function resolveSheetAnchoredStyle(mode, position, inset) {
778
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
779
+ switch (mode) {
780
+ case "bottom":
781
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
782
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
783
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
784
+ case "top":
785
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
786
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
787
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
788
+ case "left":
789
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
790
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
791
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
792
+ case "right":
793
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
794
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
795
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
796
+ }
797
+ }
798
+ function applyOverlayPosition(ws, mode, position, inset) {
799
+ switch (position) {
800
+ case "top-left":
801
+ ws.top = inset;
802
+ ws.left = inset;
803
+ break;
804
+ case "top-center":
805
+ ws.top = inset;
806
+ ws.left = "50%";
807
+ ws.transform = "translateX(-50%)";
808
+ break;
809
+ case "top-right":
810
+ ws.top = inset;
811
+ ws.right = inset;
812
+ break;
813
+ case "center-left":
814
+ ws.top = "50%";
815
+ ws.left = inset;
816
+ ws.transform = "translateY(-50%)";
817
+ break;
818
+ case "center":
819
+ ws.top = "50%";
820
+ ws.left = "50%";
821
+ ws.transform = "translate(-50%, -50%)";
822
+ break;
823
+ case "center-right":
824
+ ws.top = "50%";
825
+ ws.right = inset;
826
+ ws.transform = "translateY(-50%)";
827
+ break;
828
+ case "bottom-left":
829
+ ws.bottom = inset;
830
+ ws.left = inset;
831
+ break;
832
+ case "bottom-center":
833
+ ws.bottom = inset;
834
+ ws.left = "50%";
835
+ ws.transform = "translateX(-50%)";
836
+ break;
837
+ case "bottom-right":
838
+ ws.bottom = inset;
839
+ ws.right = inset;
840
+ break;
841
+ case "sheet-top-left":
842
+ case "sheet-top-center":
843
+ case "sheet-top-right": {
844
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
845
+ if (style.top !== void 0) ws.top = style.top;
846
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
847
+ if (style.left !== void 0) ws.left = style.left;
848
+ if (style.right !== void 0) ws.right = style.right;
849
+ if (style.transform !== void 0) ws.transform = style.transform;
850
+ break;
851
+ }
852
+ }
853
+ }
854
+
694
855
  // src/core/controllers/scrim-controller.ts
695
856
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
857
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
696
858
  var ScrimController = class {
697
859
  constructor(deps, opts) {
698
860
  __publicField(this, "screenComponent");
@@ -703,6 +865,8 @@ var ScrimController = class {
703
865
  __publicField(this, "getAllowedIds");
704
866
  __publicField(this, "getResolvedSnaps");
705
867
  __publicField(this, "snapToFn");
868
+ __publicField(this, "closeFn");
869
+ __publicField(this, "scrimInteractive", false);
706
870
  __publicField(this, "scrimMode");
707
871
  __publicField(this, "backdropRange");
708
872
  __publicField(this, "screenRange");
@@ -723,14 +887,17 @@ var ScrimController = class {
723
887
  this.getAllowedIds = deps.getAllowedIds;
724
888
  this.getResolvedSnaps = deps.getResolvedSnaps;
725
889
  this.snapToFn = deps.snapTo;
890
+ this.closeFn = deps.close;
726
891
  this.scrimMode = opts.scrimMode ?? "full";
727
892
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
728
893
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
729
894
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
730
895
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
896
+ const explicitColor = opts.scrimColor ?? preset?.color;
897
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
731
898
  if (this.screenComponent) {
732
899
  this.applyScrimStyles({
733
- color: opts.scrimColor ?? preset?.color,
900
+ color: effectiveColor,
734
901
  blur: opts.scrimBlur ?? preset?.blur,
735
902
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
736
903
  });
@@ -864,11 +1031,15 @@ var ScrimController = class {
864
1031
  }
865
1032
  const onClick = () => {
866
1033
  if (this.isDestroyed()) return;
1034
+ if (this.closeFn) {
1035
+ this.closeFn();
1036
+ return;
1037
+ }
867
1038
  const allowed = this.getAllowedIds();
868
1039
  const snaps = this.getResolvedSnaps();
869
1040
  const fallback = allowed.find((id) => {
870
1041
  const snap = findById(id, snaps);
871
- return snap !== null && snap.size > 0;
1042
+ return snap !== null && snap.size === 0;
872
1043
  });
873
1044
  if (fallback) this.snapToFn(fallback);
874
1045
  };
@@ -885,6 +1056,7 @@ var ScrimController = class {
885
1056
  if (this.isDestroyed()) return;
886
1057
  if (enabled === this.scrimEnabled) return;
887
1058
  this.scrimEnabled = enabled;
1059
+ this.invalidateOpacityCache();
888
1060
  if (!enabled) {
889
1061
  this.savedScrimRanges = {
890
1062
  screen: this.screenRange,
@@ -908,7 +1080,12 @@ var ScrimController = class {
908
1080
  blur: cfg.blur ?? null,
909
1081
  interactive: cfg.interactive
910
1082
  });
911
- this.screenRange = cfg.range;
1083
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1084
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1085
+ } else {
1086
+ this.screenRange = cfg.range;
1087
+ this.backdropRange = cfg.range;
1088
+ }
912
1089
  }
913
1090
  const noop = () => {
914
1091
  };
@@ -958,59 +1135,7 @@ var ScrimController = class {
958
1135
  wrapper.className = "bs-scrim-overlay";
959
1136
  const ws = wrapper.style;
960
1137
  ws.position = "absolute";
961
- switch (position) {
962
- case "top-left":
963
- ws.top = inset;
964
- ws.left = inset;
965
- break;
966
- case "top-center":
967
- ws.top = inset;
968
- ws.left = "50%";
969
- ws.transform = "translateX(-50%)";
970
- break;
971
- case "top-right":
972
- ws.top = inset;
973
- ws.right = inset;
974
- break;
975
- case "center-left":
976
- ws.top = "50%";
977
- ws.left = inset;
978
- ws.transform = "translateY(-50%)";
979
- break;
980
- case "center":
981
- ws.top = "50%";
982
- ws.left = "50%";
983
- ws.transform = "translate(-50%, -50%)";
984
- break;
985
- case "center-right":
986
- ws.top = "50%";
987
- ws.right = inset;
988
- ws.transform = "translateY(-50%)";
989
- break;
990
- case "bottom-left":
991
- ws.bottom = inset;
992
- ws.left = inset;
993
- break;
994
- case "bottom-center":
995
- ws.bottom = inset;
996
- ws.left = "50%";
997
- ws.transform = "translateX(-50%)";
998
- break;
999
- case "bottom-right":
1000
- ws.bottom = inset;
1001
- ws.right = inset;
1002
- break;
1003
- case "sheet-top-left":
1004
- case "sheet-top-center":
1005
- case "sheet-top-right":
1006
- applySheetAnchoredPosition(
1007
- ws,
1008
- this.mode,
1009
- position,
1010
- inset
1011
- );
1012
- break;
1013
- }
1138
+ applyOverlayPosition(ws, this.mode, position, inset);
1014
1139
  if (interactive) {
1015
1140
  ws.pointerEvents = "auto";
1016
1141
  }
@@ -1069,6 +1194,7 @@ var ScrimController = class {
1069
1194
  }
1070
1195
  }
1071
1196
  if (opts.interactive !== void 0) {
1197
+ this.scrimInteractive = opts.interactive;
1072
1198
  s.pointerEvents = opts.interactive ? "auto" : "none";
1073
1199
  }
1074
1200
  }
@@ -1083,38 +1209,9 @@ var ScrimController = class {
1083
1209
  const s = this.screenComponent.style;
1084
1210
  s.position = "fixed";
1085
1211
  s.inset = insetByMode[this.mode];
1086
- s.pointerEvents = "none";
1212
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1087
1213
  }
1088
1214
  };
1089
- function resolveSheetAnchoredStyle(mode, position, inset) {
1090
- const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1091
- switch (mode) {
1092
- case "bottom":
1093
- if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1094
- if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1095
- return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1096
- case "top":
1097
- if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1098
- if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1099
- return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1100
- case "left":
1101
- if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1102
- if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1103
- return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1104
- case "right":
1105
- if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1106
- if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1107
- return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1108
- }
1109
- }
1110
- function applySheetAnchoredPosition(ws, mode, position, inset) {
1111
- const style = resolveSheetAnchoredStyle(mode, position, inset);
1112
- if (style.top !== void 0) ws.top = style.top;
1113
- if (style.bottom !== void 0) ws.bottom = style.bottom;
1114
- if (style.left !== void 0) ws.left = style.left;
1115
- if (style.right !== void 0) ws.right = style.right;
1116
- if (style.transform !== void 0) ws.transform = style.transform;
1117
- }
1118
1215
 
1119
1216
  // src/core/animation/animation.ts
1120
1217
  var easeOutBack = (t) => {
@@ -1166,6 +1263,10 @@ var tween = ({
1166
1263
  promise
1167
1264
  };
1168
1265
  };
1266
+ var prefersReducedMotion = () => {
1267
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1268
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1269
+ };
1169
1270
 
1170
1271
  // src/core/animation/spring.ts
1171
1272
  var DEFAULT_SPRING = {
@@ -1196,14 +1297,20 @@ var runSpring = ({
1196
1297
  if (cancelled) return;
1197
1298
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1198
1299
  lastTime = now;
1199
- const subDt = Math.min(dt, 1 / 240);
1200
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1300
+ const steps = Math.max(1, Math.ceil(dt * 240));
1301
+ const subDt = dt / steps;
1302
+ let stepsLeft = steps;
1201
1303
  while (stepsLeft-- > 0) {
1202
1304
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1203
1305
  const accel = force / cfg.mass;
1204
1306
  v += accel * subDt;
1205
1307
  x += v * subDt;
1206
1308
  }
1309
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1310
+ onUpdate(to, 0);
1311
+ resolve();
1312
+ return;
1313
+ }
1207
1314
  onUpdate(x, v);
1208
1315
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1209
1316
  if (isAtRest) {
@@ -1378,9 +1485,13 @@ var isVisible = (el) => {
1378
1485
  var focusables = (root2) => Array.from(root2.querySelectorAll(FOCUSABLE)).filter(
1379
1486
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1380
1487
  );
1488
+ var trapStack = [];
1381
1489
  var installFocusTrap = (container, options = {}) => {
1382
1490
  if (typeof document === "undefined") return () => {
1383
1491
  };
1492
+ const token = {};
1493
+ trapStack.push(token);
1494
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1384
1495
  const previouslyFocused = document.activeElement;
1385
1496
  const focusInitial = () => {
1386
1497
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1388,6 +1499,7 @@ var installFocusTrap = (container, options = {}) => {
1388
1499
  };
1389
1500
  focusInitial();
1390
1501
  const handleKey = (e) => {
1502
+ if (!isActive()) return;
1391
1503
  if (e.key === "Escape" && options.onEscape) {
1392
1504
  options.onEscape();
1393
1505
  return;
@@ -1410,6 +1522,7 @@ var installFocusTrap = (container, options = {}) => {
1410
1522
  }
1411
1523
  };
1412
1524
  const handleFocusIn = (e) => {
1525
+ if (!isActive()) return;
1413
1526
  const target = e.target;
1414
1527
  if (target && !container.contains(target)) {
1415
1528
  const list = focusables(container);
@@ -1422,6 +1535,8 @@ var installFocusTrap = (container, options = {}) => {
1422
1535
  return () => {
1423
1536
  if (released) return;
1424
1537
  released = true;
1538
+ const idx = trapStack.indexOf(token);
1539
+ if (idx !== -1) trapStack.splice(idx, 1);
1425
1540
  document.removeEventListener("keydown", handleKey, true);
1426
1541
  document.removeEventListener("focusin", handleFocusIn, true);
1427
1542
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1477,11 +1592,17 @@ function createInertSiblings(rootProvider) {
1477
1592
  return {
1478
1593
  apply() {
1479
1594
  if (typeof document === "undefined") return;
1480
- const root2 = rootProvider();
1481
- const parent = root2.parentElement ?? document.body;
1595
+ let top = rootProvider();
1596
+ while (top.parentElement && top.parentElement !== document.body) {
1597
+ top = top.parentElement;
1598
+ }
1599
+ const parent = top.parentElement ?? document.body;
1482
1600
  for (const child2 of Array.from(parent.children)) {
1483
- if (child2 === root2) continue;
1601
+ if (child2 === top) continue;
1484
1602
  if (child2.hasAttribute("inert")) continue;
1603
+ if (child2.classList.contains("bs-backdrop") || child2.classList.contains("bs-screen") || child2.classList.contains("bs-root")) {
1604
+ continue;
1605
+ }
1485
1606
  child2.setAttribute("inert", "");
1486
1607
  tracked.push(child2);
1487
1608
  }
@@ -1497,7 +1618,6 @@ function createInertSiblings(rootProvider) {
1497
1618
  var LifecycleController = class {
1498
1619
  constructor(deps, opts) {
1499
1620
  __publicField(this, "element");
1500
- __publicField(this, "closeFn");
1501
1621
  __publicField(this, "focusTrapEnabled");
1502
1622
  __publicField(this, "initialFocus");
1503
1623
  __publicField(this, "closeOnEscape");
@@ -1511,7 +1631,6 @@ var LifecycleController = class {
1511
1631
  __publicField(this, "installed", false);
1512
1632
  __publicField(this, "destroyed", false);
1513
1633
  this.element = deps.element;
1514
- this.closeFn = deps.close;
1515
1634
  this.focusTrapEnabled = opts.focusTrap ?? false;
1516
1635
  this.initialFocus = opts.initialFocus;
1517
1636
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1521,6 +1640,9 @@ var LifecycleController = class {
1521
1640
  this.returnFocus = opts.returnFocus;
1522
1641
  this.inertSiblings = createInertSiblings(() => this.element);
1523
1642
  }
1643
+ get isInstalled() {
1644
+ return this.installed;
1645
+ }
1524
1646
  install() {
1525
1647
  if (this.destroyed) return;
1526
1648
  this.installed = true;
@@ -1529,8 +1651,7 @@ var LifecycleController = class {
1529
1651
  }
1530
1652
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1531
1653
  this.releaseFocusTrap = installFocusTrap(this.element, {
1532
- initialFocus: this.initialFocus,
1533
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1654
+ initialFocus: this.initialFocus
1534
1655
  });
1535
1656
  }
1536
1657
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1550,7 +1671,7 @@ var LifecycleController = class {
1550
1671
  this.releaseScrollLock = null;
1551
1672
  this.inertSiblings.remove();
1552
1673
  if (this.returnFocus) {
1553
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1674
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1554
1675
  target?.focus?.();
1555
1676
  }
1556
1677
  }
@@ -1684,10 +1805,11 @@ var installGestures = (handle, mode, callbacks) => {
1684
1805
  };
1685
1806
 
1686
1807
  // src/core/primitives/rubber-band.ts
1687
- var RUBBER_BAND_FACTOR = 0.55;
1688
1808
  function rubberBand(overshoot, maxAxisSize) {
1689
1809
  const cap = Math.min(maxAxisSize * 0.15, 80);
1690
- return Math.sign(overshoot) * Math.pow(Math.abs(overshoot), RUBBER_BAND_FACTOR) * Math.min(6, cap / Math.max(Math.abs(overshoot), 1));
1810
+ if (cap <= 0) return 0;
1811
+ const abs = Math.abs(overshoot);
1812
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1691
1813
  }
1692
1814
 
1693
1815
  // src/core/features/soft-keyboard.ts
@@ -1716,6 +1838,7 @@ var GestureController = class {
1716
1838
  __publicField(this, "dragStartSize", 0);
1717
1839
  __publicField(this, "currentPointerKind", "touch");
1718
1840
  __publicField(this, "keyboardDismissed", false);
1841
+ __publicField(this, "dragSuppressed", false);
1719
1842
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1720
1843
  this.deps = deps;
1721
1844
  }
@@ -1726,6 +1849,11 @@ var GestureController = class {
1726
1849
  if (this.detach) return this.detach;
1727
1850
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1728
1851
  onStart: (_coord, kind) => {
1852
+ if (this.deps.getDisableDrag?.()) {
1853
+ this.dragSuppressed = true;
1854
+ return;
1855
+ }
1856
+ this.dragSuppressed = false;
1729
1857
  this.deps.cancelAnimation();
1730
1858
  this.isDragging_ = true;
1731
1859
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1736,6 +1864,7 @@ var GestureController = class {
1736
1864
  this.deps.emit("dragstart", { size: this.dragStartSize });
1737
1865
  },
1738
1866
  onMove: (delta) => {
1867
+ if (this.dragSuppressed) return;
1739
1868
  const ctx = this.deps.getDragContext();
1740
1869
  const { min, max } = ctx.range;
1741
1870
  const maxAxis = ctx.maxAxisSize;
@@ -1759,6 +1888,10 @@ var GestureController = class {
1759
1888
  }
1760
1889
  },
1761
1890
  onEnd: (delta, velocity, kind) => {
1891
+ if (this.dragSuppressed) {
1892
+ this.dragSuppressed = false;
1893
+ return;
1894
+ }
1762
1895
  this.isDragging_ = false;
1763
1896
  this.keyboardDismissed = false;
1764
1897
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1769,9 +1902,17 @@ var GestureController = class {
1769
1902
  this.deps.settleAfterDrag(delta, velocity, kind);
1770
1903
  },
1771
1904
  onCancel: () => {
1905
+ if (this.dragSuppressed) {
1906
+ this.dragSuppressed = false;
1907
+ return;
1908
+ }
1772
1909
  this.isDragging_ = false;
1773
1910
  this.keyboardDismissed = false;
1774
1911
  this.deps.getRoot()?.removeAttribute("data-dragging");
1912
+ this.deps.emit("dragend", {
1913
+ size: this.deps.getDragContext().size,
1914
+ velocity: 0
1915
+ });
1775
1916
  const restoreTo = this.dragStartSize;
1776
1917
  void this.deps.animateTo(restoreTo, 0);
1777
1918
  }
@@ -1845,6 +1986,287 @@ function createScrollCache(deps) {
1845
1986
  };
1846
1987
  }
1847
1988
 
1989
+ // src/core/primitives/anchor-animations.ts
1990
+ var DEFAULT_DURATION3 = 200;
1991
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1992
+ var PRESET_FRAMES = {
1993
+ fade: {
1994
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1995
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1996
+ },
1997
+ scale: {
1998
+ enter: [
1999
+ { opacity: 0, transform: "scale(0.85)" },
2000
+ { opacity: 1, transform: "scale(1)" }
2001
+ ],
2002
+ exit: [
2003
+ { opacity: 1, transform: "scale(1)" },
2004
+ { opacity: 0, transform: "scale(0.85)" }
2005
+ ]
2006
+ },
2007
+ slide: {
2008
+ enter: [
2009
+ { opacity: 0, transform: "translateY(10px)" },
2010
+ { opacity: 1, transform: "translateY(0)" }
2011
+ ],
2012
+ exit: [
2013
+ { opacity: 1, transform: "translateY(0)" },
2014
+ { opacity: 0, transform: "translateY(10px)" }
2015
+ ]
2016
+ },
2017
+ pop: {
2018
+ enter: [
2019
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
2020
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
2021
+ { opacity: 1, transform: "scale(1)", offset: 1 }
2022
+ ],
2023
+ exit: [
2024
+ { opacity: 1, transform: "scale(1)" },
2025
+ { opacity: 0, transform: "scale(0.6)" }
2026
+ ]
2027
+ }
2028
+ };
2029
+ var INSTANT = {
2030
+ finished: Promise.resolve(),
2031
+ cancel: () => {
2032
+ }
2033
+ };
2034
+ function resolveTransitionFrames(spec, phase) {
2035
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
2036
+ if (spec === "none") return fallback;
2037
+ if (spec === void 0 || typeof spec === "string") {
2038
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
2039
+ return {
2040
+ frames: preset2[phase],
2041
+ duration: DEFAULT_DURATION3,
2042
+ easing: DEFAULT_EASING
2043
+ };
2044
+ }
2045
+ const custom = phase === "enter" ? spec.enter : spec.exit;
2046
+ if (custom) {
2047
+ return {
2048
+ frames: custom,
2049
+ duration: spec.duration ?? DEFAULT_DURATION3,
2050
+ easing: spec.easing ?? DEFAULT_EASING
2051
+ };
2052
+ }
2053
+ if (spec.preset === "none") return fallback;
2054
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
2055
+ return {
2056
+ frames: preset[phase],
2057
+ duration: spec.duration ?? DEFAULT_DURATION3,
2058
+ easing: spec.easing ?? DEFAULT_EASING
2059
+ };
2060
+ }
2061
+ function runAnchorTransition(el, spec, phase) {
2062
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
2063
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
2064
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
2065
+ if (!frames || duration <= 0) return INSTANT;
2066
+ if (typeof el.animate !== "function") return INSTANT;
2067
+ const animation = el.animate(frames, {
2068
+ duration,
2069
+ easing,
2070
+ fill: "both"
2071
+ });
2072
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
2073
+ return {
2074
+ finished,
2075
+ cancel: () => {
2076
+ try {
2077
+ animation.cancel();
2078
+ } catch {
2079
+ return;
2080
+ }
2081
+ }
2082
+ };
2083
+ }
2084
+
2085
+ // src/core/features/sheet-anchors.ts
2086
+ function attachAnchor(deps, opts) {
2087
+ const doc = deps.host.ownerDocument;
2088
+ const wrapper = doc.createElement("div");
2089
+ wrapper.className = "bs-anchor";
2090
+ const interactive = opts.interactive ?? true;
2091
+ const position = opts.position ?? "sheet-top-right";
2092
+ const isDock = position === "dock-bottom" || position === "dock-top";
2093
+ const ws = wrapper.style;
2094
+ ws.position = "fixed";
2095
+ if (isDock) {
2096
+ ws.left = "0";
2097
+ ws.right = "0";
2098
+ if (position === "dock-bottom") {
2099
+ ws.bottom = "0";
2100
+ } else {
2101
+ ws.top = "0";
2102
+ }
2103
+ } else {
2104
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2105
+ }
2106
+ if (opts.fadeRange) {
2107
+ const [r0, r1] = opts.fadeRange;
2108
+ const span = Math.max(r1 - r0, 1e-4);
2109
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2110
+ }
2111
+ wrapper.appendChild(opts.element);
2112
+ deps.host.appendChild(wrapper);
2113
+ let visible = null;
2114
+ let inFlight = null;
2115
+ const shouldShow = (state2) => {
2116
+ if (typeof opts.showOn === "function") return opts.showOn(state2);
2117
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state2.activeId);
2118
+ return isDock || state2.size > 0;
2119
+ };
2120
+ const applyVisibility = (next, animate) => {
2121
+ if (next === visible) return;
2122
+ visible = next;
2123
+ inFlight?.cancel();
2124
+ inFlight = null;
2125
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2126
+ if (next) {
2127
+ wrapper.style.visibility = "";
2128
+ if (animate) {
2129
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2130
+ }
2131
+ return;
2132
+ }
2133
+ if (!animate) {
2134
+ wrapper.style.visibility = "hidden";
2135
+ return;
2136
+ }
2137
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2138
+ inFlight = handle;
2139
+ void handle.finished.then(() => {
2140
+ if (inFlight === handle && visible === false) {
2141
+ wrapper.style.visibility = "hidden";
2142
+ }
2143
+ });
2144
+ };
2145
+ const evaluate = (animate) => {
2146
+ if (deps.isDestroyed()) return;
2147
+ applyVisibility(shouldShow(deps.getState()), animate);
2148
+ };
2149
+ const offs = [
2150
+ deps.on("snap", () => evaluate(true)),
2151
+ deps.on("open", () => evaluate(true)),
2152
+ deps.on("close", () => evaluate(true))
2153
+ ];
2154
+ evaluate(false);
2155
+ return {
2156
+ wrapper,
2157
+ detach: () => {
2158
+ offs.forEach((off) => off());
2159
+ inFlight?.cancel();
2160
+ inFlight = null;
2161
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2162
+ },
2163
+ syncZ: (z) => {
2164
+ wrapper.style.zIndex = String(z);
2165
+ }
2166
+ };
2167
+ }
2168
+
2169
+ // src/core/features/scrim-stages.ts
2170
+ var matchesId = (def, activeId) => {
2171
+ if (def.for === void 0) return false;
2172
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2173
+ };
2174
+ var matchesRange = (def, progress) => {
2175
+ if (!def.forRange) return false;
2176
+ const [from, to] = def.forRange;
2177
+ return progress >= from && progress <= to;
2178
+ };
2179
+ function installScrimStages(deps, opts) {
2180
+ const doc = deps.host.ownerDocument;
2181
+ const entries = opts.stages.map((def) => {
2182
+ const wrapper = doc.createElement("div");
2183
+ wrapper.className = "bs-scrim-stage";
2184
+ const ws = wrapper.style;
2185
+ ws.position = "absolute";
2186
+ applyOverlayPosition(
2187
+ ws,
2188
+ deps.mode,
2189
+ def.position ?? opts.position ?? "center",
2190
+ def.inset ?? opts.inset ?? "16px"
2191
+ );
2192
+ ws.visibility = "hidden";
2193
+ ws.pointerEvents = "none";
2194
+ wrapper.appendChild(def.element);
2195
+ deps.host.appendChild(wrapper);
2196
+ return { def, wrapper, inFlight: null };
2197
+ });
2198
+ let active = null;
2199
+ const resolveActive = () => {
2200
+ const state2 = deps.getState();
2201
+ if (state2.size === 0) {
2202
+ return entries.find((e) => matchesId(e.def, state2.activeId)) ?? null;
2203
+ }
2204
+ return entries.find((e) => matchesId(e.def, state2.activeId)) ?? entries.find((e) => matchesRange(e.def, state2.progress)) ?? null;
2205
+ };
2206
+ const hide = (entry, animate) => {
2207
+ entry.inFlight?.cancel();
2208
+ entry.wrapper.style.pointerEvents = "none";
2209
+ if (!animate) {
2210
+ entry.inFlight = null;
2211
+ entry.wrapper.style.visibility = "hidden";
2212
+ return;
2213
+ }
2214
+ const handle = runAnchorTransition(
2215
+ entry.def.element,
2216
+ entry.def.animation ?? opts.animation,
2217
+ "exit"
2218
+ );
2219
+ entry.inFlight = handle;
2220
+ void handle.finished.then(() => {
2221
+ if (entry.inFlight === handle && active !== entry) {
2222
+ entry.wrapper.style.visibility = "hidden";
2223
+ }
2224
+ });
2225
+ };
2226
+ const show = (entry, animate) => {
2227
+ entry.inFlight?.cancel();
2228
+ entry.inFlight = null;
2229
+ entry.wrapper.style.visibility = "";
2230
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2231
+ if (animate) {
2232
+ entry.inFlight = runAnchorTransition(
2233
+ entry.def.element,
2234
+ entry.def.animation ?? opts.animation,
2235
+ "enter"
2236
+ );
2237
+ }
2238
+ };
2239
+ const update = (animate) => {
2240
+ if (deps.isDestroyed()) return;
2241
+ const next = resolveActive();
2242
+ if (next === active) return;
2243
+ const prev = active;
2244
+ active = next;
2245
+ if (prev) hide(prev, animate);
2246
+ if (next) show(next, animate);
2247
+ };
2248
+ const offs = [
2249
+ deps.on("snap", () => update(true)),
2250
+ deps.on("open", () => update(true)),
2251
+ deps.on("close", () => update(true))
2252
+ ];
2253
+ if (entries.some((e) => e.def.forRange)) {
2254
+ offs.push(deps.on("progress", () => update(true)));
2255
+ }
2256
+ update(false);
2257
+ return () => {
2258
+ offs.forEach((off) => off());
2259
+ for (const entry of entries) {
2260
+ entry.inFlight?.cancel();
2261
+ entry.inFlight = null;
2262
+ if (entry.wrapper.parentNode) {
2263
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2264
+ }
2265
+ }
2266
+ active = null;
2267
+ };
2268
+ }
2269
+
1848
2270
  // src/core/primitives/engine-options.ts
1849
2271
  var DEFAULT_FLICK_VELOCITY = 0.65;
1850
2272
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1863,6 +2285,12 @@ function resolveEngineOptions(opts, extras) {
1863
2285
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
1864
2286
  rubberBandEnabled: opts.rubberBand ?? true,
1865
2287
  closeOnBack: opts.closeOnBack ?? false,
2288
+ persistent: opts.persistent ?? false,
2289
+ disableClose: opts.disableClose ?? false,
2290
+ disableDrag: opts.disableDrag ?? false,
2291
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2292
+ radius: opts.radius,
2293
+ maxHeight: opts.maxHeight,
1866
2294
  initialAllowed,
1867
2295
  initialId,
1868
2296
  scrim: {
@@ -1889,7 +2317,8 @@ function resolveEngineOptions(opts, extras) {
1889
2317
  closeOnEscape: opts.closeOnEscape,
1890
2318
  lockBodyScroll: opts.lockBodyScroll,
1891
2319
  inertSiblings: opts.inertSiblings,
1892
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2320
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2321
+ returnFocus: opts.returnFocusTo
1893
2322
  }
1894
2323
  };
1895
2324
  }
@@ -1934,6 +2363,11 @@ var BottomSheetEngine = class {
1934
2363
  __publicField(this, "lifecycle");
1935
2364
  __publicField(this, "closeOnBack");
1936
2365
  __publicField(this, "routedTo");
2366
+ __publicField(this, "persistent");
2367
+ __publicField(this, "disableCloseFlag");
2368
+ __publicField(this, "disableDragFlag");
2369
+ __publicField(this, "closeOnRouteChange");
2370
+ __publicField(this, "maxHeightCap");
1937
2371
  __publicField(this, "snapPointsRaw");
1938
2372
  __publicField(this, "snaps");
1939
2373
  __publicField(this, "activeId");
@@ -1946,6 +2380,7 @@ var BottomSheetEngine = class {
1946
2380
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
1947
2381
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
1948
2382
  __publicField(this, "isTopSheet", true);
2383
+ __publicField(this, "opening", false);
1949
2384
  __publicField(this, "progressPayload", {
1950
2385
  value: 0,
1951
2386
  size: 0
@@ -1961,6 +2396,12 @@ var BottomSheetEngine = class {
1961
2396
  __publicField(this, "linkedSheets", []);
1962
2397
  __publicField(this, "scrollCache");
1963
2398
  __publicField(this, "teardowns", new TeardownStack());
2399
+ __publicField(this, "anchors", []);
2400
+ __publicField(this, "anchorHost", null);
2401
+ __publicField(this, "stackZ", 100);
2402
+ __publicField(this, "detachScrimStages", null);
2403
+ __publicField(this, "stackEffectEnabled", false);
2404
+ __publicField(this, "stackEffectPrimed", false);
1964
2405
  __publicField(this, "transformTemplate");
1965
2406
  const resolved = resolveEngineOptions(opts, {
1966
2407
  shouldApplyInertSiblings: () => {
@@ -1994,6 +2435,10 @@ var BottomSheetEngine = class {
1994
2435
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1995
2436
  snapTo: (id) => {
1996
2437
  void this.snapTo(id);
2438
+ },
2439
+ close: () => {
2440
+ if (!this.canDismiss()) return;
2441
+ void this.close("backdrop");
1997
2442
  }
1998
2443
  },
1999
2444
  resolved.scrim
@@ -2017,6 +2462,11 @@ var BottomSheetEngine = class {
2017
2462
  );
2018
2463
  this.closeOnBack = resolved.closeOnBack;
2019
2464
  this.routedTo = opts.routedTo;
2465
+ this.persistent = resolved.persistent;
2466
+ this.disableCloseFlag = resolved.disableClose;
2467
+ this.disableDragFlag = resolved.disableDrag;
2468
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2469
+ this.stackEffectEnabled = opts.stackEffect ?? false;
2020
2470
  this.snapPointsRaw = opts.snapPoints;
2021
2471
  this.persistKey = opts.persistKey;
2022
2472
  if (opts.linkedSheets) {
@@ -2029,7 +2479,7 @@ var BottomSheetEngine = class {
2029
2479
  this.snapPointsRaw,
2030
2480
  resolved.initialAllowed,
2031
2481
  this.mode,
2032
- () => this.handle.offsetHeight,
2482
+ () => this.measureFitSize(),
2033
2483
  (maxAxisSize) => {
2034
2484
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2035
2485
  }
@@ -2063,6 +2513,19 @@ var BottomSheetEngine = class {
2063
2513
  io.observe(this.element);
2064
2514
  this.teardowns.add(() => io.disconnect());
2065
2515
  }
2516
+ this.installFitObserver();
2517
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2518
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2519
+ this.applyAutoAriaLabelledBy();
2520
+ if (this.closeOnRouteChange) {
2521
+ this.teardowns.add(
2522
+ installRouteChange({
2523
+ isDestroyed: () => this.destroyed,
2524
+ getSize: () => this.size,
2525
+ close: () => this.close()
2526
+ })
2527
+ );
2528
+ }
2066
2529
  if (this.persistKey) {
2067
2530
  this.teardowns.add(installPersist(this, this.persistKey));
2068
2531
  }
@@ -2087,7 +2550,7 @@ var BottomSheetEngine = class {
2087
2550
  isTopSheet: () => this.isTopSheet,
2088
2551
  getSize: () => this.size,
2089
2552
  isDestroyed: () => this.destroyed,
2090
- close: () => this.close(),
2553
+ close: () => this.close("back"),
2091
2554
  on: (event, fn) => this.on(event, fn),
2092
2555
  sheetId: this.id
2093
2556
  })
@@ -2181,6 +2644,10 @@ var BottomSheetEngine = class {
2181
2644
  const previousSize = this.size;
2182
2645
  this.scrollCache.cache(previousId, previousSize, target.size);
2183
2646
  this.activeId = id;
2647
+ if (wasClosed && target.size > 0) {
2648
+ this.opening = true;
2649
+ sheetStack.promote(this.id);
2650
+ }
2184
2651
  if (this.animation.viewTransitionsAvailable) {
2185
2652
  this.currentViewTransition?.skipTransition?.();
2186
2653
  const vt = document.startViewTransition(() => {
@@ -2195,19 +2662,26 @@ var BottomSheetEngine = class {
2195
2662
  } else {
2196
2663
  await this.animation.animateTo(target.size, velocityPxPerMs);
2197
2664
  }
2665
+ this.opening = false;
2198
2666
  externalSignal?.removeEventListener("abort", onExternalAbort);
2199
2667
  if (signal.aborted) return;
2200
2668
  this.scrollCache.restore(id, previousSize, target.size);
2201
2669
  this.updateAriaSlider();
2202
- this.emit("snap", { id, size: target.size });
2670
+ this.emit("snap", {
2671
+ id,
2672
+ size: target.size,
2673
+ progress: this.computeProgress(target.size)
2674
+ });
2203
2675
  if (wasClosed && target.size > 0) {
2204
2676
  this.emit("open", { id });
2205
2677
  this.handleOpen();
2206
2678
  notifyLinkedSheets(this.linkedSheets, this);
2679
+ this.emit("opened", { id });
2207
2680
  }
2208
2681
  if (target.size === 0) {
2209
2682
  this.emit("close", void 0);
2210
2683
  this.handleClose();
2684
+ this.emit("closed", void 0);
2211
2685
  }
2212
2686
  }
2213
2687
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2232,18 +2706,76 @@ var BottomSheetEngine = class {
2232
2706
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2233
2707
  return this.snapTo(target);
2234
2708
  }
2235
- close() {
2709
+ close(reason = "programmatic") {
2236
2710
  if (this.destroyed) return Promise.resolve();
2711
+ if (this.disableCloseFlag) return Promise.resolve();
2712
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2237
2713
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2238
2714
  return this.snapTo(closedId ?? this.activeId);
2239
2715
  }
2240
- setAllowed(ids, snap) {
2716
+ canDismiss() {
2717
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2718
+ }
2719
+ isTop() {
2720
+ return this.isTopSheet;
2721
+ }
2722
+ depth() {
2723
+ return sheetStack.depthOf(this.id);
2724
+ }
2725
+ expand() {
2726
+ if (this.destroyed) return Promise.resolve();
2727
+ const ids = this.allowedIdsBySize();
2728
+ const target = ids[ids.length - 1];
2729
+ if (!target) return Promise.resolve();
2730
+ return this.snapTo(target);
2731
+ }
2732
+ collapse() {
2733
+ if (this.destroyed) return Promise.resolve();
2734
+ const ids = this.allowedIdsBySize();
2735
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2736
+ const target = nonZero ?? ids[0];
2737
+ if (!target) return Promise.resolve();
2738
+ return this.snapTo(target);
2739
+ }
2740
+ setRadius(r) {
2241
2741
  if (this.destroyed) return;
2242
- const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
2243
- if (!willSnap) {
2244
- this.animation.cancel();
2245
- this.newCycle();
2742
+ this.element.style.setProperty(
2743
+ "--bs-radius",
2744
+ typeof r === "number" ? `${r}px` : r
2745
+ );
2746
+ }
2747
+ setMaxHeight(h) {
2748
+ if (this.destroyed) return;
2749
+ const value = typeof h === "number" ? `${h}px` : h;
2750
+ const axis = layoutAxis(this.mode);
2751
+ this.element.style.setProperty(
2752
+ axis === "height" ? "maxHeight" : "maxWidth",
2753
+ value
2754
+ );
2755
+ if (typeof h === "number") {
2756
+ this.maxHeightCap = h;
2757
+ this.clampToMaxHeight();
2758
+ } else if (typeof window !== "undefined") {
2759
+ const probe2 = document.createElement("div");
2760
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2761
+ document.body.appendChild(probe2);
2762
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2763
+ probe2.remove();
2764
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2765
+ this.clampToMaxHeight();
2246
2766
  }
2767
+ }
2768
+ clampToMaxHeight() {
2769
+ if (this.maxHeightCap === void 0) return;
2770
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2771
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2772
+ }
2773
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2774
+ this.applySize(this.maxHeightCap);
2775
+ }
2776
+ }
2777
+ setAllowed(ids, snap) {
2778
+ if (this.destroyed) return;
2247
2779
  this.snaps.setAllowedIds(ids);
2248
2780
  this.updateAriaSlider();
2249
2781
  if (snap && ids.includes(snap)) {
@@ -2283,6 +2815,79 @@ var BottomSheetEngine = class {
2283
2815
  setScrimOverlay(opts) {
2284
2816
  return this.scrim.setScrimOverlay(opts);
2285
2817
  }
2818
+ addAnchor(opts) {
2819
+ if (this.destroyed || typeof document === "undefined") return () => {
2820
+ };
2821
+ if (!this.anchorHost) {
2822
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2823
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2824
+ this.anchorHost.style.setProperty(
2825
+ "--bs-progress",
2826
+ String(this.computeProgress(this.size))
2827
+ );
2828
+ }
2829
+ const handle = attachAnchor(
2830
+ {
2831
+ mode: this.mode,
2832
+ host: this.anchorHost,
2833
+ getState: () => ({
2834
+ activeId: this.activeId,
2835
+ size: this.size,
2836
+ progress: this.computeProgress(this.size)
2837
+ }),
2838
+ on: (event, fn) => this.on(event, fn),
2839
+ isDestroyed: () => this.destroyed
2840
+ },
2841
+ opts
2842
+ );
2843
+ handle.syncZ(this.stackZ + 1);
2844
+ this.anchors.push(handle);
2845
+ const detach = () => {
2846
+ const idx = this.anchors.indexOf(handle);
2847
+ if (idx === -1) return;
2848
+ this.anchors.splice(idx, 1);
2849
+ handle.detach();
2850
+ };
2851
+ this.teardowns.add(detach);
2852
+ return detach;
2853
+ }
2854
+ setScrimStages(opts) {
2855
+ if (this.destroyed || typeof document === "undefined") return () => {
2856
+ };
2857
+ this.detachScrimStages?.();
2858
+ this.detachScrimStages = null;
2859
+ if (!opts) return () => {
2860
+ };
2861
+ const host = this.screenComponent?.parentElement;
2862
+ if (!host) {
2863
+ console.warn(
2864
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2865
+ );
2866
+ return () => {
2867
+ };
2868
+ }
2869
+ const detachInstalled = installScrimStages(
2870
+ {
2871
+ mode: this.mode,
2872
+ host,
2873
+ getState: () => ({
2874
+ activeId: this.activeId,
2875
+ size: this.size,
2876
+ progress: this.computeProgress(this.size)
2877
+ }),
2878
+ on: (event, fn) => this.on(event, fn),
2879
+ isDestroyed: () => this.destroyed
2880
+ },
2881
+ opts
2882
+ );
2883
+ const detach = () => {
2884
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2885
+ detachInstalled();
2886
+ };
2887
+ this.detachScrimStages = detach;
2888
+ this.teardowns.add(() => this.detachScrimStages?.());
2889
+ return detach;
2890
+ }
2286
2891
  getScrimState() {
2287
2892
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2288
2893
  }
@@ -2302,8 +2907,53 @@ var BottomSheetEngine = class {
2302
2907
  }
2303
2908
  this.updateAriaSlider();
2304
2909
  }
2910
+ recompute() {
2911
+ if (this.destroyed) return;
2912
+ this.snaps.recompute();
2913
+ this.scrim.invalidateOpacityCache();
2914
+ if (this.gesture?.isDragging) return;
2915
+ const current = this.snaps.findById(this.activeId);
2916
+ if (current) {
2917
+ this.size = current.size;
2918
+ this.applySize(this.size);
2919
+ this.updateAriaSlider();
2920
+ }
2921
+ }
2922
+ measureFitSize() {
2923
+ const vertical = layoutAxis(this.mode) === "height";
2924
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2925
+ const content = this.scrollContainer;
2926
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2927
+ if (typeof window === "undefined") return natural;
2928
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2929
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2930
+ }
2931
+ installFitObserver() {
2932
+ if (typeof ResizeObserver === "undefined") return;
2933
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2934
+ return;
2935
+ const targets = [this.handle];
2936
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2937
+ targets.push(this.scrollContainer);
2938
+ }
2939
+ let raf = 0;
2940
+ const ro = new ResizeObserver(() => {
2941
+ if (raf) return;
2942
+ raf = requestAnimationFrame(() => {
2943
+ raf = 0;
2944
+ if (this.destroyed || this.gesture?.isDragging) return;
2945
+ this.recompute();
2946
+ });
2947
+ });
2948
+ for (const t of targets) ro.observe(t);
2949
+ this.teardowns.add(() => {
2950
+ ro.disconnect();
2951
+ if (raf) cancelAnimationFrame(raf);
2952
+ });
2953
+ }
2305
2954
  destroy() {
2306
2955
  this.destroyed = true;
2956
+ this.opening = false;
2307
2957
  this.currentAbort.abort();
2308
2958
  this.currentViewTransition?.skipTransition?.();
2309
2959
  this.currentViewTransition = null;
@@ -2353,6 +3003,36 @@ var BottomSheetEngine = class {
2353
3003
  frozen = true;
2354
3004
  return cancelled;
2355
3005
  }
3006
+ emitBeforeClose(reason) {
3007
+ let cancelled = false;
3008
+ let frozen = false;
3009
+ this.emit("before-close", {
3010
+ reason,
3011
+ cancel: () => {
3012
+ if (frozen) {
3013
+ console.warn(
3014
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3015
+ );
3016
+ return;
3017
+ }
3018
+ cancelled = true;
3019
+ }
3020
+ });
3021
+ frozen = true;
3022
+ return cancelled;
3023
+ }
3024
+ applyAutoAriaLabelledBy() {
3025
+ if (typeof document === "undefined") return;
3026
+ if (!this.lifecycle.focusTrapEnabled) return;
3027
+ if (this.element.getAttribute("aria-labelledby")) return;
3028
+ const scope = this.handle ?? this.element;
3029
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3030
+ if (!titled) return;
3031
+ if (!titled.id) {
3032
+ titled.id = nextInstanceId("bs-title");
3033
+ }
3034
+ this.element.setAttribute("aria-labelledby", titled.id);
3035
+ }
2356
3036
  recomputeSnaps() {
2357
3037
  const seen = /* @__PURE__ */ new Set();
2358
3038
  for (const p of this.snapPointsRaw) {
@@ -2369,24 +3049,42 @@ var BottomSheetEngine = class {
2369
3049
  getAllowedRange() {
2370
3050
  return this.snaps.getAllowedRange();
2371
3051
  }
3052
+ allowedIdsBySize() {
3053
+ return this.snaps.getAllowedIds().slice().sort(
3054
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
3055
+ );
3056
+ }
2372
3057
  updateAriaSlider() {
2373
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
3058
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2374
3059
  }
2375
3060
  registerInStack() {
2376
3061
  this.teardowns.add(sheetStack.push({
2377
3062
  id: this.id,
2378
3063
  setZIndex: (z) => {
3064
+ this.stackZ = z;
2379
3065
  this.element.style.zIndex = String(z);
2380
3066
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3067
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2381
3068
  },
2382
3069
  setIsTop: (isTop) => {
2383
3070
  this.isTopSheet = isTop;
2384
- if (this.backdrop) {
2385
- this.backdrop.style.display = isTop ? "" : "none";
2386
- }
2387
- }
3071
+ },
3072
+ isOpen: () => this.size > 0 || this.opening,
3073
+ setDepth: (depth) => this.applyStackDepth(depth)
2388
3074
  }));
2389
3075
  }
3076
+ applyStackDepth(depth) {
3077
+ if (!this.stackEffectEnabled) return;
3078
+ this.element.setAttribute("data-stack-depth", String(depth));
3079
+ if (!this.stackEffectPrimed) {
3080
+ this.stackEffectPrimed = true;
3081
+ const isVertical = this.mode === "bottom" || this.mode === "top";
3082
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
3083
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
3084
+ }
3085
+ const scale = Math.max(1 - depth * 0.04, 0.86);
3086
+ this.element.style.scale = scale === 1 ? "" : String(scale);
3087
+ }
2390
3088
  attach() {
2391
3089
  this.gesture = new GestureController({
2392
3090
  handle: this.handle,
@@ -2403,6 +3101,7 @@ var BottomSheetEngine = class {
2403
3101
  buf.rubberBandEnabled = this.rubberBandEnabled;
2404
3102
  return buf;
2405
3103
  },
3104
+ getDisableDrag: () => this.disableDragFlag,
2406
3105
  cancelAnimation: () => this.animation.cancel(),
2407
3106
  applySize: (size) => this.applySize(size),
2408
3107
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2446,7 +3145,7 @@ var BottomSheetEngine = class {
2446
3145
  container: this.scrollContainer,
2447
3146
  isDragging: () => this.gesture?.isDragging ?? false,
2448
3147
  isAnimating: () => this.animation.isAnimating,
2449
- getAllowedIds: () => this.snaps.getAllowedIds(),
3148
+ getAllowedIds: () => this.allowedIdsBySize(),
2450
3149
  getActiveId: () => this.activeId,
2451
3150
  snapTo: (id) => {
2452
3151
  void this.snapTo(id);
@@ -2475,13 +3174,16 @@ var BottomSheetEngine = class {
2475
3174
  },
2476
3175
  newCycle: () => {
2477
3176
  this.newCycle();
2478
- }
3177
+ },
3178
+ isAnimating: () => this.animation.isAnimating,
3179
+ resyncAfterCancel: () => this.resyncAfterResize()
2479
3180
  }));
2480
3181
  if (this.lifecycle.closeOnEscape) {
2481
3182
  const onKey = (e) => {
2482
- if (this.destroyed) return;
3183
+ if (this.destroyed || e.defaultPrevented) return;
3184
+ if (!this.canDismiss()) return;
2483
3185
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2484
- void this.close();
3186
+ void this.close("escape");
2485
3187
  }
2486
3188
  };
2487
3189
  document.addEventListener("keydown", onKey);
@@ -2511,7 +3213,7 @@ var BottomSheetEngine = class {
2511
3213
  handle: this.handle,
2512
3214
  mode: this.mode,
2513
3215
  isDestroyed: () => this.destroyed,
2514
- getAllowedIds: () => this.snaps.getAllowedIds(),
3216
+ getAllowedIds: () => this.allowedIdsBySize(),
2515
3217
  getActiveId: () => this.activeId,
2516
3218
  snapTo: (id) => {
2517
3219
  void this.snapTo(id);
@@ -2532,6 +3234,7 @@ var BottomSheetEngine = class {
2532
3234
  });
2533
3235
  if (!target) return;
2534
3236
  const previousId = this.activeId;
3237
+ if (target.id === previousId && target.size === this.size) return;
2535
3238
  if (this.emitBeforeSnap(target, previousId)) {
2536
3239
  const restore = this.snaps.findById(previousId);
2537
3240
  if (restore) {
@@ -2540,21 +3243,34 @@ var BottomSheetEngine = class {
2540
3243
  return;
2541
3244
  }
2542
3245
  const previousSize = this.size;
3246
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2543
3247
  this.scrollCache.cache(previousId, previousSize, target.size);
2544
3248
  this.activeId = target.id;
3249
+ if (previousSnapSize === 0 && target.size > 0) {
3250
+ this.opening = true;
3251
+ sheetStack.promote(this.id);
3252
+ }
2545
3253
  void this.animation.animateTo(target.size, velocity).then(() => {
3254
+ this.opening = false;
2546
3255
  if (signal.aborted) return;
2547
3256
  this.scrollCache.restore(target.id, previousSize, target.size);
2548
3257
  this.updateAriaSlider();
2549
- this.emit("snap", { id: target.id, size: target.size });
3258
+ this.emit("snap", {
3259
+ id: target.id,
3260
+ size: target.size,
3261
+ progress: this.computeProgress(target.size)
3262
+ });
2550
3263
  this.haptic();
2551
- if (previousId === "closed" && target.size > 0) {
3264
+ if (previousSnapSize === 0 && target.size > 0) {
2552
3265
  this.emit("open", { id: target.id });
2553
3266
  this.handleOpen();
3267
+ notifyLinkedSheets(this.linkedSheets, this);
3268
+ this.emit("opened", { id: target.id });
2554
3269
  }
2555
3270
  if (target.size === 0) {
2556
3271
  this.emit("close", void 0);
2557
3272
  this.handleClose();
3273
+ this.emit("closed", void 0);
2558
3274
  }
2559
3275
  });
2560
3276
  }
@@ -2576,6 +3292,9 @@ var BottomSheetEngine = class {
2576
3292
  if (this.scrimParent) {
2577
3293
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2578
3294
  }
3295
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3296
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3297
+ }
2579
3298
  }
2580
3299
  const progress = this.computeProgress(size);
2581
3300
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2584,6 +3303,9 @@ var BottomSheetEngine = class {
2584
3303
  );
2585
3304
  if (progressChanged) {
2586
3305
  style.setProperty("--bs-progress", String(progress));
3306
+ if (this.anchorHost) {
3307
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3308
+ }
2587
3309
  }
2588
3310
  this.scrim.applyOpacity(progress, progressChanged);
2589
3311
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2598,6 +3320,7 @@ var BottomSheetEngine = class {
2598
3320
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2599
3321
  }
2600
3322
  handleOpen() {
3323
+ sheetStack.promote(this.id);
2601
3324
  try {
2602
3325
  this.lifecycle.install();
2603
3326
  } catch (err) {
@@ -2611,8 +3334,31 @@ var BottomSheetEngine = class {
2611
3334
  });
2612
3335
  }
2613
3336
  }
3337
+ resyncAfterResize() {
3338
+ if (this.destroyed) return;
3339
+ const target = this.snaps.findById(this.activeId);
3340
+ if (!target) return;
3341
+ this.updateAriaSlider();
3342
+ this.emit("snap", {
3343
+ id: target.id,
3344
+ size: target.size,
3345
+ progress: this.computeProgress(target.size)
3346
+ });
3347
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3348
+ this.emit("open", { id: target.id });
3349
+ this.handleOpen();
3350
+ notifyLinkedSheets(this.linkedSheets, this);
3351
+ this.emit("opened", { id: target.id });
3352
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3353
+ this.emit("close", void 0);
3354
+ this.handleClose();
3355
+ this.emit("closed", void 0);
3356
+ }
3357
+ }
2614
3358
  handleClose() {
3359
+ this.opening = false;
2615
3360
  this.lifecycle.release();
3361
+ sheetStack.update();
2616
3362
  }
2617
3363
  };
2618
3364
 
@@ -2630,17 +3376,18 @@ function createBottomSheet(opts) {
2630
3376
  const attach = (refs) => {
2631
3377
  if (engine) engine.destroy();
2632
3378
  const { onSnap, ...engineOpts } = opts;
2633
- engine = new BottomSheetEngine({ ...engineOpts, ...refs });
3379
+ const created = new BottomSheetEngine({ ...engineOpts, ...refs });
3380
+ engine = created;
2634
3381
  if (onSnap) {
2635
- engine.on("snap", (payload) => onSnap(payload.id));
3382
+ created.on("snap", (payload) => onSnap(payload.id));
2636
3383
  }
2637
3384
  for (const entry of pending) {
2638
- entry.engineUnsub = engine.on(entry.event, entry.fn);
3385
+ entry.engineUnsub = created.on(entry.event, entry.fn);
2639
3386
  }
2640
3387
  pending.length = 0;
2641
3388
  return () => {
2642
- engine?.destroy();
2643
- engine = null;
3389
+ created.destroy();
3390
+ if (engine === created) engine = null;
2644
3391
  };
2645
3392
  };
2646
3393
  return {
@@ -2663,7 +3410,20 @@ function createBottomSheet(opts) {
2663
3410
  snapTo: (id) => engine?.snapTo(id) ?? Promise.resolve(),
2664
3411
  open: (id) => engine?.open(id) ?? Promise.resolve(),
2665
3412
  close: () => engine?.close() ?? Promise.resolve(),
3413
+ expand: () => engine?.expand() ?? Promise.resolve(),
3414
+ collapse: () => engine?.collapse() ?? Promise.resolve(),
3415
+ isTop: () => engine?.isTop() ?? false,
3416
+ depth: () => engine?.depth() ?? 0,
2666
3417
  setAllowed: (ids, snap) => engine?.setAllowed(ids, snap),
3418
+ setSnapPoints: (points, allowed) => engine?.setSnapPoints(points, allowed),
3419
+ setScrim: (scrimOpts) => engine?.setScrim(scrimOpts),
3420
+ setScrimOverlay: (overlayOpts) => engine?.setScrimOverlay(overlayOpts) ?? (() => {
3421
+ }),
3422
+ addAnchor: (anchorOpts) => engine?.addAnchor(anchorOpts) ?? (() => {
3423
+ }),
3424
+ setScrimStages: (stagesOpts) => engine?.setScrimStages(stagesOpts) ?? (() => {
3425
+ }),
3426
+ recompute: () => engine?.recompute(),
2667
3427
  destroy: () => {
2668
3428
  engine?.destroy();
2669
3429
  engine = null;
@@ -2677,7 +3437,7 @@ var root_4 = $2__namespace.from_html(`<div class="bs-button-slot" data-side="rig
2677
3437
  var root = $2__namespace.from_html(`<div class="bs-root"><!> <div class="bs-screen"><!></div> <section class="bs-sheet" role="dialog"><!> <!> <div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet"><!></div> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section></div>`);
2678
3438
  function BottomSheet($$anchor, $$props) {
2679
3439
  $2__namespace.push($$props, true);
2680
- let mode = $2__namespace.prop($$props, "mode", 3, "bottom"), animation = $2__namespace.prop($$props, "animation", 3, "spring"), focusTrap = $2__namespace.prop($$props, "focusTrap", 3, false), closeOnEscape = $2__namespace.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2__namespace.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2__namespace.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2__namespace.prop($$props, "rubberBand", 3, true), backdrop = $2__namespace.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2__namespace.prop($$props, "closeOnBackdrop", 3, true), ariaLabel = $2__namespace.prop($$props, "ariaLabel", 3, "Bottom sheet");
3440
+ let mode = $2__namespace.prop($$props, "mode", 3, "bottom"), animation = $2__namespace.prop($$props, "animation", 3, "spring"), focusTrap = $2__namespace.prop($$props, "focusTrap", 3, false), closeOnEscape = $2__namespace.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2__namespace.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2__namespace.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2__namespace.prop($$props, "rubberBand", 3, true), backdrop = $2__namespace.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2__namespace.prop($$props, "closeOnBackdrop", 3, true), persistent = $2__namespace.prop($$props, "persistent", 3, false), disableClose = $2__namespace.prop($$props, "disableClose", 3, false), disableDrag = $2__namespace.prop($$props, "disableDrag", 3, false), closeOnRouteChange = $2__namespace.prop($$props, "closeOnRouteChange", 3, false), openProp = $2__namespace.prop($$props, "open", 15), snap = $2__namespace.prop($$props, "snap", 15), ariaLabel = $2__namespace.prop($$props, "ariaLabel", 3, "Bottom sheet");
2681
3441
  let sheetEl = $2__namespace.state(void 0);
2682
3442
  let handleEl = $2__namespace.state(void 0);
2683
3443
  let contentEl = $2__namespace.state(void 0);
@@ -2685,7 +3445,7 @@ function BottomSheet($$anchor, $$props) {
2685
3445
  let screenEl = $2__namespace.state(void 0);
2686
3446
  let viewState = $2__namespace.state($2__namespace.proxy({
2687
3447
  size: 0,
2688
- activeId: $$props.initial ?? $$props.snapPoints[0]?.id ?? "default",
3448
+ activeId: svelte.untrack(() => $$props.initial ?? $$props.snapPoints[0]?.id ?? "default"),
2689
3449
  isDragging: false,
2690
3450
  isAnimating: false,
2691
3451
  progress: 0
@@ -2710,7 +3470,14 @@ function BottomSheet($$anchor, $$props) {
2710
3470
  closeOnBack: closeOnBack(),
2711
3471
  lockBodyScroll: lockBodyScroll2(),
2712
3472
  rubberBand: rubberBand2(),
2713
- backdropRange: $$props.backdropRange
3473
+ backdropRange: $$props.backdropRange,
3474
+ persistent: persistent(),
3475
+ disableClose: disableClose(),
3476
+ disableDrag: disableDrag(),
3477
+ closeOnRouteChange: closeOnRouteChange(),
3478
+ radius: $$props.radius,
3479
+ maxHeight: $$props.maxHeight,
3480
+ returnFocusTo: $$props.returnFocusTo
2714
3481
  });
2715
3482
  const sync = () => {
2716
3483
  if (!engine) return;
@@ -2718,14 +3485,32 @@ function BottomSheet($$anchor, $$props) {
2718
3485
  $$props.onchange?.($2__namespace.get(viewState));
2719
3486
  };
2720
3487
  sync();
3488
+ if ($$props.backdropColor !== void 0) engine.setScrimColor($$props.backdropColor);
3489
+ if ($$props.backdropOpacity !== void 0) engine.setBackdropRange([0, $$props.backdropOpacity]);
2721
3490
  engine.on("snap", (payload) => {
2722
3491
  sync();
3492
+ snap(payload.id);
2723
3493
  $$props.onsnap?.(payload.id);
2724
3494
  });
2725
- engine.on("open", (payload) => $$props.onopen?.(payload.id));
2726
- engine.on("close", () => $$props.onclose?.());
2727
- engine.on("dragstart", sync);
2728
- engine.on("dragend", sync);
3495
+ engine.on("open", (payload) => {
3496
+ openProp(true);
3497
+ $$props.onopen?.(payload.id);
3498
+ });
3499
+ engine.on("close", () => {
3500
+ openProp(false);
3501
+ $$props.onclose?.();
3502
+ });
3503
+ engine.on("before-close", (payload) => $$props.onbeforeclose?.(payload));
3504
+ engine.on("opened", (payload) => $$props.onopened?.(payload.id));
3505
+ engine.on("closed", () => $$props.onclosed?.());
3506
+ engine.on("dragstart", () => {
3507
+ sync();
3508
+ $$props.ondragstart?.();
3509
+ });
3510
+ engine.on("dragend", () => {
3511
+ sync();
3512
+ $$props.ondragend?.();
3513
+ });
2729
3514
  });
2730
3515
  svelte.onDestroy(() => {
2731
3516
  engine?.destroy();
@@ -2734,13 +3519,46 @@ function BottomSheet($$anchor, $$props) {
2734
3519
  const snapTo = (id) => engine?.snapTo(id) ?? Promise.resolve();
2735
3520
  const open = (id) => engine?.open(id) ?? Promise.resolve();
2736
3521
  const close = () => engine?.close() ?? Promise.resolve();
2737
- const setAllowed = (ids, snap) => engine?.setAllowed(ids, snap);
3522
+ const expand = () => engine?.expand() ?? Promise.resolve();
3523
+ const collapse = () => engine?.collapse() ?? Promise.resolve();
3524
+ const isTop = () => engine?.isTop() ?? false;
3525
+ const depth = () => engine?.depth() ?? 0;
3526
+ const setAllowed = (ids, snap2) => engine?.setAllowed(ids, snap2);
2738
3527
  const setSnapPoints = (points, nextAllowed) => engine?.setSnapPoints(points, nextAllowed);
2739
3528
  const setScrim = (opts) => engine?.setScrim(opts);
2740
3529
  const setScrimOverlay = (opts) => engine?.setScrimOverlay(opts) ?? (() => {
2741
3530
  });
3531
+ const setRadius = (r) => engine?.setRadius(r);
3532
+ const setMaxHeight = (h) => engine?.setMaxHeight(h);
3533
+ const canDismiss = () => engine?.canDismiss() ?? false;
2742
3534
  const getState = () => engine?.state ?? $2__namespace.get(viewState);
2743
3535
  const getEngine = () => engine;
3536
+ $2__namespace.user_effect(() => {
3537
+ if ($$props.radius !== void 0) engine?.setRadius($$props.radius);
3538
+ });
3539
+ $2__namespace.user_effect(() => {
3540
+ if ($$props.maxHeight !== void 0) engine?.setMaxHeight($$props.maxHeight);
3541
+ });
3542
+ $2__namespace.user_effect(() => {
3543
+ if ($$props.backdropColor !== void 0) engine?.setScrimColor($$props.backdropColor);
3544
+ });
3545
+ $2__namespace.user_effect(() => {
3546
+ if ($$props.backdropOpacity !== void 0) engine?.setBackdropRange([0, $$props.backdropOpacity]);
3547
+ });
3548
+ $2__namespace.user_effect(() => {
3549
+ if (snap() === void 0) return;
3550
+ const next = snap();
3551
+ if (!engine || svelte.untrack(() => $2__namespace.get(viewState).activeId) === next) return;
3552
+ void engine.snapTo(next);
3553
+ });
3554
+ $2__namespace.user_effect(() => {
3555
+ if (openProp() === void 0) return;
3556
+ const next = openProp();
3557
+ if (!engine) return;
3558
+ const isOpen = svelte.untrack(() => $2__namespace.get(viewState).size) > 0;
3559
+ if (next && !isOpen) void engine.open();
3560
+ else if (!next && isOpen) void engine.close();
3561
+ });
2744
3562
  let allowedIds = $2__namespace.derived(() => $$props.allowed && $$props.allowed.length > 0 ? $$props.allowed : $$props.snapPoints.map((s) => s.id));
2745
3563
  let activeIdx = $2__namespace.derived(() => $2__namespace.get(allowedIds).indexOf($2__namespace.get(viewState).activeId));
2746
3564
  let isVerticalAxis = $2__namespace.derived(() => mode() === "bottom" || mode() === "top");
@@ -2748,10 +3566,17 @@ function BottomSheet($$anchor, $$props) {
2748
3566
  snapTo,
2749
3567
  open,
2750
3568
  close,
3569
+ expand,
3570
+ collapse,
3571
+ isTop,
3572
+ depth,
2751
3573
  setAllowed,
2752
3574
  setSnapPoints,
2753
3575
  setScrim,
2754
3576
  setScrimOverlay,
3577
+ setRadius,
3578
+ setMaxHeight,
3579
+ canDismiss,
2755
3580
  getState,
2756
3581
  getEngine
2757
3582
  };
@@ -2762,7 +3587,9 @@ function BottomSheet($$anchor, $$props) {
2762
3587
  var div_1 = root_1();
2763
3588
  $2__namespace.bind_this(div_1, ($$value) => $2__namespace.set(backdropEl, $$value), () => $2__namespace.get(backdropEl));
2764
3589
  $2__namespace.delegated("click", div_1, function(...$$args) {
2765
- (closeOnBackdrop() ? () => engine?.close() : void 0)?.apply(this, $$args);
3590
+ (closeOnBackdrop() ? () => {
3591
+ if (engine?.canDismiss()) void engine.close("backdrop");
3592
+ } : void 0)?.apply(this, $$args);
2766
3593
  });
2767
3594
  $2__namespace.append($$anchor2, div_1);
2768
3595
  };