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