@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/react.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
+ var reactDom = require('react-dom');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
6
 
6
7
  var __defProp = Object.defineProperty;
@@ -25,16 +26,52 @@ var SheetStack = class {
25
26
  this.entries.splice(idx, 1);
26
27
  this.recompute();
27
28
  }
29
+ promote(id) {
30
+ const idx = this.entries.findIndex((e) => e.id === id);
31
+ if (idx === -1) return;
32
+ if (idx !== this.entries.length - 1) {
33
+ const [entry] = this.entries.splice(idx, 1);
34
+ this.entries.push(entry);
35
+ }
36
+ this.recompute();
37
+ }
38
+ update() {
39
+ this.recompute();
40
+ }
28
41
  size() {
29
42
  return this.entries.length;
30
43
  }
44
+ depthOf(id) {
45
+ const open = [];
46
+ for (const entry of this.entries) {
47
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
48
+ }
49
+ const idx = open.findIndex((e) => e.id === id);
50
+ if (idx === -1) return 0;
51
+ return open.length - 1 - idx;
52
+ }
31
53
  clear() {
32
54
  this.entries = [];
33
55
  }
34
56
  recompute() {
57
+ let top = null;
58
+ const open = [];
59
+ for (const entry of this.entries) {
60
+ if (!entry.isOpen || entry.isOpen()) {
61
+ top = entry;
62
+ open.push(entry);
63
+ }
64
+ }
65
+ if (!top && this.entries.length > 0) {
66
+ top = this.entries[this.entries.length - 1];
67
+ }
35
68
  this.entries.forEach((entry, i) => {
36
69
  entry.setZIndex(BASE_Z + i * STEP);
37
- entry.setIsTop(i === this.entries.length - 1);
70
+ entry.setIsTop(entry === top);
71
+ if (entry.setDepth) {
72
+ const openIdx = open.indexOf(entry);
73
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
74
+ }
38
75
  });
39
76
  }
40
77
  };
@@ -137,7 +174,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
137
174
  var resolveSnap = (point, mode, measureFit) => {
138
175
  if (typeof point === "number") return Math.max(0, point);
139
176
  if (point === "full") return viewportSize(mode);
140
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
177
+ if (point === "fit" || point === "content") {
178
+ return Math.max(0, measureFit?.() ?? 0);
179
+ }
141
180
  const pctMatch = point.match(PERCENT);
142
181
  if (pctMatch) {
143
182
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -325,14 +364,17 @@ function installAutoCollapse(deps) {
325
364
  const fire = () => {
326
365
  if (deps.isDestroyed()) return;
327
366
  if (deps.isDragging()) return;
328
- const allowed = deps.getAllowedIds();
329
- const target = allowed.find((id) => {
367
+ const active = deps.resolveSnap(deps.getActiveId());
368
+ if (!active || active.size === 0) return;
369
+ let target = null;
370
+ for (const id of deps.getAllowedIds()) {
330
371
  const snap = deps.resolveSnap(id);
331
- return snap !== null && snap.size > 0;
332
- });
372
+ if (!snap || snap.size <= 0) continue;
373
+ if (!target || snap.size < target.size) target = snap;
374
+ }
333
375
  if (!target) return;
334
- if (target === deps.getActiveId()) return;
335
- deps.snapTo(target);
376
+ if (target.id === active.id || target.size >= active.size) return;
377
+ deps.snapTo(target.id);
336
378
  };
337
379
  const reset = () => {
338
380
  clearTimer();
@@ -343,6 +385,7 @@ function installAutoCollapse(deps) {
343
385
  }, ms);
344
386
  };
345
387
  const unsubscribe = deps.on("snap", () => reset());
388
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
346
389
  return () => {
347
390
  clearTimer();
348
391
  unsubscribe();
@@ -355,6 +398,7 @@ function notifyLinkedSheets(sheets, self) {
355
398
  for (const linked of sheets) {
356
399
  if (linked === self) continue;
357
400
  try {
401
+ if (linked.state.size === 0) continue;
358
402
  const ids = linked.getAllowedIds();
359
403
  const target = ids.find((id) => id !== "closed");
360
404
  if (target && target !== linked.state.activeId) {
@@ -369,6 +413,42 @@ function notifyLinkedSheets(sheets, self) {
369
413
  }
370
414
 
371
415
  // src/core/features/route.ts
416
+ var closers = [];
417
+ var handlerInstalled = false;
418
+ var suppress = 0;
419
+ var ensureHandler = () => {
420
+ if (handlerInstalled || typeof window === "undefined") return;
421
+ handlerInstalled = true;
422
+ window.addEventListener("popstate", () => {
423
+ if (suppress > 0) {
424
+ suppress -= 1;
425
+ return;
426
+ }
427
+ let target = null;
428
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
429
+ const c = closers[i];
430
+ if (!c.isOpen()) continue;
431
+ if (!target) target = c;
432
+ if (c.isTop()) {
433
+ target = c;
434
+ break;
435
+ }
436
+ }
437
+ if (!target) return;
438
+ target.consumed = true;
439
+ const idx = closers.indexOf(target);
440
+ if (idx !== -1) closers.splice(idx, 1);
441
+ target.close();
442
+ });
443
+ };
444
+ var programmaticBack = () => {
445
+ suppress += 1;
446
+ try {
447
+ history.back();
448
+ } catch {
449
+ suppress = Math.max(0, suppress - 1);
450
+ }
451
+ };
372
452
  function installRoute(deps) {
373
453
  if (typeof window === "undefined") {
374
454
  return () => {
@@ -378,82 +458,81 @@ function installRoute(deps) {
378
458
  return () => {
379
459
  };
380
460
  }
381
- let routePushed = false;
382
- let closeOnBackPushed = false;
383
- let detachPopstate = null;
384
- const popOurMarker = (key, expected) => {
385
- const state = history.state;
386
- if (state && state[key] === expected) {
387
- try {
388
- history.back();
389
- } catch {
390
- }
461
+ let pushed = false;
462
+ let closer = null;
463
+ const teardownEntry = () => {
464
+ if (closer) {
465
+ const idx = closers.indexOf(closer);
466
+ if (idx !== -1) closers.splice(idx, 1);
467
+ }
468
+ const wasConsumed = closer?.consumed ?? false;
469
+ closer = null;
470
+ if (pushed && !wasConsumed) {
471
+ pushed = false;
472
+ programmaticBack();
473
+ } else {
474
+ pushed = false;
391
475
  }
392
476
  };
393
477
  const onOpen = () => {
394
- if (deps.routedTo && !detachPopstate) {
395
- const target = deps.routedTo;
396
- try {
397
- history.pushState({ __bsRouted: target }, "", target);
398
- routePushed = true;
399
- } catch {
400
- }
401
- const onPop = (event) => {
402
- if (deps.isDestroyed()) return;
403
- const state = event.state;
404
- if (state && state.__bsRouted === target && deps.getSize() > 0) {
405
- routePushed = false;
406
- void deps.close();
407
- }
408
- };
409
- window.addEventListener("popstate", onPop);
410
- detachPopstate = () => window.removeEventListener("popstate", onPop);
411
- } else if (deps.closeOnBack && !detachPopstate) {
412
- try {
478
+ if (closer) return;
479
+ closer = {
480
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
481
+ isTop: () => deps.isTopSheet(),
482
+ close: () => void deps.close(),
483
+ consumed: false
484
+ };
485
+ ensureHandler();
486
+ closers.push(closer);
487
+ try {
488
+ if (deps.routedTo !== void 0) {
489
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
490
+ } else {
413
491
  history.pushState({ __bsSheet: deps.sheetId }, "");
414
- closeOnBackPushed = true;
415
- } catch {
416
492
  }
417
- const onPop = () => {
418
- if (deps.isDestroyed()) return;
419
- if (deps.getSize() > 0 && deps.isTopSheet()) {
420
- const state = history.state;
421
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
422
- void deps.close();
423
- }
424
- };
425
- window.addEventListener("popstate", onPop);
426
- detachPopstate = () => window.removeEventListener("popstate", onPop);
427
- }
428
- };
429
- const onClose = () => {
430
- detachPopstate?.();
431
- detachPopstate = null;
432
- if (routePushed) {
433
- routePushed = false;
434
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
435
- }
436
- if (closeOnBackPushed) {
437
- closeOnBackPushed = false;
438
- popOurMarker("__bsSheet", deps.sheetId);
493
+ pushed = true;
494
+ } catch {
495
+ pushed = false;
439
496
  }
440
497
  };
441
498
  const unsubscribeOpen = deps.on("open", onOpen);
442
- const unsubscribeClose = deps.on("close", onClose);
499
+ const unsubscribeClose = deps.on("close", teardownEntry);
443
500
  if (deps.getSize() > 0) onOpen();
444
501
  return () => {
445
502
  unsubscribeOpen();
446
503
  unsubscribeClose();
447
- detachPopstate?.();
448
- detachPopstate = null;
449
- if (routePushed) {
450
- routePushed = false;
451
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
452
- }
453
- if (closeOnBackPushed) {
454
- closeOnBackPushed = false;
455
- popOurMarker("__bsSheet", deps.sheetId);
456
- }
504
+ teardownEntry();
505
+ };
506
+ }
507
+ function installRouteChange(deps) {
508
+ if (typeof window === "undefined" || typeof history === "undefined") {
509
+ return () => {
510
+ };
511
+ }
512
+ const onChange = () => {
513
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
514
+ void deps.close();
515
+ };
516
+ const wrap = (key) => {
517
+ const original = history[key];
518
+ const patched = function(...args) {
519
+ original.apply(this, args);
520
+ onChange();
521
+ };
522
+ history[key] = patched;
523
+ return () => {
524
+ if (history[key] === patched) {
525
+ history[key] = original;
526
+ }
527
+ };
528
+ };
529
+ window.addEventListener("popstate", onChange);
530
+ const restorePush = wrap("pushState");
531
+ const restoreReplace = wrap("replaceState");
532
+ return () => {
533
+ window.removeEventListener("popstate", onChange);
534
+ restorePush();
535
+ restoreReplace();
457
536
  };
458
537
  }
459
538
 
@@ -555,13 +634,17 @@ function installResizeObserver(deps) {
555
634
  if (viewportSize2 > 0 && viewportSize2 < max) {
556
635
  deps.setMaxAxisSize(viewportSize2);
557
636
  }
637
+ const wasAnimating = deps.isAnimating?.() ?? false;
558
638
  deps.cancelInFlight();
559
639
  deps.newCycle();
560
640
  const current = deps.resolveActiveSnap();
561
641
  const newMax = deps.getMaxAxisSize();
562
642
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
563
643
  deps.setSize(targetSize);
564
- if (current && !deps.isDragging()) deps.applySize(targetSize);
644
+ if (current && !deps.isDragging()) {
645
+ deps.applySize(targetSize);
646
+ if (wasAnimating) deps.resyncAfterCancel?.();
647
+ }
565
648
  };
566
649
  let resizeObserver = null;
567
650
  if (typeof ResizeObserver !== "undefined") {
@@ -676,8 +759,88 @@ function devWarn(...args) {
676
759
  }
677
760
  }
678
761
 
762
+ // src/core/primitives/overlay-position.ts
763
+ function resolveSheetAnchoredStyle(mode, position, inset) {
764
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
765
+ switch (mode) {
766
+ case "bottom":
767
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
768
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
769
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
770
+ case "top":
771
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
772
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
773
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
774
+ case "left":
775
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
776
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
777
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
778
+ case "right":
779
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
780
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
781
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
782
+ }
783
+ }
784
+ function applyOverlayPosition(ws, mode, position, inset) {
785
+ switch (position) {
786
+ case "top-left":
787
+ ws.top = inset;
788
+ ws.left = inset;
789
+ break;
790
+ case "top-center":
791
+ ws.top = inset;
792
+ ws.left = "50%";
793
+ ws.transform = "translateX(-50%)";
794
+ break;
795
+ case "top-right":
796
+ ws.top = inset;
797
+ ws.right = inset;
798
+ break;
799
+ case "center-left":
800
+ ws.top = "50%";
801
+ ws.left = inset;
802
+ ws.transform = "translateY(-50%)";
803
+ break;
804
+ case "center":
805
+ ws.top = "50%";
806
+ ws.left = "50%";
807
+ ws.transform = "translate(-50%, -50%)";
808
+ break;
809
+ case "center-right":
810
+ ws.top = "50%";
811
+ ws.right = inset;
812
+ ws.transform = "translateY(-50%)";
813
+ break;
814
+ case "bottom-left":
815
+ ws.bottom = inset;
816
+ ws.left = inset;
817
+ break;
818
+ case "bottom-center":
819
+ ws.bottom = inset;
820
+ ws.left = "50%";
821
+ ws.transform = "translateX(-50%)";
822
+ break;
823
+ case "bottom-right":
824
+ ws.bottom = inset;
825
+ ws.right = inset;
826
+ break;
827
+ case "sheet-top-left":
828
+ case "sheet-top-center":
829
+ case "sheet-top-right": {
830
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
831
+ if (style.top !== void 0) ws.top = style.top;
832
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
833
+ if (style.left !== void 0) ws.left = style.left;
834
+ if (style.right !== void 0) ws.right = style.right;
835
+ if (style.transform !== void 0) ws.transform = style.transform;
836
+ break;
837
+ }
838
+ }
839
+ }
840
+
679
841
  // src/core/controllers/scrim-controller.ts
680
842
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
843
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
681
844
  var ScrimController = class {
682
845
  constructor(deps, opts) {
683
846
  __publicField(this, "screenComponent");
@@ -688,6 +851,8 @@ var ScrimController = class {
688
851
  __publicField(this, "getAllowedIds");
689
852
  __publicField(this, "getResolvedSnaps");
690
853
  __publicField(this, "snapToFn");
854
+ __publicField(this, "closeFn");
855
+ __publicField(this, "scrimInteractive", false);
691
856
  __publicField(this, "scrimMode");
692
857
  __publicField(this, "backdropRange");
693
858
  __publicField(this, "screenRange");
@@ -708,14 +873,17 @@ var ScrimController = class {
708
873
  this.getAllowedIds = deps.getAllowedIds;
709
874
  this.getResolvedSnaps = deps.getResolvedSnaps;
710
875
  this.snapToFn = deps.snapTo;
876
+ this.closeFn = deps.close;
711
877
  this.scrimMode = opts.scrimMode ?? "full";
712
878
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
713
879
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
714
880
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
715
881
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
882
+ const explicitColor = opts.scrimColor ?? preset?.color;
883
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
716
884
  if (this.screenComponent) {
717
885
  this.applyScrimStyles({
718
- color: opts.scrimColor ?? preset?.color,
886
+ color: effectiveColor,
719
887
  blur: opts.scrimBlur ?? preset?.blur,
720
888
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
721
889
  });
@@ -849,11 +1017,15 @@ var ScrimController = class {
849
1017
  }
850
1018
  const onClick = () => {
851
1019
  if (this.isDestroyed()) return;
1020
+ if (this.closeFn) {
1021
+ this.closeFn();
1022
+ return;
1023
+ }
852
1024
  const allowed = this.getAllowedIds();
853
1025
  const snaps = this.getResolvedSnaps();
854
1026
  const fallback = allowed.find((id) => {
855
1027
  const snap = findById(id, snaps);
856
- return snap !== null && snap.size > 0;
1028
+ return snap !== null && snap.size === 0;
857
1029
  });
858
1030
  if (fallback) this.snapToFn(fallback);
859
1031
  };
@@ -870,6 +1042,7 @@ var ScrimController = class {
870
1042
  if (this.isDestroyed()) return;
871
1043
  if (enabled === this.scrimEnabled) return;
872
1044
  this.scrimEnabled = enabled;
1045
+ this.invalidateOpacityCache();
873
1046
  if (!enabled) {
874
1047
  this.savedScrimRanges = {
875
1048
  screen: this.screenRange,
@@ -893,7 +1066,12 @@ var ScrimController = class {
893
1066
  blur: cfg.blur ?? null,
894
1067
  interactive: cfg.interactive
895
1068
  });
896
- this.screenRange = cfg.range;
1069
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1070
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1071
+ } else {
1072
+ this.screenRange = cfg.range;
1073
+ this.backdropRange = cfg.range;
1074
+ }
897
1075
  }
898
1076
  const noop = () => {
899
1077
  };
@@ -943,59 +1121,7 @@ var ScrimController = class {
943
1121
  wrapper.className = "bs-scrim-overlay";
944
1122
  const ws = wrapper.style;
945
1123
  ws.position = "absolute";
946
- switch (position) {
947
- case "top-left":
948
- ws.top = inset;
949
- ws.left = inset;
950
- break;
951
- case "top-center":
952
- ws.top = inset;
953
- ws.left = "50%";
954
- ws.transform = "translateX(-50%)";
955
- break;
956
- case "top-right":
957
- ws.top = inset;
958
- ws.right = inset;
959
- break;
960
- case "center-left":
961
- ws.top = "50%";
962
- ws.left = inset;
963
- ws.transform = "translateY(-50%)";
964
- break;
965
- case "center":
966
- ws.top = "50%";
967
- ws.left = "50%";
968
- ws.transform = "translate(-50%, -50%)";
969
- break;
970
- case "center-right":
971
- ws.top = "50%";
972
- ws.right = inset;
973
- ws.transform = "translateY(-50%)";
974
- break;
975
- case "bottom-left":
976
- ws.bottom = inset;
977
- ws.left = inset;
978
- break;
979
- case "bottom-center":
980
- ws.bottom = inset;
981
- ws.left = "50%";
982
- ws.transform = "translateX(-50%)";
983
- break;
984
- case "bottom-right":
985
- ws.bottom = inset;
986
- ws.right = inset;
987
- break;
988
- case "sheet-top-left":
989
- case "sheet-top-center":
990
- case "sheet-top-right":
991
- applySheetAnchoredPosition(
992
- ws,
993
- this.mode,
994
- position,
995
- inset
996
- );
997
- break;
998
- }
1124
+ applyOverlayPosition(ws, this.mode, position, inset);
999
1125
  if (interactive) {
1000
1126
  ws.pointerEvents = "auto";
1001
1127
  }
@@ -1054,6 +1180,7 @@ var ScrimController = class {
1054
1180
  }
1055
1181
  }
1056
1182
  if (opts.interactive !== void 0) {
1183
+ this.scrimInteractive = opts.interactive;
1057
1184
  s.pointerEvents = opts.interactive ? "auto" : "none";
1058
1185
  }
1059
1186
  }
@@ -1068,38 +1195,9 @@ var ScrimController = class {
1068
1195
  const s = this.screenComponent.style;
1069
1196
  s.position = "fixed";
1070
1197
  s.inset = insetByMode[this.mode];
1071
- s.pointerEvents = "none";
1198
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1072
1199
  }
1073
1200
  };
1074
- function resolveSheetAnchoredStyle(mode, position, inset) {
1075
- const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1076
- switch (mode) {
1077
- case "bottom":
1078
- if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1079
- if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1080
- return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1081
- case "top":
1082
- if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1083
- if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1084
- return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1085
- case "left":
1086
- if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1087
- if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1088
- return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1089
- case "right":
1090
- if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1091
- if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1092
- return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1093
- }
1094
- }
1095
- function applySheetAnchoredPosition(ws, mode, position, inset) {
1096
- const style = resolveSheetAnchoredStyle(mode, position, inset);
1097
- if (style.top !== void 0) ws.top = style.top;
1098
- if (style.bottom !== void 0) ws.bottom = style.bottom;
1099
- if (style.left !== void 0) ws.left = style.left;
1100
- if (style.right !== void 0) ws.right = style.right;
1101
- if (style.transform !== void 0) ws.transform = style.transform;
1102
- }
1103
1201
 
1104
1202
  // src/core/animation/animation.ts
1105
1203
  var easeOutBack = (t) => {
@@ -1151,6 +1249,10 @@ var tween = ({
1151
1249
  promise
1152
1250
  };
1153
1251
  };
1252
+ var prefersReducedMotion = () => {
1253
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1254
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1255
+ };
1154
1256
 
1155
1257
  // src/core/animation/spring.ts
1156
1258
  var DEFAULT_SPRING = {
@@ -1181,14 +1283,20 @@ var runSpring = ({
1181
1283
  if (cancelled) return;
1182
1284
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1183
1285
  lastTime = now;
1184
- const subDt = Math.min(dt, 1 / 240);
1185
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1286
+ const steps = Math.max(1, Math.ceil(dt * 240));
1287
+ const subDt = dt / steps;
1288
+ let stepsLeft = steps;
1186
1289
  while (stepsLeft-- > 0) {
1187
1290
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1188
1291
  const accel = force / cfg.mass;
1189
1292
  v += accel * subDt;
1190
1293
  x += v * subDt;
1191
1294
  }
1295
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1296
+ onUpdate(to, 0);
1297
+ resolve();
1298
+ return;
1299
+ }
1192
1300
  onUpdate(x, v);
1193
1301
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1194
1302
  if (isAtRest) {
@@ -1363,9 +1471,13 @@ var isVisible = (el) => {
1363
1471
  var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1364
1472
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1365
1473
  );
1474
+ var trapStack = [];
1366
1475
  var installFocusTrap = (container, options = {}) => {
1367
1476
  if (typeof document === "undefined") return () => {
1368
1477
  };
1478
+ const token = {};
1479
+ trapStack.push(token);
1480
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1369
1481
  const previouslyFocused = document.activeElement;
1370
1482
  const focusInitial = () => {
1371
1483
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1373,6 +1485,7 @@ var installFocusTrap = (container, options = {}) => {
1373
1485
  };
1374
1486
  focusInitial();
1375
1487
  const handleKey = (e) => {
1488
+ if (!isActive()) return;
1376
1489
  if (e.key === "Escape" && options.onEscape) {
1377
1490
  options.onEscape();
1378
1491
  return;
@@ -1395,6 +1508,7 @@ var installFocusTrap = (container, options = {}) => {
1395
1508
  }
1396
1509
  };
1397
1510
  const handleFocusIn = (e) => {
1511
+ if (!isActive()) return;
1398
1512
  const target = e.target;
1399
1513
  if (target && !container.contains(target)) {
1400
1514
  const list = focusables(container);
@@ -1407,6 +1521,8 @@ var installFocusTrap = (container, options = {}) => {
1407
1521
  return () => {
1408
1522
  if (released) return;
1409
1523
  released = true;
1524
+ const idx = trapStack.indexOf(token);
1525
+ if (idx !== -1) trapStack.splice(idx, 1);
1410
1526
  document.removeEventListener("keydown", handleKey, true);
1411
1527
  document.removeEventListener("focusin", handleFocusIn, true);
1412
1528
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1462,11 +1578,17 @@ function createInertSiblings(rootProvider) {
1462
1578
  return {
1463
1579
  apply() {
1464
1580
  if (typeof document === "undefined") return;
1465
- const root = rootProvider();
1466
- const parent = root.parentElement ?? document.body;
1581
+ let top = rootProvider();
1582
+ while (top.parentElement && top.parentElement !== document.body) {
1583
+ top = top.parentElement;
1584
+ }
1585
+ const parent = top.parentElement ?? document.body;
1467
1586
  for (const child of Array.from(parent.children)) {
1468
- if (child === root) continue;
1587
+ if (child === top) continue;
1469
1588
  if (child.hasAttribute("inert")) continue;
1589
+ if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
1590
+ continue;
1591
+ }
1470
1592
  child.setAttribute("inert", "");
1471
1593
  tracked.push(child);
1472
1594
  }
@@ -1482,7 +1604,6 @@ function createInertSiblings(rootProvider) {
1482
1604
  var LifecycleController = class {
1483
1605
  constructor(deps, opts) {
1484
1606
  __publicField(this, "element");
1485
- __publicField(this, "closeFn");
1486
1607
  __publicField(this, "focusTrapEnabled");
1487
1608
  __publicField(this, "initialFocus");
1488
1609
  __publicField(this, "closeOnEscape");
@@ -1496,7 +1617,6 @@ var LifecycleController = class {
1496
1617
  __publicField(this, "installed", false);
1497
1618
  __publicField(this, "destroyed", false);
1498
1619
  this.element = deps.element;
1499
- this.closeFn = deps.close;
1500
1620
  this.focusTrapEnabled = opts.focusTrap ?? false;
1501
1621
  this.initialFocus = opts.initialFocus;
1502
1622
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1506,6 +1626,9 @@ var LifecycleController = class {
1506
1626
  this.returnFocus = opts.returnFocus;
1507
1627
  this.inertSiblings = createInertSiblings(() => this.element);
1508
1628
  }
1629
+ get isInstalled() {
1630
+ return this.installed;
1631
+ }
1509
1632
  install() {
1510
1633
  if (this.destroyed) return;
1511
1634
  this.installed = true;
@@ -1514,8 +1637,7 @@ var LifecycleController = class {
1514
1637
  }
1515
1638
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1516
1639
  this.releaseFocusTrap = installFocusTrap(this.element, {
1517
- initialFocus: this.initialFocus,
1518
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1640
+ initialFocus: this.initialFocus
1519
1641
  });
1520
1642
  }
1521
1643
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1535,7 +1657,7 @@ var LifecycleController = class {
1535
1657
  this.releaseScrollLock = null;
1536
1658
  this.inertSiblings.remove();
1537
1659
  if (this.returnFocus) {
1538
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1660
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1539
1661
  target?.focus?.();
1540
1662
  }
1541
1663
  }
@@ -1669,10 +1791,11 @@ var installGestures = (handle, mode, callbacks) => {
1669
1791
  };
1670
1792
 
1671
1793
  // src/core/primitives/rubber-band.ts
1672
- var RUBBER_BAND_FACTOR = 0.55;
1673
1794
  function rubberBand(overshoot, maxAxisSize) {
1674
1795
  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));
1796
+ if (cap <= 0) return 0;
1797
+ const abs = Math.abs(overshoot);
1798
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1676
1799
  }
1677
1800
 
1678
1801
  // src/core/features/soft-keyboard.ts
@@ -1701,6 +1824,7 @@ var GestureController = class {
1701
1824
  __publicField(this, "dragStartSize", 0);
1702
1825
  __publicField(this, "currentPointerKind", "touch");
1703
1826
  __publicField(this, "keyboardDismissed", false);
1827
+ __publicField(this, "dragSuppressed", false);
1704
1828
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1705
1829
  this.deps = deps;
1706
1830
  }
@@ -1711,6 +1835,11 @@ var GestureController = class {
1711
1835
  if (this.detach) return this.detach;
1712
1836
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1713
1837
  onStart: (_coord, kind) => {
1838
+ if (this.deps.getDisableDrag?.()) {
1839
+ this.dragSuppressed = true;
1840
+ return;
1841
+ }
1842
+ this.dragSuppressed = false;
1714
1843
  this.deps.cancelAnimation();
1715
1844
  this.isDragging_ = true;
1716
1845
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1721,6 +1850,7 @@ var GestureController = class {
1721
1850
  this.deps.emit("dragstart", { size: this.dragStartSize });
1722
1851
  },
1723
1852
  onMove: (delta) => {
1853
+ if (this.dragSuppressed) return;
1724
1854
  const ctx = this.deps.getDragContext();
1725
1855
  const { min, max } = ctx.range;
1726
1856
  const maxAxis = ctx.maxAxisSize;
@@ -1744,6 +1874,10 @@ var GestureController = class {
1744
1874
  }
1745
1875
  },
1746
1876
  onEnd: (delta, velocity, kind) => {
1877
+ if (this.dragSuppressed) {
1878
+ this.dragSuppressed = false;
1879
+ return;
1880
+ }
1747
1881
  this.isDragging_ = false;
1748
1882
  this.keyboardDismissed = false;
1749
1883
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1754,9 +1888,17 @@ var GestureController = class {
1754
1888
  this.deps.settleAfterDrag(delta, velocity, kind);
1755
1889
  },
1756
1890
  onCancel: () => {
1891
+ if (this.dragSuppressed) {
1892
+ this.dragSuppressed = false;
1893
+ return;
1894
+ }
1757
1895
  this.isDragging_ = false;
1758
1896
  this.keyboardDismissed = false;
1759
1897
  this.deps.getRoot()?.removeAttribute("data-dragging");
1898
+ this.deps.emit("dragend", {
1899
+ size: this.deps.getDragContext().size,
1900
+ velocity: 0
1901
+ });
1760
1902
  const restoreTo = this.dragStartSize;
1761
1903
  void this.deps.animateTo(restoreTo, 0);
1762
1904
  }
@@ -1830,6 +1972,287 @@ function createScrollCache(deps) {
1830
1972
  };
1831
1973
  }
1832
1974
 
1975
+ // src/core/primitives/anchor-animations.ts
1976
+ var DEFAULT_DURATION3 = 200;
1977
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1978
+ var PRESET_FRAMES = {
1979
+ fade: {
1980
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1981
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1982
+ },
1983
+ scale: {
1984
+ enter: [
1985
+ { opacity: 0, transform: "scale(0.85)" },
1986
+ { opacity: 1, transform: "scale(1)" }
1987
+ ],
1988
+ exit: [
1989
+ { opacity: 1, transform: "scale(1)" },
1990
+ { opacity: 0, transform: "scale(0.85)" }
1991
+ ]
1992
+ },
1993
+ slide: {
1994
+ enter: [
1995
+ { opacity: 0, transform: "translateY(10px)" },
1996
+ { opacity: 1, transform: "translateY(0)" }
1997
+ ],
1998
+ exit: [
1999
+ { opacity: 1, transform: "translateY(0)" },
2000
+ { opacity: 0, transform: "translateY(10px)" }
2001
+ ]
2002
+ },
2003
+ pop: {
2004
+ enter: [
2005
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
2006
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
2007
+ { opacity: 1, transform: "scale(1)", offset: 1 }
2008
+ ],
2009
+ exit: [
2010
+ { opacity: 1, transform: "scale(1)" },
2011
+ { opacity: 0, transform: "scale(0.6)" }
2012
+ ]
2013
+ }
2014
+ };
2015
+ var INSTANT = {
2016
+ finished: Promise.resolve(),
2017
+ cancel: () => {
2018
+ }
2019
+ };
2020
+ function resolveTransitionFrames(spec, phase) {
2021
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
2022
+ if (spec === "none") return fallback;
2023
+ if (spec === void 0 || typeof spec === "string") {
2024
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
2025
+ return {
2026
+ frames: preset2[phase],
2027
+ duration: DEFAULT_DURATION3,
2028
+ easing: DEFAULT_EASING
2029
+ };
2030
+ }
2031
+ const custom = phase === "enter" ? spec.enter : spec.exit;
2032
+ if (custom) {
2033
+ return {
2034
+ frames: custom,
2035
+ duration: spec.duration ?? DEFAULT_DURATION3,
2036
+ easing: spec.easing ?? DEFAULT_EASING
2037
+ };
2038
+ }
2039
+ if (spec.preset === "none") return fallback;
2040
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
2041
+ return {
2042
+ frames: preset[phase],
2043
+ duration: spec.duration ?? DEFAULT_DURATION3,
2044
+ easing: spec.easing ?? DEFAULT_EASING
2045
+ };
2046
+ }
2047
+ function runAnchorTransition(el, spec, phase) {
2048
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
2049
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
2050
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
2051
+ if (!frames || duration <= 0) return INSTANT;
2052
+ if (typeof el.animate !== "function") return INSTANT;
2053
+ const animation = el.animate(frames, {
2054
+ duration,
2055
+ easing,
2056
+ fill: "both"
2057
+ });
2058
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
2059
+ return {
2060
+ finished,
2061
+ cancel: () => {
2062
+ try {
2063
+ animation.cancel();
2064
+ } catch {
2065
+ return;
2066
+ }
2067
+ }
2068
+ };
2069
+ }
2070
+
2071
+ // src/core/features/sheet-anchors.ts
2072
+ function attachAnchor(deps, opts) {
2073
+ const doc = deps.host.ownerDocument;
2074
+ const wrapper = doc.createElement("div");
2075
+ wrapper.className = "bs-anchor";
2076
+ const interactive = opts.interactive ?? true;
2077
+ const position = opts.position ?? "sheet-top-right";
2078
+ const isDock = position === "dock-bottom" || position === "dock-top";
2079
+ const ws = wrapper.style;
2080
+ ws.position = "fixed";
2081
+ if (isDock) {
2082
+ ws.left = "0";
2083
+ ws.right = "0";
2084
+ if (position === "dock-bottom") {
2085
+ ws.bottom = "0";
2086
+ } else {
2087
+ ws.top = "0";
2088
+ }
2089
+ } else {
2090
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2091
+ }
2092
+ if (opts.fadeRange) {
2093
+ const [r0, r1] = opts.fadeRange;
2094
+ const span = Math.max(r1 - r0, 1e-4);
2095
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2096
+ }
2097
+ wrapper.appendChild(opts.element);
2098
+ deps.host.appendChild(wrapper);
2099
+ let visible = null;
2100
+ let inFlight = null;
2101
+ const shouldShow = (state) => {
2102
+ if (typeof opts.showOn === "function") return opts.showOn(state);
2103
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
2104
+ return isDock || state.size > 0;
2105
+ };
2106
+ const applyVisibility = (next, animate) => {
2107
+ if (next === visible) return;
2108
+ visible = next;
2109
+ inFlight?.cancel();
2110
+ inFlight = null;
2111
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2112
+ if (next) {
2113
+ wrapper.style.visibility = "";
2114
+ if (animate) {
2115
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2116
+ }
2117
+ return;
2118
+ }
2119
+ if (!animate) {
2120
+ wrapper.style.visibility = "hidden";
2121
+ return;
2122
+ }
2123
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2124
+ inFlight = handle;
2125
+ void handle.finished.then(() => {
2126
+ if (inFlight === handle && visible === false) {
2127
+ wrapper.style.visibility = "hidden";
2128
+ }
2129
+ });
2130
+ };
2131
+ const evaluate = (animate) => {
2132
+ if (deps.isDestroyed()) return;
2133
+ applyVisibility(shouldShow(deps.getState()), animate);
2134
+ };
2135
+ const offs = [
2136
+ deps.on("snap", () => evaluate(true)),
2137
+ deps.on("open", () => evaluate(true)),
2138
+ deps.on("close", () => evaluate(true))
2139
+ ];
2140
+ evaluate(false);
2141
+ return {
2142
+ wrapper,
2143
+ detach: () => {
2144
+ offs.forEach((off) => off());
2145
+ inFlight?.cancel();
2146
+ inFlight = null;
2147
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2148
+ },
2149
+ syncZ: (z) => {
2150
+ wrapper.style.zIndex = String(z);
2151
+ }
2152
+ };
2153
+ }
2154
+
2155
+ // src/core/features/scrim-stages.ts
2156
+ var matchesId = (def, activeId) => {
2157
+ if (def.for === void 0) return false;
2158
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2159
+ };
2160
+ var matchesRange = (def, progress) => {
2161
+ if (!def.forRange) return false;
2162
+ const [from, to] = def.forRange;
2163
+ return progress >= from && progress <= to;
2164
+ };
2165
+ function installScrimStages(deps, opts) {
2166
+ const doc = deps.host.ownerDocument;
2167
+ const entries = opts.stages.map((def) => {
2168
+ const wrapper = doc.createElement("div");
2169
+ wrapper.className = "bs-scrim-stage";
2170
+ const ws = wrapper.style;
2171
+ ws.position = "absolute";
2172
+ applyOverlayPosition(
2173
+ ws,
2174
+ deps.mode,
2175
+ def.position ?? opts.position ?? "center",
2176
+ def.inset ?? opts.inset ?? "16px"
2177
+ );
2178
+ ws.visibility = "hidden";
2179
+ ws.pointerEvents = "none";
2180
+ wrapper.appendChild(def.element);
2181
+ deps.host.appendChild(wrapper);
2182
+ return { def, wrapper, inFlight: null };
2183
+ });
2184
+ let active = null;
2185
+ const resolveActive = () => {
2186
+ const state = deps.getState();
2187
+ if (state.size === 0) {
2188
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2189
+ }
2190
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2191
+ };
2192
+ const hide = (entry, animate) => {
2193
+ entry.inFlight?.cancel();
2194
+ entry.wrapper.style.pointerEvents = "none";
2195
+ if (!animate) {
2196
+ entry.inFlight = null;
2197
+ entry.wrapper.style.visibility = "hidden";
2198
+ return;
2199
+ }
2200
+ const handle = runAnchorTransition(
2201
+ entry.def.element,
2202
+ entry.def.animation ?? opts.animation,
2203
+ "exit"
2204
+ );
2205
+ entry.inFlight = handle;
2206
+ void handle.finished.then(() => {
2207
+ if (entry.inFlight === handle && active !== entry) {
2208
+ entry.wrapper.style.visibility = "hidden";
2209
+ }
2210
+ });
2211
+ };
2212
+ const show = (entry, animate) => {
2213
+ entry.inFlight?.cancel();
2214
+ entry.inFlight = null;
2215
+ entry.wrapper.style.visibility = "";
2216
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2217
+ if (animate) {
2218
+ entry.inFlight = runAnchorTransition(
2219
+ entry.def.element,
2220
+ entry.def.animation ?? opts.animation,
2221
+ "enter"
2222
+ );
2223
+ }
2224
+ };
2225
+ const update = (animate) => {
2226
+ if (deps.isDestroyed()) return;
2227
+ const next = resolveActive();
2228
+ if (next === active) return;
2229
+ const prev = active;
2230
+ active = next;
2231
+ if (prev) hide(prev, animate);
2232
+ if (next) show(next, animate);
2233
+ };
2234
+ const offs = [
2235
+ deps.on("snap", () => update(true)),
2236
+ deps.on("open", () => update(true)),
2237
+ deps.on("close", () => update(true))
2238
+ ];
2239
+ if (entries.some((e) => e.def.forRange)) {
2240
+ offs.push(deps.on("progress", () => update(true)));
2241
+ }
2242
+ update(false);
2243
+ return () => {
2244
+ offs.forEach((off) => off());
2245
+ for (const entry of entries) {
2246
+ entry.inFlight?.cancel();
2247
+ entry.inFlight = null;
2248
+ if (entry.wrapper.parentNode) {
2249
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2250
+ }
2251
+ }
2252
+ active = null;
2253
+ };
2254
+ }
2255
+
1833
2256
  // src/core/primitives/engine-options.ts
1834
2257
  var DEFAULT_FLICK_VELOCITY = 0.65;
1835
2258
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1848,6 +2271,12 @@ function resolveEngineOptions(opts, extras) {
1848
2271
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
1849
2272
  rubberBandEnabled: opts.rubberBand ?? true,
1850
2273
  closeOnBack: opts.closeOnBack ?? false,
2274
+ persistent: opts.persistent ?? false,
2275
+ disableClose: opts.disableClose ?? false,
2276
+ disableDrag: opts.disableDrag ?? false,
2277
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2278
+ radius: opts.radius,
2279
+ maxHeight: opts.maxHeight,
1851
2280
  initialAllowed,
1852
2281
  initialId,
1853
2282
  scrim: {
@@ -1874,7 +2303,8 @@ function resolveEngineOptions(opts, extras) {
1874
2303
  closeOnEscape: opts.closeOnEscape,
1875
2304
  lockBodyScroll: opts.lockBodyScroll,
1876
2305
  inertSiblings: opts.inertSiblings,
1877
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2306
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2307
+ returnFocus: opts.returnFocusTo
1878
2308
  }
1879
2309
  };
1880
2310
  }
@@ -1919,6 +2349,11 @@ var BottomSheetEngine = class {
1919
2349
  __publicField(this, "lifecycle");
1920
2350
  __publicField(this, "closeOnBack");
1921
2351
  __publicField(this, "routedTo");
2352
+ __publicField(this, "persistent");
2353
+ __publicField(this, "disableCloseFlag");
2354
+ __publicField(this, "disableDragFlag");
2355
+ __publicField(this, "closeOnRouteChange");
2356
+ __publicField(this, "maxHeightCap");
1922
2357
  __publicField(this, "snapPointsRaw");
1923
2358
  __publicField(this, "snaps");
1924
2359
  __publicField(this, "activeId");
@@ -1931,6 +2366,7 @@ var BottomSheetEngine = class {
1931
2366
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
1932
2367
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
1933
2368
  __publicField(this, "isTopSheet", true);
2369
+ __publicField(this, "opening", false);
1934
2370
  __publicField(this, "progressPayload", {
1935
2371
  value: 0,
1936
2372
  size: 0
@@ -1946,6 +2382,12 @@ var BottomSheetEngine = class {
1946
2382
  __publicField(this, "linkedSheets", []);
1947
2383
  __publicField(this, "scrollCache");
1948
2384
  __publicField(this, "teardowns", new TeardownStack());
2385
+ __publicField(this, "anchors", []);
2386
+ __publicField(this, "anchorHost", null);
2387
+ __publicField(this, "stackZ", 100);
2388
+ __publicField(this, "detachScrimStages", null);
2389
+ __publicField(this, "stackEffectEnabled", false);
2390
+ __publicField(this, "stackEffectPrimed", false);
1949
2391
  __publicField(this, "transformTemplate");
1950
2392
  const resolved = resolveEngineOptions(opts, {
1951
2393
  shouldApplyInertSiblings: () => {
@@ -1979,6 +2421,10 @@ var BottomSheetEngine = class {
1979
2421
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1980
2422
  snapTo: (id) => {
1981
2423
  void this.snapTo(id);
2424
+ },
2425
+ close: () => {
2426
+ if (!this.canDismiss()) return;
2427
+ void this.close("backdrop");
1982
2428
  }
1983
2429
  },
1984
2430
  resolved.scrim
@@ -2002,6 +2448,11 @@ var BottomSheetEngine = class {
2002
2448
  );
2003
2449
  this.closeOnBack = resolved.closeOnBack;
2004
2450
  this.routedTo = opts.routedTo;
2451
+ this.persistent = resolved.persistent;
2452
+ this.disableCloseFlag = resolved.disableClose;
2453
+ this.disableDragFlag = resolved.disableDrag;
2454
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2455
+ this.stackEffectEnabled = opts.stackEffect ?? false;
2005
2456
  this.snapPointsRaw = opts.snapPoints;
2006
2457
  this.persistKey = opts.persistKey;
2007
2458
  if (opts.linkedSheets) {
@@ -2014,7 +2465,7 @@ var BottomSheetEngine = class {
2014
2465
  this.snapPointsRaw,
2015
2466
  resolved.initialAllowed,
2016
2467
  this.mode,
2017
- () => this.handle.offsetHeight,
2468
+ () => this.measureFitSize(),
2018
2469
  (maxAxisSize) => {
2019
2470
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2020
2471
  }
@@ -2048,6 +2499,19 @@ var BottomSheetEngine = class {
2048
2499
  io.observe(this.element);
2049
2500
  this.teardowns.add(() => io.disconnect());
2050
2501
  }
2502
+ this.installFitObserver();
2503
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2504
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2505
+ this.applyAutoAriaLabelledBy();
2506
+ if (this.closeOnRouteChange) {
2507
+ this.teardowns.add(
2508
+ installRouteChange({
2509
+ isDestroyed: () => this.destroyed,
2510
+ getSize: () => this.size,
2511
+ close: () => this.close()
2512
+ })
2513
+ );
2514
+ }
2051
2515
  if (this.persistKey) {
2052
2516
  this.teardowns.add(installPersist(this, this.persistKey));
2053
2517
  }
@@ -2072,7 +2536,7 @@ var BottomSheetEngine = class {
2072
2536
  isTopSheet: () => this.isTopSheet,
2073
2537
  getSize: () => this.size,
2074
2538
  isDestroyed: () => this.destroyed,
2075
- close: () => this.close(),
2539
+ close: () => this.close("back"),
2076
2540
  on: (event, fn) => this.on(event, fn),
2077
2541
  sheetId: this.id
2078
2542
  })
@@ -2166,6 +2630,10 @@ var BottomSheetEngine = class {
2166
2630
  const previousSize = this.size;
2167
2631
  this.scrollCache.cache(previousId, previousSize, target.size);
2168
2632
  this.activeId = id;
2633
+ if (wasClosed && target.size > 0) {
2634
+ this.opening = true;
2635
+ sheetStack.promote(this.id);
2636
+ }
2169
2637
  if (this.animation.viewTransitionsAvailable) {
2170
2638
  this.currentViewTransition?.skipTransition?.();
2171
2639
  const vt = document.startViewTransition(() => {
@@ -2180,19 +2648,26 @@ var BottomSheetEngine = class {
2180
2648
  } else {
2181
2649
  await this.animation.animateTo(target.size, velocityPxPerMs);
2182
2650
  }
2651
+ this.opening = false;
2183
2652
  externalSignal?.removeEventListener("abort", onExternalAbort);
2184
2653
  if (signal.aborted) return;
2185
2654
  this.scrollCache.restore(id, previousSize, target.size);
2186
2655
  this.updateAriaSlider();
2187
- this.emit("snap", { id, size: target.size });
2656
+ this.emit("snap", {
2657
+ id,
2658
+ size: target.size,
2659
+ progress: this.computeProgress(target.size)
2660
+ });
2188
2661
  if (wasClosed && target.size > 0) {
2189
2662
  this.emit("open", { id });
2190
2663
  this.handleOpen();
2191
2664
  notifyLinkedSheets(this.linkedSheets, this);
2665
+ this.emit("opened", { id });
2192
2666
  }
2193
2667
  if (target.size === 0) {
2194
2668
  this.emit("close", void 0);
2195
2669
  this.handleClose();
2670
+ this.emit("closed", void 0);
2196
2671
  }
2197
2672
  }
2198
2673
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2217,18 +2692,76 @@ var BottomSheetEngine = class {
2217
2692
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2218
2693
  return this.snapTo(target);
2219
2694
  }
2220
- close() {
2695
+ close(reason = "programmatic") {
2221
2696
  if (this.destroyed) return Promise.resolve();
2697
+ if (this.disableCloseFlag) return Promise.resolve();
2698
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2222
2699
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2223
2700
  return this.snapTo(closedId ?? this.activeId);
2224
2701
  }
2225
- setAllowed(ids, snap) {
2702
+ canDismiss() {
2703
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2704
+ }
2705
+ isTop() {
2706
+ return this.isTopSheet;
2707
+ }
2708
+ depth() {
2709
+ return sheetStack.depthOf(this.id);
2710
+ }
2711
+ expand() {
2712
+ if (this.destroyed) return Promise.resolve();
2713
+ const ids = this.allowedIdsBySize();
2714
+ const target = ids[ids.length - 1];
2715
+ if (!target) return Promise.resolve();
2716
+ return this.snapTo(target);
2717
+ }
2718
+ collapse() {
2719
+ if (this.destroyed) return Promise.resolve();
2720
+ const ids = this.allowedIdsBySize();
2721
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2722
+ const target = nonZero ?? ids[0];
2723
+ if (!target) return Promise.resolve();
2724
+ return this.snapTo(target);
2725
+ }
2726
+ setRadius(r) {
2226
2727
  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();
2728
+ this.element.style.setProperty(
2729
+ "--bs-radius",
2730
+ typeof r === "number" ? `${r}px` : r
2731
+ );
2732
+ }
2733
+ setMaxHeight(h) {
2734
+ if (this.destroyed) return;
2735
+ const value = typeof h === "number" ? `${h}px` : h;
2736
+ const axis = layoutAxis(this.mode);
2737
+ this.element.style.setProperty(
2738
+ axis === "height" ? "maxHeight" : "maxWidth",
2739
+ value
2740
+ );
2741
+ if (typeof h === "number") {
2742
+ this.maxHeightCap = h;
2743
+ this.clampToMaxHeight();
2744
+ } else if (typeof window !== "undefined") {
2745
+ const probe2 = document.createElement("div");
2746
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2747
+ document.body.appendChild(probe2);
2748
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2749
+ probe2.remove();
2750
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2751
+ this.clampToMaxHeight();
2752
+ }
2753
+ }
2754
+ clampToMaxHeight() {
2755
+ if (this.maxHeightCap === void 0) return;
2756
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2757
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2758
+ }
2759
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2760
+ this.applySize(this.maxHeightCap);
2231
2761
  }
2762
+ }
2763
+ setAllowed(ids, snap) {
2764
+ if (this.destroyed) return;
2232
2765
  this.snaps.setAllowedIds(ids);
2233
2766
  this.updateAriaSlider();
2234
2767
  if (snap && ids.includes(snap)) {
@@ -2268,6 +2801,79 @@ var BottomSheetEngine = class {
2268
2801
  setScrimOverlay(opts) {
2269
2802
  return this.scrim.setScrimOverlay(opts);
2270
2803
  }
2804
+ addAnchor(opts) {
2805
+ if (this.destroyed || typeof document === "undefined") return () => {
2806
+ };
2807
+ if (!this.anchorHost) {
2808
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2809
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2810
+ this.anchorHost.style.setProperty(
2811
+ "--bs-progress",
2812
+ String(this.computeProgress(this.size))
2813
+ );
2814
+ }
2815
+ const handle = attachAnchor(
2816
+ {
2817
+ mode: this.mode,
2818
+ host: this.anchorHost,
2819
+ getState: () => ({
2820
+ activeId: this.activeId,
2821
+ size: this.size,
2822
+ progress: this.computeProgress(this.size)
2823
+ }),
2824
+ on: (event, fn) => this.on(event, fn),
2825
+ isDestroyed: () => this.destroyed
2826
+ },
2827
+ opts
2828
+ );
2829
+ handle.syncZ(this.stackZ + 1);
2830
+ this.anchors.push(handle);
2831
+ const detach = () => {
2832
+ const idx = this.anchors.indexOf(handle);
2833
+ if (idx === -1) return;
2834
+ this.anchors.splice(idx, 1);
2835
+ handle.detach();
2836
+ };
2837
+ this.teardowns.add(detach);
2838
+ return detach;
2839
+ }
2840
+ setScrimStages(opts) {
2841
+ if (this.destroyed || typeof document === "undefined") return () => {
2842
+ };
2843
+ this.detachScrimStages?.();
2844
+ this.detachScrimStages = null;
2845
+ if (!opts) return () => {
2846
+ };
2847
+ const host = this.screenComponent?.parentElement;
2848
+ if (!host) {
2849
+ console.warn(
2850
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2851
+ );
2852
+ return () => {
2853
+ };
2854
+ }
2855
+ const detachInstalled = installScrimStages(
2856
+ {
2857
+ mode: this.mode,
2858
+ host,
2859
+ getState: () => ({
2860
+ activeId: this.activeId,
2861
+ size: this.size,
2862
+ progress: this.computeProgress(this.size)
2863
+ }),
2864
+ on: (event, fn) => this.on(event, fn),
2865
+ isDestroyed: () => this.destroyed
2866
+ },
2867
+ opts
2868
+ );
2869
+ const detach = () => {
2870
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2871
+ detachInstalled();
2872
+ };
2873
+ this.detachScrimStages = detach;
2874
+ this.teardowns.add(() => this.detachScrimStages?.());
2875
+ return detach;
2876
+ }
2271
2877
  getScrimState() {
2272
2878
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2273
2879
  }
@@ -2287,8 +2893,53 @@ var BottomSheetEngine = class {
2287
2893
  }
2288
2894
  this.updateAriaSlider();
2289
2895
  }
2896
+ recompute() {
2897
+ if (this.destroyed) return;
2898
+ this.snaps.recompute();
2899
+ this.scrim.invalidateOpacityCache();
2900
+ if (this.gesture?.isDragging) return;
2901
+ const current = this.snaps.findById(this.activeId);
2902
+ if (current) {
2903
+ this.size = current.size;
2904
+ this.applySize(this.size);
2905
+ this.updateAriaSlider();
2906
+ }
2907
+ }
2908
+ measureFitSize() {
2909
+ const vertical = layoutAxis(this.mode) === "height";
2910
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2911
+ const content = this.scrollContainer;
2912
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2913
+ if (typeof window === "undefined") return natural;
2914
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2915
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2916
+ }
2917
+ installFitObserver() {
2918
+ if (typeof ResizeObserver === "undefined") return;
2919
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2920
+ return;
2921
+ const targets = [this.handle];
2922
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2923
+ targets.push(this.scrollContainer);
2924
+ }
2925
+ let raf = 0;
2926
+ const ro = new ResizeObserver(() => {
2927
+ if (raf) return;
2928
+ raf = requestAnimationFrame(() => {
2929
+ raf = 0;
2930
+ if (this.destroyed || this.gesture?.isDragging) return;
2931
+ this.recompute();
2932
+ });
2933
+ });
2934
+ for (const t of targets) ro.observe(t);
2935
+ this.teardowns.add(() => {
2936
+ ro.disconnect();
2937
+ if (raf) cancelAnimationFrame(raf);
2938
+ });
2939
+ }
2290
2940
  destroy() {
2291
2941
  this.destroyed = true;
2942
+ this.opening = false;
2292
2943
  this.currentAbort.abort();
2293
2944
  this.currentViewTransition?.skipTransition?.();
2294
2945
  this.currentViewTransition = null;
@@ -2338,6 +2989,36 @@ var BottomSheetEngine = class {
2338
2989
  frozen = true;
2339
2990
  return cancelled;
2340
2991
  }
2992
+ emitBeforeClose(reason) {
2993
+ let cancelled = false;
2994
+ let frozen = false;
2995
+ this.emit("before-close", {
2996
+ reason,
2997
+ cancel: () => {
2998
+ if (frozen) {
2999
+ console.warn(
3000
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3001
+ );
3002
+ return;
3003
+ }
3004
+ cancelled = true;
3005
+ }
3006
+ });
3007
+ frozen = true;
3008
+ return cancelled;
3009
+ }
3010
+ applyAutoAriaLabelledBy() {
3011
+ if (typeof document === "undefined") return;
3012
+ if (!this.lifecycle.focusTrapEnabled) return;
3013
+ if (this.element.getAttribute("aria-labelledby")) return;
3014
+ const scope = this.handle ?? this.element;
3015
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3016
+ if (!titled) return;
3017
+ if (!titled.id) {
3018
+ titled.id = nextInstanceId("bs-title");
3019
+ }
3020
+ this.element.setAttribute("aria-labelledby", titled.id);
3021
+ }
2341
3022
  recomputeSnaps() {
2342
3023
  const seen = /* @__PURE__ */ new Set();
2343
3024
  for (const p of this.snapPointsRaw) {
@@ -2354,24 +3035,42 @@ var BottomSheetEngine = class {
2354
3035
  getAllowedRange() {
2355
3036
  return this.snaps.getAllowedRange();
2356
3037
  }
3038
+ allowedIdsBySize() {
3039
+ return this.snaps.getAllowedIds().slice().sort(
3040
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
3041
+ );
3042
+ }
2357
3043
  updateAriaSlider() {
2358
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
3044
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2359
3045
  }
2360
3046
  registerInStack() {
2361
3047
  this.teardowns.add(sheetStack.push({
2362
3048
  id: this.id,
2363
3049
  setZIndex: (z) => {
3050
+ this.stackZ = z;
2364
3051
  this.element.style.zIndex = String(z);
2365
3052
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3053
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2366
3054
  },
2367
3055
  setIsTop: (isTop) => {
2368
3056
  this.isTopSheet = isTop;
2369
- if (this.backdrop) {
2370
- this.backdrop.style.display = isTop ? "" : "none";
2371
- }
2372
- }
3057
+ },
3058
+ isOpen: () => this.size > 0 || this.opening,
3059
+ setDepth: (depth) => this.applyStackDepth(depth)
2373
3060
  }));
2374
3061
  }
3062
+ applyStackDepth(depth) {
3063
+ if (!this.stackEffectEnabled) return;
3064
+ this.element.setAttribute("data-stack-depth", String(depth));
3065
+ if (!this.stackEffectPrimed) {
3066
+ this.stackEffectPrimed = true;
3067
+ const isVertical = this.mode === "bottom" || this.mode === "top";
3068
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
3069
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
3070
+ }
3071
+ const scale = Math.max(1 - depth * 0.04, 0.86);
3072
+ this.element.style.scale = scale === 1 ? "" : String(scale);
3073
+ }
2375
3074
  attach() {
2376
3075
  this.gesture = new GestureController({
2377
3076
  handle: this.handle,
@@ -2388,6 +3087,7 @@ var BottomSheetEngine = class {
2388
3087
  buf.rubberBandEnabled = this.rubberBandEnabled;
2389
3088
  return buf;
2390
3089
  },
3090
+ getDisableDrag: () => this.disableDragFlag,
2391
3091
  cancelAnimation: () => this.animation.cancel(),
2392
3092
  applySize: (size) => this.applySize(size),
2393
3093
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2431,7 +3131,7 @@ var BottomSheetEngine = class {
2431
3131
  container: this.scrollContainer,
2432
3132
  isDragging: () => this.gesture?.isDragging ?? false,
2433
3133
  isAnimating: () => this.animation.isAnimating,
2434
- getAllowedIds: () => this.snaps.getAllowedIds(),
3134
+ getAllowedIds: () => this.allowedIdsBySize(),
2435
3135
  getActiveId: () => this.activeId,
2436
3136
  snapTo: (id) => {
2437
3137
  void this.snapTo(id);
@@ -2460,13 +3160,16 @@ var BottomSheetEngine = class {
2460
3160
  },
2461
3161
  newCycle: () => {
2462
3162
  this.newCycle();
2463
- }
3163
+ },
3164
+ isAnimating: () => this.animation.isAnimating,
3165
+ resyncAfterCancel: () => this.resyncAfterResize()
2464
3166
  }));
2465
3167
  if (this.lifecycle.closeOnEscape) {
2466
3168
  const onKey = (e) => {
2467
- if (this.destroyed) return;
3169
+ if (this.destroyed || e.defaultPrevented) return;
3170
+ if (!this.canDismiss()) return;
2468
3171
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2469
- void this.close();
3172
+ void this.close("escape");
2470
3173
  }
2471
3174
  };
2472
3175
  document.addEventListener("keydown", onKey);
@@ -2496,7 +3199,7 @@ var BottomSheetEngine = class {
2496
3199
  handle: this.handle,
2497
3200
  mode: this.mode,
2498
3201
  isDestroyed: () => this.destroyed,
2499
- getAllowedIds: () => this.snaps.getAllowedIds(),
3202
+ getAllowedIds: () => this.allowedIdsBySize(),
2500
3203
  getActiveId: () => this.activeId,
2501
3204
  snapTo: (id) => {
2502
3205
  void this.snapTo(id);
@@ -2517,6 +3220,7 @@ var BottomSheetEngine = class {
2517
3220
  });
2518
3221
  if (!target) return;
2519
3222
  const previousId = this.activeId;
3223
+ if (target.id === previousId && target.size === this.size) return;
2520
3224
  if (this.emitBeforeSnap(target, previousId)) {
2521
3225
  const restore = this.snaps.findById(previousId);
2522
3226
  if (restore) {
@@ -2525,21 +3229,34 @@ var BottomSheetEngine = class {
2525
3229
  return;
2526
3230
  }
2527
3231
  const previousSize = this.size;
3232
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2528
3233
  this.scrollCache.cache(previousId, previousSize, target.size);
2529
3234
  this.activeId = target.id;
3235
+ if (previousSnapSize === 0 && target.size > 0) {
3236
+ this.opening = true;
3237
+ sheetStack.promote(this.id);
3238
+ }
2530
3239
  void this.animation.animateTo(target.size, velocity).then(() => {
3240
+ this.opening = false;
2531
3241
  if (signal.aborted) return;
2532
3242
  this.scrollCache.restore(target.id, previousSize, target.size);
2533
3243
  this.updateAriaSlider();
2534
- this.emit("snap", { id: target.id, size: target.size });
3244
+ this.emit("snap", {
3245
+ id: target.id,
3246
+ size: target.size,
3247
+ progress: this.computeProgress(target.size)
3248
+ });
2535
3249
  this.haptic();
2536
- if (previousId === "closed" && target.size > 0) {
3250
+ if (previousSnapSize === 0 && target.size > 0) {
2537
3251
  this.emit("open", { id: target.id });
2538
3252
  this.handleOpen();
3253
+ notifyLinkedSheets(this.linkedSheets, this);
3254
+ this.emit("opened", { id: target.id });
2539
3255
  }
2540
3256
  if (target.size === 0) {
2541
3257
  this.emit("close", void 0);
2542
3258
  this.handleClose();
3259
+ this.emit("closed", void 0);
2543
3260
  }
2544
3261
  });
2545
3262
  }
@@ -2561,6 +3278,9 @@ var BottomSheetEngine = class {
2561
3278
  if (this.scrimParent) {
2562
3279
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2563
3280
  }
3281
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3282
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3283
+ }
2564
3284
  }
2565
3285
  const progress = this.computeProgress(size);
2566
3286
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2569,6 +3289,9 @@ var BottomSheetEngine = class {
2569
3289
  );
2570
3290
  if (progressChanged) {
2571
3291
  style.setProperty("--bs-progress", String(progress));
3292
+ if (this.anchorHost) {
3293
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3294
+ }
2572
3295
  }
2573
3296
  this.scrim.applyOpacity(progress, progressChanged);
2574
3297
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2583,6 +3306,7 @@ var BottomSheetEngine = class {
2583
3306
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2584
3307
  }
2585
3308
  handleOpen() {
3309
+ sheetStack.promote(this.id);
2586
3310
  try {
2587
3311
  this.lifecycle.install();
2588
3312
  } catch (err) {
@@ -2596,8 +3320,31 @@ var BottomSheetEngine = class {
2596
3320
  });
2597
3321
  }
2598
3322
  }
3323
+ resyncAfterResize() {
3324
+ if (this.destroyed) return;
3325
+ const target = this.snaps.findById(this.activeId);
3326
+ if (!target) return;
3327
+ this.updateAriaSlider();
3328
+ this.emit("snap", {
3329
+ id: target.id,
3330
+ size: target.size,
3331
+ progress: this.computeProgress(target.size)
3332
+ });
3333
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3334
+ this.emit("open", { id: target.id });
3335
+ this.handleOpen();
3336
+ notifyLinkedSheets(this.linkedSheets, this);
3337
+ this.emit("opened", { id: target.id });
3338
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3339
+ this.emit("close", void 0);
3340
+ this.handleClose();
3341
+ this.emit("closed", void 0);
3342
+ }
3343
+ }
2599
3344
  handleClose() {
3345
+ this.opening = false;
2600
3346
  this.lifecycle.release();
3347
+ sheetStack.update();
2601
3348
  }
2602
3349
  };
2603
3350
 
@@ -2645,8 +3392,23 @@ function useBottomSheet(opts) {
2645
3392
  refresh();
2646
3393
  optsRef.current.onSnap?.(payload.id);
2647
3394
  }),
2648
- engine.on("dragstart", refresh),
2649
- engine.on("dragend", refresh)
3395
+ engine.on("before-close", (payload) => {
3396
+ optsRef.current.onBeforeClose?.(payload);
3397
+ }),
3398
+ engine.on("opened", (payload) => {
3399
+ optsRef.current.onOpened?.(payload.id);
3400
+ }),
3401
+ engine.on("closed", () => {
3402
+ optsRef.current.onClosed?.();
3403
+ }),
3404
+ engine.on("dragstart", (payload) => {
3405
+ refresh();
3406
+ optsRef.current.onDragStart?.(payload);
3407
+ }),
3408
+ engine.on("dragend", (payload) => {
3409
+ refresh();
3410
+ optsRef.current.onDragEnd?.(payload);
3411
+ })
2650
3412
  ];
2651
3413
  return () => {
2652
3414
  offs.forEach((off) => off());
@@ -2684,13 +3446,34 @@ function useBottomSheet(opts) {
2684
3446
  []
2685
3447
  );
2686
3448
  const close = react.useCallback(
2687
- () => engineRef.current?.close() ?? Promise.resolve(),
3449
+ (reason) => engineRef.current?.close(reason) ?? Promise.resolve(),
2688
3450
  []
2689
3451
  );
3452
+ const expand = react.useCallback(
3453
+ () => engineRef.current?.expand() ?? Promise.resolve(),
3454
+ []
3455
+ );
3456
+ const collapse = react.useCallback(
3457
+ () => engineRef.current?.collapse() ?? Promise.resolve(),
3458
+ []
3459
+ );
3460
+ const isTop = react.useCallback(() => engineRef.current?.isTop() ?? false, []);
3461
+ const depth = react.useCallback(() => engineRef.current?.depth() ?? 0, []);
2690
3462
  const setAllowed = react.useCallback((ids, snap) => {
2691
3463
  engineRef.current?.setAllowed(ids, snap);
2692
3464
  }, []);
2693
3465
  const getEngine = react.useCallback(() => engineRef.current, []);
3466
+ const addAnchor = react.useCallback(
3467
+ (anchorOpts) => engineRef.current?.addAnchor(anchorOpts) ?? (() => {
3468
+ }),
3469
+ []
3470
+ );
3471
+ const setScrimStages = react.useCallback(
3472
+ (stagesOpts) => engineRef.current?.setScrimStages(stagesOpts) ?? (() => {
3473
+ }),
3474
+ []
3475
+ );
3476
+ const recompute = react.useCallback(() => engineRef.current?.recompute(), []);
2694
3477
  return react.useMemo(
2695
3478
  () => ({
2696
3479
  sheetRef,
@@ -2702,11 +3485,32 @@ function useBottomSheet(opts) {
2702
3485
  snapTo,
2703
3486
  open,
2704
3487
  close,
3488
+ expand,
3489
+ collapse,
3490
+ isTop,
3491
+ depth,
2705
3492
  setAllowed,
3493
+ addAnchor,
3494
+ setScrimStages,
3495
+ recompute,
2706
3496
  engine: engineRef.current,
2707
3497
  getEngine
2708
3498
  }),
2709
- [state, snapTo, open, close, setAllowed, getEngine]
3499
+ [
3500
+ state,
3501
+ snapTo,
3502
+ open,
3503
+ close,
3504
+ expand,
3505
+ collapse,
3506
+ isTop,
3507
+ depth,
3508
+ setAllowed,
3509
+ addAnchor,
3510
+ setScrimStages,
3511
+ recompute,
3512
+ getEngine
3513
+ ]
2710
3514
  );
2711
3515
  }
2712
3516
  var useHasMounted = () => {
@@ -2726,7 +3530,14 @@ var BottomSheet = react.forwardRef(
2726
3530
  leftButton,
2727
3531
  rightButton,
2728
3532
  screen,
3533
+ anchors,
3534
+ scrimStages,
2729
3535
  onChange,
3536
+ onBeforeClose,
3537
+ onOpened,
3538
+ onClosed,
3539
+ onDragStart,
3540
+ onDragEnd,
2730
3541
  ariaLabel = "Bottom sheet",
2731
3542
  ariaLabelledBy,
2732
3543
  noSSR = false,
@@ -2743,13 +3554,21 @@ var BottomSheet = react.forwardRef(
2743
3554
  snapTo,
2744
3555
  open,
2745
3556
  close,
3557
+ expand,
3558
+ collapse,
3559
+ isTop,
3560
+ depth,
2746
3561
  setAllowed,
2747
3562
  getEngine
2748
- } = useBottomSheet(engineOpts);
2749
- const isVerticalAxis = (engineOpts.mode ?? "bottom") === "bottom" || (engineOpts.mode ?? "bottom") === "top";
3563
+ } = useBottomSheet({
3564
+ ...engineOpts,
3565
+ onBeforeClose,
3566
+ onOpened,
3567
+ onClosed,
3568
+ onDragStart,
3569
+ onDragEnd
3570
+ });
2750
3571
  const ariaModal = engineOpts.focusTrap === true;
2751
- const allowedIds = engineOpts.allowed ?? engineOpts.snapPoints.map((p) => p.id);
2752
- const activeIdx = allowedIds.indexOf(state.activeId);
2753
3572
  const lastAnnouncedRef = react.useRef("");
2754
3573
  const [announce, setAnnounce] = react.useState("");
2755
3574
  react.useEffect(() => {
@@ -2764,21 +3583,96 @@ var BottomSheet = react.forwardRef(
2764
3583
  snapTo,
2765
3584
  open,
2766
3585
  close,
3586
+ expand,
3587
+ collapse,
3588
+ isTop,
3589
+ depth,
2767
3590
  setAllowed,
2768
3591
  setSnapPoints: (points, allowed) => getEngine()?.setSnapPoints(points, allowed),
2769
3592
  setScrim: (opts) => getEngine()?.setScrim(opts),
2770
3593
  setScrimOverlay: (opts) => getEngine()?.setScrimOverlay(opts) ?? (() => {
2771
3594
  }),
3595
+ addAnchor: (opts) => getEngine()?.addAnchor(opts) ?? (() => {
3596
+ }),
3597
+ setScrimStages: (opts) => getEngine()?.setScrimStages(opts) ?? (() => {
3598
+ }),
3599
+ recompute: () => getEngine()?.recompute(),
2772
3600
  getEngine,
2773
3601
  get state() {
2774
3602
  return getEngine()?.state ?? state;
2775
3603
  }
2776
3604
  }),
2777
- [snapTo, open, close, setAllowed, getEngine, state]
3605
+ [
3606
+ snapTo,
3607
+ open,
3608
+ close,
3609
+ expand,
3610
+ collapse,
3611
+ isTop,
3612
+ depth,
3613
+ setAllowed,
3614
+ getEngine,
3615
+ state
3616
+ ]
2778
3617
  );
2779
3618
  react.useEffect(() => {
2780
3619
  onChange?.(state);
2781
3620
  }, [state, onChange]);
3621
+ const [anchorHosts, setAnchorHosts] = react.useState([]);
3622
+ const anchorsKey = anchors ? JSON.stringify(
3623
+ anchors.map((a) => [
3624
+ a.position,
3625
+ a.inset,
3626
+ Array.isArray(a.showOn) ? a.showOn : a.showOn ? "fn" : null,
3627
+ a.fadeRange,
3628
+ a.interactive,
3629
+ typeof a.animation === "string" ? a.animation : a.animation ? "custom" : null
3630
+ ])
3631
+ ) : "";
3632
+ react.useEffect(() => {
3633
+ const engine = getEngine();
3634
+ if (!engine || !anchors || anchors.length === 0) return;
3635
+ const hosts = [];
3636
+ const detachers = anchors.map((a) => {
3637
+ const host = document.createElement("div");
3638
+ hosts.push(host);
3639
+ const { node: _node, ...rest } = a;
3640
+ return engine.addAnchor({ ...rest, element: host });
3641
+ });
3642
+ setAnchorHosts(hosts);
3643
+ return () => {
3644
+ detachers.forEach((d) => d());
3645
+ setAnchorHosts([]);
3646
+ };
3647
+ }, [getEngine, anchorsKey]);
3648
+ const [stageHosts, setStageHosts] = react.useState([]);
3649
+ const stagesKey = scrimStages ? JSON.stringify(
3650
+ scrimStages.stages.map((s) => [
3651
+ s.for,
3652
+ s.forRange,
3653
+ s.position,
3654
+ s.inset,
3655
+ s.interactive,
3656
+ typeof s.animation === "string" ? s.animation : s.animation ? "custom" : null
3657
+ ])
3658
+ ) : "";
3659
+ react.useEffect(() => {
3660
+ const engine = getEngine();
3661
+ if (!engine || !scrimStages || scrimStages.stages.length === 0) return;
3662
+ const hosts = [];
3663
+ const defs = scrimStages.stages.map((s) => {
3664
+ const host = document.createElement("div");
3665
+ hosts.push(host);
3666
+ const { node: _node, ...rest } = s;
3667
+ return { ...rest, element: host };
3668
+ });
3669
+ const detach = engine.setScrimStages({ ...scrimStages, stages: defs });
3670
+ setStageHosts(hosts);
3671
+ return () => {
3672
+ detach();
3673
+ setStageHosts([]);
3674
+ };
3675
+ }, [getEngine, stagesKey]);
2782
3676
  if (noSSR && !mounted) return null;
2783
3677
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bs-root", children: [
2784
3678
  backdrop && /* @__PURE__ */ jsxRuntime.jsx(
@@ -2786,7 +3680,9 @@ var BottomSheet = react.forwardRef(
2786
3680
  {
2787
3681
  ref: backdropRef,
2788
3682
  className: "bs-backdrop",
2789
- onClick: closeOnBackdrop ? () => void close() : void 0
3683
+ onClick: closeOnBackdrop ? () => {
3684
+ if (getEngine()?.canDismiss()) void close("backdrop");
3685
+ } : void 0
2790
3686
  }
2791
3687
  ),
2792
3688
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2819,11 +3715,6 @@ var BottomSheet = react.forwardRef(
2819
3715
  className: "bs-handle",
2820
3716
  role: "slider",
2821
3717
  tabIndex: 0,
2822
- "aria-orientation": isVerticalAxis ? "vertical" : "horizontal",
2823
- "aria-valuemin": 0,
2824
- "aria-valuemax": Math.max(0, allowedIds.length - 1),
2825
- "aria-valuenow": Math.max(0, activeIdx),
2826
- "aria-valuetext": state.activeId,
2827
3718
  "aria-label": "Resize sheet",
2828
3719
  children: typeof header === "function" ? header(state) : header
2829
3720
  }
@@ -2842,6 +3733,12 @@ var BottomSheet = react.forwardRef(
2842
3733
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bs-sr-only", role: "status", "aria-live": "polite", children: announce })
2843
3734
  ]
2844
3735
  }
3736
+ ),
3737
+ anchorHosts.map(
3738
+ (host, i) => anchors?.[i] ? reactDom.createPortal(anchors[i].node, host) : null
3739
+ ),
3740
+ stageHosts.map(
3741
+ (host, i) => scrimStages?.stages[i] ? reactDom.createPortal(scrimStages.stages[i].node, host) : null
2845
3742
  )
2846
3743
  ] });
2847
3744
  }
@@ -2926,6 +3823,10 @@ var OverlayEngine = class {
2926
3823
  __publicField(this, "closeOnOutsidePointerEnabled");
2927
3824
  __publicField(this, "detachOutsidePointer", null);
2928
3825
  __publicField(this, "detachSwipe", null);
3826
+ __publicField(this, "respectReducedMotionEnabled");
3827
+ __publicField(this, "isTop", true);
3828
+ __publicField(this, "backPushed", false);
3829
+ __publicField(this, "movedToTarget", false);
2929
3830
  __publicField(this, "onOpenCb");
2930
3831
  __publicField(this, "onCloseCb");
2931
3832
  __publicField(this, "isOpen_", false);
@@ -2962,6 +3863,7 @@ var OverlayEngine = class {
2962
3863
  this.mountTarget = opts.mountTo;
2963
3864
  this.peekRaw = opts.peek;
2964
3865
  this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
3866
+ this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
2965
3867
  this.onOpenCb = opts.onOpen;
2966
3868
  this.onCloseCb = opts.onClose;
2967
3869
  if (this.peekRaw !== void 0 && this.edge !== "bottom") {
@@ -3009,15 +3911,17 @@ var OverlayEngine = class {
3009
3911
  this.emit("before-open", void 0);
3010
3912
  this.isOpen_ = true;
3011
3913
  this.isAnimating_ = true;
3914
+ sheetStack.promote(this.id);
3012
3915
  this.element.removeAttribute("hidden");
3013
3916
  this.element.setAttribute("data-state", "entering");
3014
3917
  if (this.focusTrapEnabled) {
3015
3918
  this.element.setAttribute("aria-modal", "true");
3016
3919
  }
3017
- const transitionStr = `transform ${this.duration}ms ${this.enterEasing}, opacity ${this.duration}ms ease-out`;
3920
+ const dur = this.effectiveDuration();
3921
+ const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
3018
3922
  this.element.style.transition = transitionStr;
3019
3923
  if (this.backdrop) {
3020
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-out`;
3924
+ this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
3021
3925
  }
3022
3926
  const cycle = ++this.cycleNonce;
3023
3927
  return new Promise((resolve) => {
@@ -3029,7 +3933,7 @@ var OverlayEngine = class {
3029
3933
  this.element.style.opacity = "1";
3030
3934
  if (this.backdrop) {
3031
3935
  this.backdrop.style.opacity = String(this.backdropOpacity);
3032
- this.backdrop.style.pointerEvents = "auto";
3936
+ this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
3033
3937
  if (this.backdropFilter) {
3034
3938
  this.backdrop.style.backdropFilter = this.backdropFilter;
3035
3939
  this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
@@ -3065,7 +3969,10 @@ var OverlayEngine = class {
3065
3969
  });
3066
3970
  return resolve();
3067
3971
  }
3068
- this.awaitTransition(cycle).then(() => {
3972
+ this.awaitTransition(
3973
+ cycle,
3974
+ this.enterAnimation === "fade" ? "opacity" : "transform"
3975
+ ).then(() => {
3069
3976
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
3070
3977
  this.isAnimating_ = false;
3071
3978
  this.element.setAttribute("data-state", "open");
@@ -3082,11 +3989,14 @@ var OverlayEngine = class {
3082
3989
  this.emit("before-close", void 0);
3083
3990
  this.isOpen_ = false;
3084
3991
  this.isAnimating_ = true;
3992
+ sheetStack.update();
3993
+ this.popBackMarker(reason);
3085
3994
  this.element.setAttribute("data-state", "exiting");
3086
- const transitionStr = `transform ${this.duration}ms ${this.exitEasing}, opacity ${this.duration}ms ease-in`;
3995
+ const dur = this.effectiveDuration();
3996
+ const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
3087
3997
  this.element.style.transition = transitionStr;
3088
3998
  if (this.backdrop) {
3089
- this.backdrop.style.transition = `opacity ${this.duration}ms ease-in`;
3999
+ this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
3090
4000
  }
3091
4001
  const cycle = ++this.cycleNonce;
3092
4002
  return new Promise((resolve) => {
@@ -3097,7 +4007,7 @@ var OverlayEngine = class {
3097
4007
  this.exitAnimation,
3098
4008
  this.peekPx
3099
4009
  );
3100
- this.element.style.opacity = "0";
4010
+ this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
3101
4011
  if (this.backdrop) {
3102
4012
  this.backdrop.style.opacity = "0";
3103
4013
  this.backdrop.style.pointerEvents = "none";
@@ -3107,7 +4017,10 @@ var OverlayEngine = class {
3107
4017
  }
3108
4018
  }
3109
4019
  this.releaseInteractiveListeners();
3110
- this.awaitTransition(cycle).then(() => {
4020
+ this.awaitTransition(
4021
+ cycle,
4022
+ this.exitAnimation === "fade" ? "opacity" : "transform"
4023
+ ).then(() => {
3111
4024
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
3112
4025
  this.isAnimating_ = false;
3113
4026
  if (this.peekPx === void 0) {
@@ -3135,7 +4048,11 @@ var OverlayEngine = class {
3135
4048
  this.releaseInteractiveListeners();
3136
4049
  this.releaseStackEntry?.();
3137
4050
  this.releaseStackEntry = null;
4051
+ const hadOriginalParent = this.originalParent !== null;
3138
4052
  this.restoreMount();
4053
+ if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
4054
+ this.element.parentNode.removeChild(this.element);
4055
+ }
3139
4056
  }
3140
4057
  setBackdropOpacity(opacity) {
3141
4058
  if (this.destroyed) return;
@@ -3236,6 +4153,36 @@ var OverlayEngine = class {
3236
4153
  }
3237
4154
  }
3238
4155
  }
4156
+ if (opts.duration !== void 0) this.duration = opts.duration;
4157
+ if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
4158
+ if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
4159
+ if (opts.closeOnEscape !== void 0) {
4160
+ this.closeOnEscape = opts.closeOnEscape;
4161
+ if (!opts.closeOnEscape) {
4162
+ this.detachEscape?.();
4163
+ this.detachEscape = null;
4164
+ } else if (this.isOpen_) {
4165
+ this.installInteractiveListeners();
4166
+ }
4167
+ }
4168
+ if (opts.closeOnBackdrop !== void 0) {
4169
+ this.closeOnBackdropEnabled = opts.closeOnBackdrop;
4170
+ if (!opts.closeOnBackdrop) {
4171
+ this.detachBackdrop?.();
4172
+ this.detachBackdrop = null;
4173
+ } else if (this.isOpen_) {
4174
+ this.installInteractiveListeners();
4175
+ }
4176
+ }
4177
+ if (opts.closeOnOutsidePointer !== void 0) {
4178
+ this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
4179
+ if (!opts.closeOnOutsidePointer) {
4180
+ this.detachOutsidePointer?.();
4181
+ this.detachOutsidePointer = null;
4182
+ } else if (this.isOpen_) {
4183
+ this.installInteractiveListeners();
4184
+ }
4185
+ }
3239
4186
  if (opts.children !== void 0) {
3240
4187
  if (opts.children === null) {
3241
4188
  this.clearOverlayChildren();
@@ -3269,6 +4216,22 @@ var OverlayEngine = class {
3269
4216
  emit(event, payload) {
3270
4217
  this.bus.emit(event, payload);
3271
4218
  }
4219
+ effectiveDuration() {
4220
+ return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
4221
+ }
4222
+ popBackMarker(reason) {
4223
+ if (!this.backPushed) return;
4224
+ this.backPushed = false;
4225
+ if (reason === "back") return;
4226
+ const state = history.state;
4227
+ if (state && state.__bsOverlay === this.id) {
4228
+ try {
4229
+ history.back();
4230
+ } catch {
4231
+ return;
4232
+ }
4233
+ }
4234
+ }
3272
4235
  resolvePeekPx(raw) {
3273
4236
  if (typeof raw === "number") return Math.max(0, raw);
3274
4237
  try {
@@ -3290,6 +4253,7 @@ var OverlayEngine = class {
3290
4253
  this.originalParent = this.element.parentNode;
3291
4254
  this.originalNextSibling = this.element.nextSibling;
3292
4255
  target.appendChild(this.element);
4256
+ this.movedToTarget = true;
3293
4257
  this.mounted = true;
3294
4258
  }
3295
4259
  restoreMount() {
@@ -3323,7 +4287,7 @@ var OverlayEngine = class {
3323
4287
  this.backdrop.style.willChange = "opacity";
3324
4288
  }
3325
4289
  }
3326
- awaitTransition(cycle) {
4290
+ awaitTransition(cycle, property = "transform") {
3327
4291
  return new Promise((resolve) => {
3328
4292
  let done = false;
3329
4293
  const finish = () => {
@@ -3338,10 +4302,10 @@ var OverlayEngine = class {
3338
4302
  finish();
3339
4303
  return;
3340
4304
  }
3341
- if (e.propertyName === "transform") finish();
4305
+ if (e.target === this.element && e.propertyName === property) finish();
3342
4306
  };
3343
4307
  this.element.addEventListener("transitionend", onEnd);
3344
- const timer = window.setTimeout(finish, this.duration + 50);
4308
+ const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
3345
4309
  });
3346
4310
  }
3347
4311
  installInteractiveListeners() {
@@ -3351,7 +4315,10 @@ var OverlayEngine = class {
3351
4315
  }
3352
4316
  if (this.closeOnEscape && !this.detachEscape) {
3353
4317
  const handler = (e) => {
3354
- if (e.key === "Escape" && this.isOpen_) void this.close("escape");
4318
+ if (e.defaultPrevented) return;
4319
+ if (e.key === "Escape" && this.isOpen_ && this.isTop) {
4320
+ void this.close("escape");
4321
+ }
3355
4322
  };
3356
4323
  document.addEventListener("keydown", handler);
3357
4324
  this.detachEscape = () => document.removeEventListener("keydown", handler);
@@ -3364,10 +4331,14 @@ var OverlayEngine = class {
3364
4331
  if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
3365
4332
  try {
3366
4333
  history.pushState({ __bsOverlay: this.id }, "");
4334
+ this.backPushed = true;
3367
4335
  } catch {
3368
4336
  }
3369
4337
  const onPop = () => {
3370
- if (this.isOpen_) void this.close("back");
4338
+ if (this.isOpen_ && this.isTop) {
4339
+ this.backPushed = false;
4340
+ void this.close("back");
4341
+ }
3371
4342
  };
3372
4343
  window.addEventListener("popstate", onPop);
3373
4344
  this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
@@ -3403,43 +4374,52 @@ var OverlayEngine = class {
3403
4374
  const edge = this.edge;
3404
4375
  const isVertical = edge === "bottom" || edge === "top";
3405
4376
  const sign = edge === "bottom" || edge === "right" ? 1 : -1;
3406
- const threshold = this.swipeThreshold;
3407
- const velocityThreshold = this.swipeVelocityThreshold;
4377
+ const SLOP = 8;
3408
4378
  let startX = 0;
3409
4379
  let startY = 0;
3410
4380
  let startT = 0;
4381
+ let tracking = false;
3411
4382
  let dragging = false;
3412
4383
  let pointerId = null;
4384
+ const previousTouchAction = el.style.touchAction;
4385
+ el.style.touchAction = isVertical ? "pan-x" : "pan-y";
3413
4386
  const onDown = (e) => {
3414
4387
  if (e.button !== void 0 && e.button !== 0) return;
3415
4388
  pointerId = e.pointerId;
3416
4389
  startX = e.clientX;
3417
4390
  startY = e.clientY;
3418
4391
  startT = performance.now();
3419
- dragging = true;
3420
- el.style.transition = "none";
3421
- try {
3422
- el.setPointerCapture(e.pointerId);
3423
- } catch {
3424
- }
4392
+ tracking = true;
4393
+ dragging = false;
3425
4394
  };
3426
4395
  const onMove = (e) => {
3427
- if (!dragging || e.pointerId !== pointerId) return;
4396
+ if (!tracking || e.pointerId !== pointerId) return;
3428
4397
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
4398
+ if (!dragging) {
4399
+ if (Math.abs(delta) < SLOP) return;
4400
+ dragging = true;
4401
+ el.style.transition = "none";
4402
+ try {
4403
+ el.setPointerCapture(e.pointerId);
4404
+ } catch {
4405
+ }
4406
+ }
3429
4407
  const offset = Math.max(0, sign * delta);
3430
4408
  el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
3431
4409
  };
3432
4410
  const finish = (e) => {
3433
- if (!dragging || e.pointerId !== pointerId) return;
3434
- dragging = false;
4411
+ if (!tracking || e.pointerId !== pointerId) return;
4412
+ tracking = false;
3435
4413
  pointerId = null;
4414
+ if (!dragging) return;
4415
+ dragging = false;
3436
4416
  const dt = performance.now() - startT || 1;
3437
4417
  const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3438
4418
  const offset = Math.max(0, sign * delta);
3439
4419
  const velocity = sign * delta / dt;
3440
4420
  const size = isVertical ? el.offsetHeight : el.offsetWidth;
3441
- const past = offset > size * threshold || velocity > velocityThreshold;
3442
- el.style.transition = `transform ${this.duration}ms ${this.exitEasing}`;
4421
+ const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
4422
+ el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
3443
4423
  if (past) {
3444
4424
  void this.close("swipe");
3445
4425
  } else {
@@ -3451,6 +4431,7 @@ var OverlayEngine = class {
3451
4431
  el.addEventListener("pointerup", finish);
3452
4432
  el.addEventListener("pointercancel", finish);
3453
4433
  return () => {
4434
+ el.style.touchAction = previousTouchAction;
3454
4435
  el.removeEventListener("pointerdown", onDown);
3455
4436
  el.removeEventListener("pointermove", onMove);
3456
4437
  el.removeEventListener("pointerup", finish);
@@ -3465,10 +4446,12 @@ var OverlayEngine = class {
3465
4446
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3466
4447
  },
3467
4448
  setIsTop: (isTop) => {
4449
+ this.isTop = isTop;
3468
4450
  if (this.backdrop) {
3469
4451
  this.backdrop.style.display = isTop ? "" : "none";
3470
4452
  }
3471
- }
4453
+ },
4454
+ isOpen: () => this.isOpen_
3472
4455
  });
3473
4456
  }
3474
4457
  };
@@ -3554,7 +4537,7 @@ var Overlay = react.forwardRef(function Overlay2(props, ref) {
3554
4537
  if (openProp === void 0) return;
3555
4538
  if (openProp && !state.isOpen) void open();
3556
4539
  else if (!openProp && state.isOpen) void close();
3557
- }, [openProp]);
4540
+ }, [openProp, state.isOpen]);
3558
4541
  react.useEffect(() => {
3559
4542
  onChange?.(state);
3560
4543
  }, [state, onChange]);
@@ -3686,46 +4669,34 @@ var BottomSheetFromConfig = react.forwardRef(function BottomSheetFromConfig2(pro
3686
4669
  () => sheetRef.current,
3687
4670
  []
3688
4671
  );
3689
- const lastActiveIdRef = react.useRef(null);
3690
- const handleChange = (state) => {
3691
- const prev = lastActiveIdRef.current;
3692
- const next = state.activeId;
3693
- lastActiveIdRef.current = next;
3694
- const evs = eventsRef.current;
3695
- const hs = handlersRef.current;
3696
- if (evs.onSnap) {
3697
- const fn = hs[evs.onSnap];
3698
- if (typeof fn === "function") {
3699
- fn(next);
3700
- } else {
3701
- console.warn(
3702
- `[BottomSheetFromConfig] eventHandlers["${evs.onSnap}"] is not a function; onSnap is a no-op.`
3703
- );
3704
- }
3705
- }
3706
- const isClosedNext = next === "" || next === "closed";
3707
- const isClosedPrev = prev === null || prev === "" || prev === "closed";
3708
- if (isClosedPrev && !isClosedNext && evs.onOpen) {
3709
- const fn = hs[evs.onOpen];
3710
- if (typeof fn === "function") {
3711
- fn(next);
3712
- } else {
3713
- console.warn(
3714
- `[BottomSheetFromConfig] eventHandlers["${evs.onOpen}"] is not a function; onOpen is a no-op.`
3715
- );
3716
- }
3717
- }
3718
- if (!isClosedPrev && isClosedNext && evs.onClose) {
3719
- const fn = hs[evs.onClose];
3720
- if (typeof fn === "function") {
4672
+ const invoke = (key, arg) => {
4673
+ const evName = eventsRef.current[key];
4674
+ if (!evName) return;
4675
+ const fn = handlersRef.current[evName];
4676
+ if (typeof fn === "function") {
4677
+ if (arg === void 0) {
3721
4678
  fn();
3722
4679
  } else {
3723
- console.warn(
3724
- `[BottomSheetFromConfig] eventHandlers["${evs.onClose}"] is not a function; onClose is a no-op.`
3725
- );
4680
+ fn(arg);
3726
4681
  }
4682
+ } else {
4683
+ console.warn(
4684
+ `[BottomSheetFromConfig] eventHandlers["${evName}"] is not a function; ${key} is a no-op.`
4685
+ );
3727
4686
  }
3728
4687
  };
4688
+ const invokeRef = react.useRef(invoke);
4689
+ invokeRef.current = invoke;
4690
+ react.useEffect(() => {
4691
+ const engine = sheetRef.current?.getEngine();
4692
+ if (!engine) return;
4693
+ const offs = [
4694
+ engine.on("snap", (p) => invokeRef.current("onSnap", p.id)),
4695
+ engine.on("open", (p) => invokeRef.current("onOpen", p.id)),
4696
+ engine.on("close", () => invokeRef.current("onClose"))
4697
+ ];
4698
+ return () => offs.forEach((off) => off());
4699
+ }, []);
3729
4700
  return /* @__PURE__ */ jsxRuntime.jsx(
3730
4701
  BottomSheet,
3731
4702
  {
@@ -3741,7 +4712,6 @@ var BottomSheetFromConfig = react.forwardRef(function BottomSheetFromConfig2(pro
3741
4712
  closeOnBackdrop: derived.behavior.closeOnBackdrop,
3742
4713
  lockBodyScroll: derived.behavior.lockBodyScroll,
3743
4714
  rubberBand: derived.behavior.rubberBand,
3744
- onChange: handleChange,
3745
4715
  header: slotContent?.header,
3746
4716
  children: slotContent?.body
3747
4717
  }