@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/vue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ref, reactive, onMounted, onBeforeUnmount, openBlock, createElementBlock, createCommentVNode, createElementVNode, renderSlot, normalizeClass, toDisplayString, defineComponent, watch, computed, readonly } from 'vue';
1
+ import { ref, reactive, onMounted, onBeforeUnmount, openBlock, createBlock, Teleport, createElementVNode, createElementBlock, createCommentVNode, renderSlot, normalizeClass, toDisplayString, Fragment, renderList, defineComponent, watch, shallowRef, computed, readonly } from 'vue';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
4
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -22,16 +22,52 @@ var SheetStack = class {
22
22
  this.entries.splice(idx, 1);
23
23
  this.recompute();
24
24
  }
25
+ promote(id) {
26
+ const idx = this.entries.findIndex((e) => e.id === id);
27
+ if (idx === -1) return;
28
+ if (idx !== this.entries.length - 1) {
29
+ const [entry] = this.entries.splice(idx, 1);
30
+ this.entries.push(entry);
31
+ }
32
+ this.recompute();
33
+ }
34
+ update() {
35
+ this.recompute();
36
+ }
25
37
  size() {
26
38
  return this.entries.length;
27
39
  }
40
+ depthOf(id) {
41
+ const open = [];
42
+ for (const entry of this.entries) {
43
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
44
+ }
45
+ const idx = open.findIndex((e) => e.id === id);
46
+ if (idx === -1) return 0;
47
+ return open.length - 1 - idx;
48
+ }
28
49
  clear() {
29
50
  this.entries = [];
30
51
  }
31
52
  recompute() {
53
+ let top = null;
54
+ const open = [];
55
+ for (const entry of this.entries) {
56
+ if (!entry.isOpen || entry.isOpen()) {
57
+ top = entry;
58
+ open.push(entry);
59
+ }
60
+ }
61
+ if (!top && this.entries.length > 0) {
62
+ top = this.entries[this.entries.length - 1];
63
+ }
32
64
  this.entries.forEach((entry, i) => {
33
65
  entry.setZIndex(BASE_Z + i * STEP);
34
- entry.setIsTop(i === this.entries.length - 1);
66
+ entry.setIsTop(entry === top);
67
+ if (entry.setDepth) {
68
+ const openIdx = open.indexOf(entry);
69
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
70
+ }
35
71
  });
36
72
  }
37
73
  };
@@ -128,7 +164,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
128
164
  var resolveSnap = (point, mode, measureFit) => {
129
165
  if (typeof point === "number") return Math.max(0, point);
130
166
  if (point === "full") return viewportSize(mode);
131
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
167
+ if (point === "fit" || point === "content") {
168
+ return Math.max(0, measureFit?.() ?? 0);
169
+ }
132
170
  const pctMatch = point.match(PERCENT);
133
171
  if (pctMatch) {
134
172
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -316,14 +354,17 @@ function installAutoCollapse(deps) {
316
354
  const fire = () => {
317
355
  if (deps.isDestroyed()) return;
318
356
  if (deps.isDragging()) return;
319
- const allowed = deps.getAllowedIds();
320
- const target = allowed.find((id) => {
357
+ const active = deps.resolveSnap(deps.getActiveId());
358
+ if (!active || active.size === 0) return;
359
+ let target = null;
360
+ for (const id of deps.getAllowedIds()) {
321
361
  const snap = deps.resolveSnap(id);
322
- return snap !== null && snap.size > 0;
323
- });
362
+ if (!snap || snap.size <= 0) continue;
363
+ if (!target || snap.size < target.size) target = snap;
364
+ }
324
365
  if (!target) return;
325
- if (target === deps.getActiveId()) return;
326
- deps.snapTo(target);
366
+ if (target.id === active.id || target.size >= active.size) return;
367
+ deps.snapTo(target.id);
327
368
  };
328
369
  const reset = () => {
329
370
  clearTimer();
@@ -334,6 +375,7 @@ function installAutoCollapse(deps) {
334
375
  }, ms);
335
376
  };
336
377
  const unsubscribe = deps.on("snap", () => reset());
378
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
337
379
  return () => {
338
380
  clearTimer();
339
381
  unsubscribe();
@@ -346,6 +388,7 @@ function notifyLinkedSheets(sheets, self) {
346
388
  for (const linked of sheets) {
347
389
  if (linked === self) continue;
348
390
  try {
391
+ if (linked.state.size === 0) continue;
349
392
  const ids = linked.getAllowedIds();
350
393
  const target = ids.find((id) => id !== "closed");
351
394
  if (target && target !== linked.state.activeId) {
@@ -360,6 +403,42 @@ function notifyLinkedSheets(sheets, self) {
360
403
  }
361
404
 
362
405
  // src/core/features/route.ts
406
+ var closers = [];
407
+ var handlerInstalled = false;
408
+ var suppress = 0;
409
+ var ensureHandler = () => {
410
+ if (handlerInstalled || typeof window === "undefined") return;
411
+ handlerInstalled = true;
412
+ window.addEventListener("popstate", () => {
413
+ if (suppress > 0) {
414
+ suppress -= 1;
415
+ return;
416
+ }
417
+ let target = null;
418
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
419
+ const c = closers[i];
420
+ if (!c.isOpen()) continue;
421
+ if (!target) target = c;
422
+ if (c.isTop()) {
423
+ target = c;
424
+ break;
425
+ }
426
+ }
427
+ if (!target) return;
428
+ target.consumed = true;
429
+ const idx = closers.indexOf(target);
430
+ if (idx !== -1) closers.splice(idx, 1);
431
+ target.close();
432
+ });
433
+ };
434
+ var programmaticBack = () => {
435
+ suppress += 1;
436
+ try {
437
+ history.back();
438
+ } catch {
439
+ suppress = Math.max(0, suppress - 1);
440
+ }
441
+ };
363
442
  function installRoute(deps) {
364
443
  if (typeof window === "undefined") {
365
444
  return () => {
@@ -369,82 +448,81 @@ function installRoute(deps) {
369
448
  return () => {
370
449
  };
371
450
  }
372
- let routePushed = false;
373
- let closeOnBackPushed = false;
374
- let detachPopstate = null;
375
- const popOurMarker = (key, expected) => {
376
- const state = history.state;
377
- if (state && state[key] === expected) {
378
- try {
379
- history.back();
380
- } catch {
381
- }
451
+ let pushed = false;
452
+ let closer = null;
453
+ const teardownEntry = () => {
454
+ if (closer) {
455
+ const idx = closers.indexOf(closer);
456
+ if (idx !== -1) closers.splice(idx, 1);
457
+ }
458
+ const wasConsumed = closer?.consumed ?? false;
459
+ closer = null;
460
+ if (pushed && !wasConsumed) {
461
+ pushed = false;
462
+ programmaticBack();
463
+ } else {
464
+ pushed = false;
382
465
  }
383
466
  };
384
467
  const onOpen = () => {
385
- if (deps.routedTo && !detachPopstate) {
386
- const target = deps.routedTo;
387
- try {
388
- history.pushState({ __bsRouted: target }, "", target);
389
- routePushed = true;
390
- } catch {
391
- }
392
- const onPop = (event) => {
393
- if (deps.isDestroyed()) return;
394
- const state = event.state;
395
- if (state && state.__bsRouted === target && deps.getSize() > 0) {
396
- routePushed = false;
397
- void deps.close();
398
- }
399
- };
400
- window.addEventListener("popstate", onPop);
401
- detachPopstate = () => window.removeEventListener("popstate", onPop);
402
- } else if (deps.closeOnBack && !detachPopstate) {
403
- try {
468
+ if (closer) return;
469
+ closer = {
470
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
471
+ isTop: () => deps.isTopSheet(),
472
+ close: () => void deps.close(),
473
+ consumed: false
474
+ };
475
+ ensureHandler();
476
+ closers.push(closer);
477
+ try {
478
+ if (deps.routedTo !== void 0) {
479
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
480
+ } else {
404
481
  history.pushState({ __bsSheet: deps.sheetId }, "");
405
- closeOnBackPushed = true;
406
- } catch {
407
482
  }
408
- const onPop = () => {
409
- if (deps.isDestroyed()) return;
410
- if (deps.getSize() > 0 && deps.isTopSheet()) {
411
- const state = history.state;
412
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
413
- void deps.close();
414
- }
415
- };
416
- window.addEventListener("popstate", onPop);
417
- detachPopstate = () => window.removeEventListener("popstate", onPop);
418
- }
419
- };
420
- const onClose = () => {
421
- detachPopstate?.();
422
- detachPopstate = null;
423
- if (routePushed) {
424
- routePushed = false;
425
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
426
- }
427
- if (closeOnBackPushed) {
428
- closeOnBackPushed = false;
429
- popOurMarker("__bsSheet", deps.sheetId);
483
+ pushed = true;
484
+ } catch {
485
+ pushed = false;
430
486
  }
431
487
  };
432
488
  const unsubscribeOpen = deps.on("open", onOpen);
433
- const unsubscribeClose = deps.on("close", onClose);
489
+ const unsubscribeClose = deps.on("close", teardownEntry);
434
490
  if (deps.getSize() > 0) onOpen();
435
491
  return () => {
436
492
  unsubscribeOpen();
437
493
  unsubscribeClose();
438
- detachPopstate?.();
439
- detachPopstate = null;
440
- if (routePushed) {
441
- routePushed = false;
442
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
443
- }
444
- if (closeOnBackPushed) {
445
- closeOnBackPushed = false;
446
- popOurMarker("__bsSheet", deps.sheetId);
447
- }
494
+ teardownEntry();
495
+ };
496
+ }
497
+ function installRouteChange(deps) {
498
+ if (typeof window === "undefined" || typeof history === "undefined") {
499
+ return () => {
500
+ };
501
+ }
502
+ const onChange = () => {
503
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
504
+ void deps.close();
505
+ };
506
+ const wrap = (key) => {
507
+ const original = history[key];
508
+ const patched = function(...args) {
509
+ original.apply(this, args);
510
+ onChange();
511
+ };
512
+ history[key] = patched;
513
+ return () => {
514
+ if (history[key] === patched) {
515
+ history[key] = original;
516
+ }
517
+ };
518
+ };
519
+ window.addEventListener("popstate", onChange);
520
+ const restorePush = wrap("pushState");
521
+ const restoreReplace = wrap("replaceState");
522
+ return () => {
523
+ window.removeEventListener("popstate", onChange);
524
+ restorePush();
525
+ restoreReplace();
448
526
  };
449
527
  }
450
528
 
@@ -546,13 +624,17 @@ function installResizeObserver(deps) {
546
624
  if (viewportSize2 > 0 && viewportSize2 < max) {
547
625
  deps.setMaxAxisSize(viewportSize2);
548
626
  }
627
+ const wasAnimating = deps.isAnimating?.() ?? false;
549
628
  deps.cancelInFlight();
550
629
  deps.newCycle();
551
630
  const current = deps.resolveActiveSnap();
552
631
  const newMax = deps.getMaxAxisSize();
553
632
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
554
633
  deps.setSize(targetSize);
555
- if (current && !deps.isDragging()) deps.applySize(targetSize);
634
+ if (current && !deps.isDragging()) {
635
+ deps.applySize(targetSize);
636
+ if (wasAnimating) deps.resyncAfterCancel?.();
637
+ }
556
638
  };
557
639
  let resizeObserver = null;
558
640
  if (typeof ResizeObserver !== "undefined") {
@@ -667,8 +749,88 @@ function devWarn(...args) {
667
749
  }
668
750
  }
669
751
 
752
+ // src/core/primitives/overlay-position.ts
753
+ function resolveSheetAnchoredStyle(mode, position, inset) {
754
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
755
+ switch (mode) {
756
+ case "bottom":
757
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
758
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
759
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
760
+ case "top":
761
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
762
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
763
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
764
+ case "left":
765
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
766
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
767
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
768
+ case "right":
769
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
770
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
771
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
772
+ }
773
+ }
774
+ function applyOverlayPosition(ws, mode, position, inset) {
775
+ switch (position) {
776
+ case "top-left":
777
+ ws.top = inset;
778
+ ws.left = inset;
779
+ break;
780
+ case "top-center":
781
+ ws.top = inset;
782
+ ws.left = "50%";
783
+ ws.transform = "translateX(-50%)";
784
+ break;
785
+ case "top-right":
786
+ ws.top = inset;
787
+ ws.right = inset;
788
+ break;
789
+ case "center-left":
790
+ ws.top = "50%";
791
+ ws.left = inset;
792
+ ws.transform = "translateY(-50%)";
793
+ break;
794
+ case "center":
795
+ ws.top = "50%";
796
+ ws.left = "50%";
797
+ ws.transform = "translate(-50%, -50%)";
798
+ break;
799
+ case "center-right":
800
+ ws.top = "50%";
801
+ ws.right = inset;
802
+ ws.transform = "translateY(-50%)";
803
+ break;
804
+ case "bottom-left":
805
+ ws.bottom = inset;
806
+ ws.left = inset;
807
+ break;
808
+ case "bottom-center":
809
+ ws.bottom = inset;
810
+ ws.left = "50%";
811
+ ws.transform = "translateX(-50%)";
812
+ break;
813
+ case "bottom-right":
814
+ ws.bottom = inset;
815
+ ws.right = inset;
816
+ break;
817
+ case "sheet-top-left":
818
+ case "sheet-top-center":
819
+ case "sheet-top-right": {
820
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
821
+ if (style.top !== void 0) ws.top = style.top;
822
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
823
+ if (style.left !== void 0) ws.left = style.left;
824
+ if (style.right !== void 0) ws.right = style.right;
825
+ if (style.transform !== void 0) ws.transform = style.transform;
826
+ break;
827
+ }
828
+ }
829
+ }
830
+
670
831
  // src/core/controllers/scrim-controller.ts
671
832
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
833
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
672
834
  var ScrimController = class {
673
835
  constructor(deps, opts) {
674
836
  __publicField(this, "screenComponent");
@@ -679,6 +841,8 @@ var ScrimController = class {
679
841
  __publicField(this, "getAllowedIds");
680
842
  __publicField(this, "getResolvedSnaps");
681
843
  __publicField(this, "snapToFn");
844
+ __publicField(this, "closeFn");
845
+ __publicField(this, "scrimInteractive", false);
682
846
  __publicField(this, "scrimMode");
683
847
  __publicField(this, "backdropRange");
684
848
  __publicField(this, "screenRange");
@@ -699,14 +863,17 @@ var ScrimController = class {
699
863
  this.getAllowedIds = deps.getAllowedIds;
700
864
  this.getResolvedSnaps = deps.getResolvedSnaps;
701
865
  this.snapToFn = deps.snapTo;
866
+ this.closeFn = deps.close;
702
867
  this.scrimMode = opts.scrimMode ?? "full";
703
868
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
704
869
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
705
870
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
706
871
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
872
+ const explicitColor = opts.scrimColor ?? preset?.color;
873
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
707
874
  if (this.screenComponent) {
708
875
  this.applyScrimStyles({
709
- color: opts.scrimColor ?? preset?.color,
876
+ color: effectiveColor,
710
877
  blur: opts.scrimBlur ?? preset?.blur,
711
878
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
712
879
  });
@@ -840,11 +1007,15 @@ var ScrimController = class {
840
1007
  }
841
1008
  const onClick = () => {
842
1009
  if (this.isDestroyed()) return;
1010
+ if (this.closeFn) {
1011
+ this.closeFn();
1012
+ return;
1013
+ }
843
1014
  const allowed = this.getAllowedIds();
844
1015
  const snaps = this.getResolvedSnaps();
845
1016
  const fallback = allowed.find((id) => {
846
1017
  const snap = findById(id, snaps);
847
- return snap !== null && snap.size > 0;
1018
+ return snap !== null && snap.size === 0;
848
1019
  });
849
1020
  if (fallback) this.snapToFn(fallback);
850
1021
  };
@@ -861,6 +1032,7 @@ var ScrimController = class {
861
1032
  if (this.isDestroyed()) return;
862
1033
  if (enabled === this.scrimEnabled) return;
863
1034
  this.scrimEnabled = enabled;
1035
+ this.invalidateOpacityCache();
864
1036
  if (!enabled) {
865
1037
  this.savedScrimRanges = {
866
1038
  screen: this.screenRange,
@@ -884,7 +1056,12 @@ var ScrimController = class {
884
1056
  blur: cfg.blur ?? null,
885
1057
  interactive: cfg.interactive
886
1058
  });
887
- this.screenRange = cfg.range;
1059
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1060
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1061
+ } else {
1062
+ this.screenRange = cfg.range;
1063
+ this.backdropRange = cfg.range;
1064
+ }
888
1065
  }
889
1066
  const noop = () => {
890
1067
  };
@@ -934,59 +1111,7 @@ var ScrimController = class {
934
1111
  wrapper.className = "bs-scrim-overlay";
935
1112
  const ws = wrapper.style;
936
1113
  ws.position = "absolute";
937
- switch (position) {
938
- case "top-left":
939
- ws.top = inset;
940
- ws.left = inset;
941
- break;
942
- case "top-center":
943
- ws.top = inset;
944
- ws.left = "50%";
945
- ws.transform = "translateX(-50%)";
946
- break;
947
- case "top-right":
948
- ws.top = inset;
949
- ws.right = inset;
950
- break;
951
- case "center-left":
952
- ws.top = "50%";
953
- ws.left = inset;
954
- ws.transform = "translateY(-50%)";
955
- break;
956
- case "center":
957
- ws.top = "50%";
958
- ws.left = "50%";
959
- ws.transform = "translate(-50%, -50%)";
960
- break;
961
- case "center-right":
962
- ws.top = "50%";
963
- ws.right = inset;
964
- ws.transform = "translateY(-50%)";
965
- break;
966
- case "bottom-left":
967
- ws.bottom = inset;
968
- ws.left = inset;
969
- break;
970
- case "bottom-center":
971
- ws.bottom = inset;
972
- ws.left = "50%";
973
- ws.transform = "translateX(-50%)";
974
- break;
975
- case "bottom-right":
976
- ws.bottom = inset;
977
- ws.right = inset;
978
- break;
979
- case "sheet-top-left":
980
- case "sheet-top-center":
981
- case "sheet-top-right":
982
- applySheetAnchoredPosition(
983
- ws,
984
- this.mode,
985
- position,
986
- inset
987
- );
988
- break;
989
- }
1114
+ applyOverlayPosition(ws, this.mode, position, inset);
990
1115
  if (interactive) {
991
1116
  ws.pointerEvents = "auto";
992
1117
  }
@@ -1045,6 +1170,7 @@ var ScrimController = class {
1045
1170
  }
1046
1171
  }
1047
1172
  if (opts.interactive !== void 0) {
1173
+ this.scrimInteractive = opts.interactive;
1048
1174
  s.pointerEvents = opts.interactive ? "auto" : "none";
1049
1175
  }
1050
1176
  }
@@ -1059,38 +1185,9 @@ var ScrimController = class {
1059
1185
  const s = this.screenComponent.style;
1060
1186
  s.position = "fixed";
1061
1187
  s.inset = insetByMode[this.mode];
1062
- s.pointerEvents = "none";
1188
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1063
1189
  }
1064
1190
  };
1065
- function resolveSheetAnchoredStyle(mode, position, inset) {
1066
- const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1067
- switch (mode) {
1068
- case "bottom":
1069
- if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1070
- if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1071
- return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1072
- case "top":
1073
- if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1074
- if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1075
- return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1076
- case "left":
1077
- if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1078
- if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1079
- return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1080
- case "right":
1081
- if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1082
- if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1083
- return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1084
- }
1085
- }
1086
- function applySheetAnchoredPosition(ws, mode, position, inset) {
1087
- const style = resolveSheetAnchoredStyle(mode, position, inset);
1088
- if (style.top !== void 0) ws.top = style.top;
1089
- if (style.bottom !== void 0) ws.bottom = style.bottom;
1090
- if (style.left !== void 0) ws.left = style.left;
1091
- if (style.right !== void 0) ws.right = style.right;
1092
- if (style.transform !== void 0) ws.transform = style.transform;
1093
- }
1094
1191
 
1095
1192
  // src/core/animation/animation.ts
1096
1193
  var easeOutBack = (t) => {
@@ -1142,6 +1239,10 @@ var tween = ({
1142
1239
  promise
1143
1240
  };
1144
1241
  };
1242
+ var prefersReducedMotion = () => {
1243
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1244
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1245
+ };
1145
1246
 
1146
1247
  // src/core/animation/spring.ts
1147
1248
  var DEFAULT_SPRING = {
@@ -1172,14 +1273,20 @@ var runSpring = ({
1172
1273
  if (cancelled) return;
1173
1274
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1174
1275
  lastTime = now;
1175
- const subDt = Math.min(dt, 1 / 240);
1176
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1276
+ const steps = Math.max(1, Math.ceil(dt * 240));
1277
+ const subDt = dt / steps;
1278
+ let stepsLeft = steps;
1177
1279
  while (stepsLeft-- > 0) {
1178
1280
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1179
1281
  const accel = force / cfg.mass;
1180
1282
  v += accel * subDt;
1181
1283
  x += v * subDt;
1182
1284
  }
1285
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1286
+ onUpdate(to, 0);
1287
+ resolve();
1288
+ return;
1289
+ }
1183
1290
  onUpdate(x, v);
1184
1291
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1185
1292
  if (isAtRest) {
@@ -1354,9 +1461,13 @@ var isVisible = (el) => {
1354
1461
  var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1355
1462
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1356
1463
  );
1464
+ var trapStack = [];
1357
1465
  var installFocusTrap = (container, options = {}) => {
1358
1466
  if (typeof document === "undefined") return () => {
1359
1467
  };
1468
+ const token = {};
1469
+ trapStack.push(token);
1470
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1360
1471
  const previouslyFocused = document.activeElement;
1361
1472
  const focusInitial = () => {
1362
1473
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1364,6 +1475,7 @@ var installFocusTrap = (container, options = {}) => {
1364
1475
  };
1365
1476
  focusInitial();
1366
1477
  const handleKey = (e) => {
1478
+ if (!isActive()) return;
1367
1479
  if (e.key === "Escape" && options.onEscape) {
1368
1480
  options.onEscape();
1369
1481
  return;
@@ -1386,6 +1498,7 @@ var installFocusTrap = (container, options = {}) => {
1386
1498
  }
1387
1499
  };
1388
1500
  const handleFocusIn = (e) => {
1501
+ if (!isActive()) return;
1389
1502
  const target = e.target;
1390
1503
  if (target && !container.contains(target)) {
1391
1504
  const list = focusables(container);
@@ -1398,6 +1511,8 @@ var installFocusTrap = (container, options = {}) => {
1398
1511
  return () => {
1399
1512
  if (released) return;
1400
1513
  released = true;
1514
+ const idx = trapStack.indexOf(token);
1515
+ if (idx !== -1) trapStack.splice(idx, 1);
1401
1516
  document.removeEventListener("keydown", handleKey, true);
1402
1517
  document.removeEventListener("focusin", handleFocusIn, true);
1403
1518
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1453,11 +1568,17 @@ function createInertSiblings(rootProvider) {
1453
1568
  return {
1454
1569
  apply() {
1455
1570
  if (typeof document === "undefined") return;
1456
- const root = rootProvider();
1457
- const parent = root.parentElement ?? document.body;
1571
+ let top = rootProvider();
1572
+ while (top.parentElement && top.parentElement !== document.body) {
1573
+ top = top.parentElement;
1574
+ }
1575
+ const parent = top.parentElement ?? document.body;
1458
1576
  for (const child of Array.from(parent.children)) {
1459
- if (child === root) continue;
1577
+ if (child === top) continue;
1460
1578
  if (child.hasAttribute("inert")) continue;
1579
+ if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
1580
+ continue;
1581
+ }
1461
1582
  child.setAttribute("inert", "");
1462
1583
  tracked.push(child);
1463
1584
  }
@@ -1473,7 +1594,6 @@ function createInertSiblings(rootProvider) {
1473
1594
  var LifecycleController = class {
1474
1595
  constructor(deps, opts) {
1475
1596
  __publicField(this, "element");
1476
- __publicField(this, "closeFn");
1477
1597
  __publicField(this, "focusTrapEnabled");
1478
1598
  __publicField(this, "initialFocus");
1479
1599
  __publicField(this, "closeOnEscape");
@@ -1487,7 +1607,6 @@ var LifecycleController = class {
1487
1607
  __publicField(this, "installed", false);
1488
1608
  __publicField(this, "destroyed", false);
1489
1609
  this.element = deps.element;
1490
- this.closeFn = deps.close;
1491
1610
  this.focusTrapEnabled = opts.focusTrap ?? false;
1492
1611
  this.initialFocus = opts.initialFocus;
1493
1612
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1497,6 +1616,9 @@ var LifecycleController = class {
1497
1616
  this.returnFocus = opts.returnFocus;
1498
1617
  this.inertSiblings = createInertSiblings(() => this.element);
1499
1618
  }
1619
+ get isInstalled() {
1620
+ return this.installed;
1621
+ }
1500
1622
  install() {
1501
1623
  if (this.destroyed) return;
1502
1624
  this.installed = true;
@@ -1505,8 +1627,7 @@ var LifecycleController = class {
1505
1627
  }
1506
1628
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1507
1629
  this.releaseFocusTrap = installFocusTrap(this.element, {
1508
- initialFocus: this.initialFocus,
1509
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1630
+ initialFocus: this.initialFocus
1510
1631
  });
1511
1632
  }
1512
1633
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1526,7 +1647,7 @@ var LifecycleController = class {
1526
1647
  this.releaseScrollLock = null;
1527
1648
  this.inertSiblings.remove();
1528
1649
  if (this.returnFocus) {
1529
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1650
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1530
1651
  target?.focus?.();
1531
1652
  }
1532
1653
  }
@@ -1660,10 +1781,11 @@ var installGestures = (handle, mode, callbacks) => {
1660
1781
  };
1661
1782
 
1662
1783
  // src/core/primitives/rubber-band.ts
1663
- var RUBBER_BAND_FACTOR = 0.55;
1664
1784
  function rubberBand(overshoot, maxAxisSize) {
1665
1785
  const cap = Math.min(maxAxisSize * 0.15, 80);
1666
- return Math.sign(overshoot) * Math.pow(Math.abs(overshoot), RUBBER_BAND_FACTOR) * Math.min(6, cap / Math.max(Math.abs(overshoot), 1));
1786
+ if (cap <= 0) return 0;
1787
+ const abs = Math.abs(overshoot);
1788
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1667
1789
  }
1668
1790
 
1669
1791
  // src/core/features/soft-keyboard.ts
@@ -1692,6 +1814,7 @@ var GestureController = class {
1692
1814
  __publicField(this, "dragStartSize", 0);
1693
1815
  __publicField(this, "currentPointerKind", "touch");
1694
1816
  __publicField(this, "keyboardDismissed", false);
1817
+ __publicField(this, "dragSuppressed", false);
1695
1818
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1696
1819
  this.deps = deps;
1697
1820
  }
@@ -1702,6 +1825,11 @@ var GestureController = class {
1702
1825
  if (this.detach) return this.detach;
1703
1826
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1704
1827
  onStart: (_coord, kind) => {
1828
+ if (this.deps.getDisableDrag?.()) {
1829
+ this.dragSuppressed = true;
1830
+ return;
1831
+ }
1832
+ this.dragSuppressed = false;
1705
1833
  this.deps.cancelAnimation();
1706
1834
  this.isDragging_ = true;
1707
1835
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1712,6 +1840,7 @@ var GestureController = class {
1712
1840
  this.deps.emit("dragstart", { size: this.dragStartSize });
1713
1841
  },
1714
1842
  onMove: (delta) => {
1843
+ if (this.dragSuppressed) return;
1715
1844
  const ctx = this.deps.getDragContext();
1716
1845
  const { min, max } = ctx.range;
1717
1846
  const maxAxis = ctx.maxAxisSize;
@@ -1735,6 +1864,10 @@ var GestureController = class {
1735
1864
  }
1736
1865
  },
1737
1866
  onEnd: (delta, velocity, kind) => {
1867
+ if (this.dragSuppressed) {
1868
+ this.dragSuppressed = false;
1869
+ return;
1870
+ }
1738
1871
  this.isDragging_ = false;
1739
1872
  this.keyboardDismissed = false;
1740
1873
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1745,9 +1878,17 @@ var GestureController = class {
1745
1878
  this.deps.settleAfterDrag(delta, velocity, kind);
1746
1879
  },
1747
1880
  onCancel: () => {
1881
+ if (this.dragSuppressed) {
1882
+ this.dragSuppressed = false;
1883
+ return;
1884
+ }
1748
1885
  this.isDragging_ = false;
1749
1886
  this.keyboardDismissed = false;
1750
1887
  this.deps.getRoot()?.removeAttribute("data-dragging");
1888
+ this.deps.emit("dragend", {
1889
+ size: this.deps.getDragContext().size,
1890
+ velocity: 0
1891
+ });
1751
1892
  const restoreTo = this.dragStartSize;
1752
1893
  void this.deps.animateTo(restoreTo, 0);
1753
1894
  }
@@ -1821,6 +1962,287 @@ function createScrollCache(deps) {
1821
1962
  };
1822
1963
  }
1823
1964
 
1965
+ // src/core/primitives/anchor-animations.ts
1966
+ var DEFAULT_DURATION3 = 200;
1967
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1968
+ var PRESET_FRAMES = {
1969
+ fade: {
1970
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1971
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1972
+ },
1973
+ scale: {
1974
+ enter: [
1975
+ { opacity: 0, transform: "scale(0.85)" },
1976
+ { opacity: 1, transform: "scale(1)" }
1977
+ ],
1978
+ exit: [
1979
+ { opacity: 1, transform: "scale(1)" },
1980
+ { opacity: 0, transform: "scale(0.85)" }
1981
+ ]
1982
+ },
1983
+ slide: {
1984
+ enter: [
1985
+ { opacity: 0, transform: "translateY(10px)" },
1986
+ { opacity: 1, transform: "translateY(0)" }
1987
+ ],
1988
+ exit: [
1989
+ { opacity: 1, transform: "translateY(0)" },
1990
+ { opacity: 0, transform: "translateY(10px)" }
1991
+ ]
1992
+ },
1993
+ pop: {
1994
+ enter: [
1995
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
1996
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
1997
+ { opacity: 1, transform: "scale(1)", offset: 1 }
1998
+ ],
1999
+ exit: [
2000
+ { opacity: 1, transform: "scale(1)" },
2001
+ { opacity: 0, transform: "scale(0.6)" }
2002
+ ]
2003
+ }
2004
+ };
2005
+ var INSTANT = {
2006
+ finished: Promise.resolve(),
2007
+ cancel: () => {
2008
+ }
2009
+ };
2010
+ function resolveTransitionFrames(spec, phase) {
2011
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
2012
+ if (spec === "none") return fallback;
2013
+ if (spec === void 0 || typeof spec === "string") {
2014
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
2015
+ return {
2016
+ frames: preset2[phase],
2017
+ duration: DEFAULT_DURATION3,
2018
+ easing: DEFAULT_EASING
2019
+ };
2020
+ }
2021
+ const custom = phase === "enter" ? spec.enter : spec.exit;
2022
+ if (custom) {
2023
+ return {
2024
+ frames: custom,
2025
+ duration: spec.duration ?? DEFAULT_DURATION3,
2026
+ easing: spec.easing ?? DEFAULT_EASING
2027
+ };
2028
+ }
2029
+ if (spec.preset === "none") return fallback;
2030
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
2031
+ return {
2032
+ frames: preset[phase],
2033
+ duration: spec.duration ?? DEFAULT_DURATION3,
2034
+ easing: spec.easing ?? DEFAULT_EASING
2035
+ };
2036
+ }
2037
+ function runAnchorTransition(el, spec, phase) {
2038
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
2039
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
2040
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
2041
+ if (!frames || duration <= 0) return INSTANT;
2042
+ if (typeof el.animate !== "function") return INSTANT;
2043
+ const animation = el.animate(frames, {
2044
+ duration,
2045
+ easing,
2046
+ fill: "both"
2047
+ });
2048
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
2049
+ return {
2050
+ finished,
2051
+ cancel: () => {
2052
+ try {
2053
+ animation.cancel();
2054
+ } catch {
2055
+ return;
2056
+ }
2057
+ }
2058
+ };
2059
+ }
2060
+
2061
+ // src/core/features/sheet-anchors.ts
2062
+ function attachAnchor(deps, opts) {
2063
+ const doc = deps.host.ownerDocument;
2064
+ const wrapper = doc.createElement("div");
2065
+ wrapper.className = "bs-anchor";
2066
+ const interactive = opts.interactive ?? true;
2067
+ const position = opts.position ?? "sheet-top-right";
2068
+ const isDock = position === "dock-bottom" || position === "dock-top";
2069
+ const ws = wrapper.style;
2070
+ ws.position = "fixed";
2071
+ if (isDock) {
2072
+ ws.left = "0";
2073
+ ws.right = "0";
2074
+ if (position === "dock-bottom") {
2075
+ ws.bottom = "0";
2076
+ } else {
2077
+ ws.top = "0";
2078
+ }
2079
+ } else {
2080
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2081
+ }
2082
+ if (opts.fadeRange) {
2083
+ const [r0, r1] = opts.fadeRange;
2084
+ const span = Math.max(r1 - r0, 1e-4);
2085
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2086
+ }
2087
+ wrapper.appendChild(opts.element);
2088
+ deps.host.appendChild(wrapper);
2089
+ let visible = null;
2090
+ let inFlight = null;
2091
+ const shouldShow = (state) => {
2092
+ if (typeof opts.showOn === "function") return opts.showOn(state);
2093
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
2094
+ return isDock || state.size > 0;
2095
+ };
2096
+ const applyVisibility = (next, animate) => {
2097
+ if (next === visible) return;
2098
+ visible = next;
2099
+ inFlight?.cancel();
2100
+ inFlight = null;
2101
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2102
+ if (next) {
2103
+ wrapper.style.visibility = "";
2104
+ if (animate) {
2105
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2106
+ }
2107
+ return;
2108
+ }
2109
+ if (!animate) {
2110
+ wrapper.style.visibility = "hidden";
2111
+ return;
2112
+ }
2113
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2114
+ inFlight = handle;
2115
+ void handle.finished.then(() => {
2116
+ if (inFlight === handle && visible === false) {
2117
+ wrapper.style.visibility = "hidden";
2118
+ }
2119
+ });
2120
+ };
2121
+ const evaluate = (animate) => {
2122
+ if (deps.isDestroyed()) return;
2123
+ applyVisibility(shouldShow(deps.getState()), animate);
2124
+ };
2125
+ const offs = [
2126
+ deps.on("snap", () => evaluate(true)),
2127
+ deps.on("open", () => evaluate(true)),
2128
+ deps.on("close", () => evaluate(true))
2129
+ ];
2130
+ evaluate(false);
2131
+ return {
2132
+ wrapper,
2133
+ detach: () => {
2134
+ offs.forEach((off) => off());
2135
+ inFlight?.cancel();
2136
+ inFlight = null;
2137
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2138
+ },
2139
+ syncZ: (z) => {
2140
+ wrapper.style.zIndex = String(z);
2141
+ }
2142
+ };
2143
+ }
2144
+
2145
+ // src/core/features/scrim-stages.ts
2146
+ var matchesId = (def, activeId) => {
2147
+ if (def.for === void 0) return false;
2148
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2149
+ };
2150
+ var matchesRange = (def, progress) => {
2151
+ if (!def.forRange) return false;
2152
+ const [from, to] = def.forRange;
2153
+ return progress >= from && progress <= to;
2154
+ };
2155
+ function installScrimStages(deps, opts) {
2156
+ const doc = deps.host.ownerDocument;
2157
+ const entries = opts.stages.map((def) => {
2158
+ const wrapper = doc.createElement("div");
2159
+ wrapper.className = "bs-scrim-stage";
2160
+ const ws = wrapper.style;
2161
+ ws.position = "absolute";
2162
+ applyOverlayPosition(
2163
+ ws,
2164
+ deps.mode,
2165
+ def.position ?? opts.position ?? "center",
2166
+ def.inset ?? opts.inset ?? "16px"
2167
+ );
2168
+ ws.visibility = "hidden";
2169
+ ws.pointerEvents = "none";
2170
+ wrapper.appendChild(def.element);
2171
+ deps.host.appendChild(wrapper);
2172
+ return { def, wrapper, inFlight: null };
2173
+ });
2174
+ let active = null;
2175
+ const resolveActive = () => {
2176
+ const state = deps.getState();
2177
+ if (state.size === 0) {
2178
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2179
+ }
2180
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2181
+ };
2182
+ const hide = (entry, animate) => {
2183
+ entry.inFlight?.cancel();
2184
+ entry.wrapper.style.pointerEvents = "none";
2185
+ if (!animate) {
2186
+ entry.inFlight = null;
2187
+ entry.wrapper.style.visibility = "hidden";
2188
+ return;
2189
+ }
2190
+ const handle = runAnchorTransition(
2191
+ entry.def.element,
2192
+ entry.def.animation ?? opts.animation,
2193
+ "exit"
2194
+ );
2195
+ entry.inFlight = handle;
2196
+ void handle.finished.then(() => {
2197
+ if (entry.inFlight === handle && active !== entry) {
2198
+ entry.wrapper.style.visibility = "hidden";
2199
+ }
2200
+ });
2201
+ };
2202
+ const show = (entry, animate) => {
2203
+ entry.inFlight?.cancel();
2204
+ entry.inFlight = null;
2205
+ entry.wrapper.style.visibility = "";
2206
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2207
+ if (animate) {
2208
+ entry.inFlight = runAnchorTransition(
2209
+ entry.def.element,
2210
+ entry.def.animation ?? opts.animation,
2211
+ "enter"
2212
+ );
2213
+ }
2214
+ };
2215
+ const update = (animate) => {
2216
+ if (deps.isDestroyed()) return;
2217
+ const next = resolveActive();
2218
+ if (next === active) return;
2219
+ const prev = active;
2220
+ active = next;
2221
+ if (prev) hide(prev, animate);
2222
+ if (next) show(next, animate);
2223
+ };
2224
+ const offs = [
2225
+ deps.on("snap", () => update(true)),
2226
+ deps.on("open", () => update(true)),
2227
+ deps.on("close", () => update(true))
2228
+ ];
2229
+ if (entries.some((e) => e.def.forRange)) {
2230
+ offs.push(deps.on("progress", () => update(true)));
2231
+ }
2232
+ update(false);
2233
+ return () => {
2234
+ offs.forEach((off) => off());
2235
+ for (const entry of entries) {
2236
+ entry.inFlight?.cancel();
2237
+ entry.inFlight = null;
2238
+ if (entry.wrapper.parentNode) {
2239
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2240
+ }
2241
+ }
2242
+ active = null;
2243
+ };
2244
+ }
2245
+
1824
2246
  // src/core/primitives/engine-options.ts
1825
2247
  var DEFAULT_FLICK_VELOCITY = 0.65;
1826
2248
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1839,6 +2261,12 @@ function resolveEngineOptions(opts, extras) {
1839
2261
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
1840
2262
  rubberBandEnabled: opts.rubberBand ?? true,
1841
2263
  closeOnBack: opts.closeOnBack ?? false,
2264
+ persistent: opts.persistent ?? false,
2265
+ disableClose: opts.disableClose ?? false,
2266
+ disableDrag: opts.disableDrag ?? false,
2267
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2268
+ radius: opts.radius,
2269
+ maxHeight: opts.maxHeight,
1842
2270
  initialAllowed,
1843
2271
  initialId,
1844
2272
  scrim: {
@@ -1865,7 +2293,8 @@ function resolveEngineOptions(opts, extras) {
1865
2293
  closeOnEscape: opts.closeOnEscape,
1866
2294
  lockBodyScroll: opts.lockBodyScroll,
1867
2295
  inertSiblings: opts.inertSiblings,
1868
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2296
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2297
+ returnFocus: opts.returnFocusTo
1869
2298
  }
1870
2299
  };
1871
2300
  }
@@ -1910,6 +2339,11 @@ var BottomSheetEngine = class {
1910
2339
  __publicField(this, "lifecycle");
1911
2340
  __publicField(this, "closeOnBack");
1912
2341
  __publicField(this, "routedTo");
2342
+ __publicField(this, "persistent");
2343
+ __publicField(this, "disableCloseFlag");
2344
+ __publicField(this, "disableDragFlag");
2345
+ __publicField(this, "closeOnRouteChange");
2346
+ __publicField(this, "maxHeightCap");
1913
2347
  __publicField(this, "snapPointsRaw");
1914
2348
  __publicField(this, "snaps");
1915
2349
  __publicField(this, "activeId");
@@ -1922,6 +2356,7 @@ var BottomSheetEngine = class {
1922
2356
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
1923
2357
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
1924
2358
  __publicField(this, "isTopSheet", true);
2359
+ __publicField(this, "opening", false);
1925
2360
  __publicField(this, "progressPayload", {
1926
2361
  value: 0,
1927
2362
  size: 0
@@ -1937,6 +2372,12 @@ var BottomSheetEngine = class {
1937
2372
  __publicField(this, "linkedSheets", []);
1938
2373
  __publicField(this, "scrollCache");
1939
2374
  __publicField(this, "teardowns", new TeardownStack());
2375
+ __publicField(this, "anchors", []);
2376
+ __publicField(this, "anchorHost", null);
2377
+ __publicField(this, "stackZ", 100);
2378
+ __publicField(this, "detachScrimStages", null);
2379
+ __publicField(this, "stackEffectEnabled", false);
2380
+ __publicField(this, "stackEffectPrimed", false);
1940
2381
  __publicField(this, "transformTemplate");
1941
2382
  const resolved = resolveEngineOptions(opts, {
1942
2383
  shouldApplyInertSiblings: () => {
@@ -1970,6 +2411,10 @@ var BottomSheetEngine = class {
1970
2411
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1971
2412
  snapTo: (id) => {
1972
2413
  void this.snapTo(id);
2414
+ },
2415
+ close: () => {
2416
+ if (!this.canDismiss()) return;
2417
+ void this.close("backdrop");
1973
2418
  }
1974
2419
  },
1975
2420
  resolved.scrim
@@ -1993,6 +2438,11 @@ var BottomSheetEngine = class {
1993
2438
  );
1994
2439
  this.closeOnBack = resolved.closeOnBack;
1995
2440
  this.routedTo = opts.routedTo;
2441
+ this.persistent = resolved.persistent;
2442
+ this.disableCloseFlag = resolved.disableClose;
2443
+ this.disableDragFlag = resolved.disableDrag;
2444
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2445
+ this.stackEffectEnabled = opts.stackEffect ?? false;
1996
2446
  this.snapPointsRaw = opts.snapPoints;
1997
2447
  this.persistKey = opts.persistKey;
1998
2448
  if (opts.linkedSheets) {
@@ -2005,7 +2455,7 @@ var BottomSheetEngine = class {
2005
2455
  this.snapPointsRaw,
2006
2456
  resolved.initialAllowed,
2007
2457
  this.mode,
2008
- () => this.handle.offsetHeight,
2458
+ () => this.measureFitSize(),
2009
2459
  (maxAxisSize) => {
2010
2460
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2011
2461
  }
@@ -2039,6 +2489,19 @@ var BottomSheetEngine = class {
2039
2489
  io.observe(this.element);
2040
2490
  this.teardowns.add(() => io.disconnect());
2041
2491
  }
2492
+ this.installFitObserver();
2493
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2494
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2495
+ this.applyAutoAriaLabelledBy();
2496
+ if (this.closeOnRouteChange) {
2497
+ this.teardowns.add(
2498
+ installRouteChange({
2499
+ isDestroyed: () => this.destroyed,
2500
+ getSize: () => this.size,
2501
+ close: () => this.close()
2502
+ })
2503
+ );
2504
+ }
2042
2505
  if (this.persistKey) {
2043
2506
  this.teardowns.add(installPersist(this, this.persistKey));
2044
2507
  }
@@ -2063,7 +2526,7 @@ var BottomSheetEngine = class {
2063
2526
  isTopSheet: () => this.isTopSheet,
2064
2527
  getSize: () => this.size,
2065
2528
  isDestroyed: () => this.destroyed,
2066
- close: () => this.close(),
2529
+ close: () => this.close("back"),
2067
2530
  on: (event, fn) => this.on(event, fn),
2068
2531
  sheetId: this.id
2069
2532
  })
@@ -2157,6 +2620,10 @@ var BottomSheetEngine = class {
2157
2620
  const previousSize = this.size;
2158
2621
  this.scrollCache.cache(previousId, previousSize, target.size);
2159
2622
  this.activeId = id;
2623
+ if (wasClosed && target.size > 0) {
2624
+ this.opening = true;
2625
+ sheetStack.promote(this.id);
2626
+ }
2160
2627
  if (this.animation.viewTransitionsAvailable) {
2161
2628
  this.currentViewTransition?.skipTransition?.();
2162
2629
  const vt = document.startViewTransition(() => {
@@ -2171,19 +2638,26 @@ var BottomSheetEngine = class {
2171
2638
  } else {
2172
2639
  await this.animation.animateTo(target.size, velocityPxPerMs);
2173
2640
  }
2641
+ this.opening = false;
2174
2642
  externalSignal?.removeEventListener("abort", onExternalAbort);
2175
2643
  if (signal.aborted) return;
2176
2644
  this.scrollCache.restore(id, previousSize, target.size);
2177
2645
  this.updateAriaSlider();
2178
- this.emit("snap", { id, size: target.size });
2646
+ this.emit("snap", {
2647
+ id,
2648
+ size: target.size,
2649
+ progress: this.computeProgress(target.size)
2650
+ });
2179
2651
  if (wasClosed && target.size > 0) {
2180
2652
  this.emit("open", { id });
2181
2653
  this.handleOpen();
2182
2654
  notifyLinkedSheets(this.linkedSheets, this);
2655
+ this.emit("opened", { id });
2183
2656
  }
2184
2657
  if (target.size === 0) {
2185
2658
  this.emit("close", void 0);
2186
2659
  this.handleClose();
2660
+ this.emit("closed", void 0);
2187
2661
  }
2188
2662
  }
2189
2663
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2208,18 +2682,76 @@ var BottomSheetEngine = class {
2208
2682
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2209
2683
  return this.snapTo(target);
2210
2684
  }
2211
- close() {
2685
+ close(reason = "programmatic") {
2212
2686
  if (this.destroyed) return Promise.resolve();
2687
+ if (this.disableCloseFlag) return Promise.resolve();
2688
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2213
2689
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2214
2690
  return this.snapTo(closedId ?? this.activeId);
2215
2691
  }
2216
- setAllowed(ids, snap) {
2692
+ canDismiss() {
2693
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2694
+ }
2695
+ isTop() {
2696
+ return this.isTopSheet;
2697
+ }
2698
+ depth() {
2699
+ return sheetStack.depthOf(this.id);
2700
+ }
2701
+ expand() {
2702
+ if (this.destroyed) return Promise.resolve();
2703
+ const ids = this.allowedIdsBySize();
2704
+ const target = ids[ids.length - 1];
2705
+ if (!target) return Promise.resolve();
2706
+ return this.snapTo(target);
2707
+ }
2708
+ collapse() {
2709
+ if (this.destroyed) return Promise.resolve();
2710
+ const ids = this.allowedIdsBySize();
2711
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2712
+ const target = nonZero ?? ids[0];
2713
+ if (!target) return Promise.resolve();
2714
+ return this.snapTo(target);
2715
+ }
2716
+ setRadius(r) {
2217
2717
  if (this.destroyed) return;
2218
- const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
2219
- if (!willSnap) {
2220
- this.animation.cancel();
2221
- this.newCycle();
2718
+ this.element.style.setProperty(
2719
+ "--bs-radius",
2720
+ typeof r === "number" ? `${r}px` : r
2721
+ );
2722
+ }
2723
+ setMaxHeight(h) {
2724
+ if (this.destroyed) return;
2725
+ const value = typeof h === "number" ? `${h}px` : h;
2726
+ const axis = layoutAxis(this.mode);
2727
+ this.element.style.setProperty(
2728
+ axis === "height" ? "maxHeight" : "maxWidth",
2729
+ value
2730
+ );
2731
+ if (typeof h === "number") {
2732
+ this.maxHeightCap = h;
2733
+ this.clampToMaxHeight();
2734
+ } else if (typeof window !== "undefined") {
2735
+ const probe2 = document.createElement("div");
2736
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2737
+ document.body.appendChild(probe2);
2738
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2739
+ probe2.remove();
2740
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2741
+ this.clampToMaxHeight();
2742
+ }
2743
+ }
2744
+ clampToMaxHeight() {
2745
+ if (this.maxHeightCap === void 0) return;
2746
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2747
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2222
2748
  }
2749
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2750
+ this.applySize(this.maxHeightCap);
2751
+ }
2752
+ }
2753
+ setAllowed(ids, snap) {
2754
+ if (this.destroyed) return;
2223
2755
  this.snaps.setAllowedIds(ids);
2224
2756
  this.updateAriaSlider();
2225
2757
  if (snap && ids.includes(snap)) {
@@ -2259,6 +2791,79 @@ var BottomSheetEngine = class {
2259
2791
  setScrimOverlay(opts) {
2260
2792
  return this.scrim.setScrimOverlay(opts);
2261
2793
  }
2794
+ addAnchor(opts) {
2795
+ if (this.destroyed || typeof document === "undefined") return () => {
2796
+ };
2797
+ if (!this.anchorHost) {
2798
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2799
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2800
+ this.anchorHost.style.setProperty(
2801
+ "--bs-progress",
2802
+ String(this.computeProgress(this.size))
2803
+ );
2804
+ }
2805
+ const handle = attachAnchor(
2806
+ {
2807
+ mode: this.mode,
2808
+ host: this.anchorHost,
2809
+ getState: () => ({
2810
+ activeId: this.activeId,
2811
+ size: this.size,
2812
+ progress: this.computeProgress(this.size)
2813
+ }),
2814
+ on: (event, fn) => this.on(event, fn),
2815
+ isDestroyed: () => this.destroyed
2816
+ },
2817
+ opts
2818
+ );
2819
+ handle.syncZ(this.stackZ + 1);
2820
+ this.anchors.push(handle);
2821
+ const detach = () => {
2822
+ const idx = this.anchors.indexOf(handle);
2823
+ if (idx === -1) return;
2824
+ this.anchors.splice(idx, 1);
2825
+ handle.detach();
2826
+ };
2827
+ this.teardowns.add(detach);
2828
+ return detach;
2829
+ }
2830
+ setScrimStages(opts) {
2831
+ if (this.destroyed || typeof document === "undefined") return () => {
2832
+ };
2833
+ this.detachScrimStages?.();
2834
+ this.detachScrimStages = null;
2835
+ if (!opts) return () => {
2836
+ };
2837
+ const host = this.screenComponent?.parentElement;
2838
+ if (!host) {
2839
+ console.warn(
2840
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2841
+ );
2842
+ return () => {
2843
+ };
2844
+ }
2845
+ const detachInstalled = installScrimStages(
2846
+ {
2847
+ mode: this.mode,
2848
+ host,
2849
+ getState: () => ({
2850
+ activeId: this.activeId,
2851
+ size: this.size,
2852
+ progress: this.computeProgress(this.size)
2853
+ }),
2854
+ on: (event, fn) => this.on(event, fn),
2855
+ isDestroyed: () => this.destroyed
2856
+ },
2857
+ opts
2858
+ );
2859
+ const detach = () => {
2860
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2861
+ detachInstalled();
2862
+ };
2863
+ this.detachScrimStages = detach;
2864
+ this.teardowns.add(() => this.detachScrimStages?.());
2865
+ return detach;
2866
+ }
2262
2867
  getScrimState() {
2263
2868
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2264
2869
  }
@@ -2278,8 +2883,53 @@ var BottomSheetEngine = class {
2278
2883
  }
2279
2884
  this.updateAriaSlider();
2280
2885
  }
2886
+ recompute() {
2887
+ if (this.destroyed) return;
2888
+ this.snaps.recompute();
2889
+ this.scrim.invalidateOpacityCache();
2890
+ if (this.gesture?.isDragging) return;
2891
+ const current = this.snaps.findById(this.activeId);
2892
+ if (current) {
2893
+ this.size = current.size;
2894
+ this.applySize(this.size);
2895
+ this.updateAriaSlider();
2896
+ }
2897
+ }
2898
+ measureFitSize() {
2899
+ const vertical = layoutAxis(this.mode) === "height";
2900
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2901
+ const content = this.scrollContainer;
2902
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2903
+ if (typeof window === "undefined") return natural;
2904
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2905
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2906
+ }
2907
+ installFitObserver() {
2908
+ if (typeof ResizeObserver === "undefined") return;
2909
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2910
+ return;
2911
+ const targets = [this.handle];
2912
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2913
+ targets.push(this.scrollContainer);
2914
+ }
2915
+ let raf = 0;
2916
+ const ro = new ResizeObserver(() => {
2917
+ if (raf) return;
2918
+ raf = requestAnimationFrame(() => {
2919
+ raf = 0;
2920
+ if (this.destroyed || this.gesture?.isDragging) return;
2921
+ this.recompute();
2922
+ });
2923
+ });
2924
+ for (const t of targets) ro.observe(t);
2925
+ this.teardowns.add(() => {
2926
+ ro.disconnect();
2927
+ if (raf) cancelAnimationFrame(raf);
2928
+ });
2929
+ }
2281
2930
  destroy() {
2282
2931
  this.destroyed = true;
2932
+ this.opening = false;
2283
2933
  this.currentAbort.abort();
2284
2934
  this.currentViewTransition?.skipTransition?.();
2285
2935
  this.currentViewTransition = null;
@@ -2329,6 +2979,36 @@ var BottomSheetEngine = class {
2329
2979
  frozen = true;
2330
2980
  return cancelled;
2331
2981
  }
2982
+ emitBeforeClose(reason) {
2983
+ let cancelled = false;
2984
+ let frozen = false;
2985
+ this.emit("before-close", {
2986
+ reason,
2987
+ cancel: () => {
2988
+ if (frozen) {
2989
+ console.warn(
2990
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
2991
+ );
2992
+ return;
2993
+ }
2994
+ cancelled = true;
2995
+ }
2996
+ });
2997
+ frozen = true;
2998
+ return cancelled;
2999
+ }
3000
+ applyAutoAriaLabelledBy() {
3001
+ if (typeof document === "undefined") return;
3002
+ if (!this.lifecycle.focusTrapEnabled) return;
3003
+ if (this.element.getAttribute("aria-labelledby")) return;
3004
+ const scope = this.handle ?? this.element;
3005
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3006
+ if (!titled) return;
3007
+ if (!titled.id) {
3008
+ titled.id = nextInstanceId("bs-title");
3009
+ }
3010
+ this.element.setAttribute("aria-labelledby", titled.id);
3011
+ }
2332
3012
  recomputeSnaps() {
2333
3013
  const seen = /* @__PURE__ */ new Set();
2334
3014
  for (const p of this.snapPointsRaw) {
@@ -2345,24 +3025,42 @@ var BottomSheetEngine = class {
2345
3025
  getAllowedRange() {
2346
3026
  return this.snaps.getAllowedRange();
2347
3027
  }
3028
+ allowedIdsBySize() {
3029
+ return this.snaps.getAllowedIds().slice().sort(
3030
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
3031
+ );
3032
+ }
2348
3033
  updateAriaSlider() {
2349
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
3034
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2350
3035
  }
2351
3036
  registerInStack() {
2352
3037
  this.teardowns.add(sheetStack.push({
2353
3038
  id: this.id,
2354
3039
  setZIndex: (z) => {
3040
+ this.stackZ = z;
2355
3041
  this.element.style.zIndex = String(z);
2356
3042
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3043
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2357
3044
  },
2358
3045
  setIsTop: (isTop) => {
2359
3046
  this.isTopSheet = isTop;
2360
- if (this.backdrop) {
2361
- this.backdrop.style.display = isTop ? "" : "none";
2362
- }
2363
- }
3047
+ },
3048
+ isOpen: () => this.size > 0 || this.opening,
3049
+ setDepth: (depth) => this.applyStackDepth(depth)
2364
3050
  }));
2365
3051
  }
3052
+ applyStackDepth(depth) {
3053
+ if (!this.stackEffectEnabled) return;
3054
+ this.element.setAttribute("data-stack-depth", String(depth));
3055
+ if (!this.stackEffectPrimed) {
3056
+ this.stackEffectPrimed = true;
3057
+ const isVertical = this.mode === "bottom" || this.mode === "top";
3058
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
3059
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
3060
+ }
3061
+ const scale = Math.max(1 - depth * 0.04, 0.86);
3062
+ this.element.style.scale = scale === 1 ? "" : String(scale);
3063
+ }
2366
3064
  attach() {
2367
3065
  this.gesture = new GestureController({
2368
3066
  handle: this.handle,
@@ -2379,6 +3077,7 @@ var BottomSheetEngine = class {
2379
3077
  buf.rubberBandEnabled = this.rubberBandEnabled;
2380
3078
  return buf;
2381
3079
  },
3080
+ getDisableDrag: () => this.disableDragFlag,
2382
3081
  cancelAnimation: () => this.animation.cancel(),
2383
3082
  applySize: (size) => this.applySize(size),
2384
3083
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2422,7 +3121,7 @@ var BottomSheetEngine = class {
2422
3121
  container: this.scrollContainer,
2423
3122
  isDragging: () => this.gesture?.isDragging ?? false,
2424
3123
  isAnimating: () => this.animation.isAnimating,
2425
- getAllowedIds: () => this.snaps.getAllowedIds(),
3124
+ getAllowedIds: () => this.allowedIdsBySize(),
2426
3125
  getActiveId: () => this.activeId,
2427
3126
  snapTo: (id) => {
2428
3127
  void this.snapTo(id);
@@ -2451,13 +3150,16 @@ var BottomSheetEngine = class {
2451
3150
  },
2452
3151
  newCycle: () => {
2453
3152
  this.newCycle();
2454
- }
3153
+ },
3154
+ isAnimating: () => this.animation.isAnimating,
3155
+ resyncAfterCancel: () => this.resyncAfterResize()
2455
3156
  }));
2456
3157
  if (this.lifecycle.closeOnEscape) {
2457
3158
  const onKey = (e) => {
2458
- if (this.destroyed) return;
3159
+ if (this.destroyed || e.defaultPrevented) return;
3160
+ if (!this.canDismiss()) return;
2459
3161
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2460
- void this.close();
3162
+ void this.close("escape");
2461
3163
  }
2462
3164
  };
2463
3165
  document.addEventListener("keydown", onKey);
@@ -2487,7 +3189,7 @@ var BottomSheetEngine = class {
2487
3189
  handle: this.handle,
2488
3190
  mode: this.mode,
2489
3191
  isDestroyed: () => this.destroyed,
2490
- getAllowedIds: () => this.snaps.getAllowedIds(),
3192
+ getAllowedIds: () => this.allowedIdsBySize(),
2491
3193
  getActiveId: () => this.activeId,
2492
3194
  snapTo: (id) => {
2493
3195
  void this.snapTo(id);
@@ -2508,6 +3210,7 @@ var BottomSheetEngine = class {
2508
3210
  });
2509
3211
  if (!target) return;
2510
3212
  const previousId = this.activeId;
3213
+ if (target.id === previousId && target.size === this.size) return;
2511
3214
  if (this.emitBeforeSnap(target, previousId)) {
2512
3215
  const restore = this.snaps.findById(previousId);
2513
3216
  if (restore) {
@@ -2516,21 +3219,34 @@ var BottomSheetEngine = class {
2516
3219
  return;
2517
3220
  }
2518
3221
  const previousSize = this.size;
3222
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2519
3223
  this.scrollCache.cache(previousId, previousSize, target.size);
2520
3224
  this.activeId = target.id;
3225
+ if (previousSnapSize === 0 && target.size > 0) {
3226
+ this.opening = true;
3227
+ sheetStack.promote(this.id);
3228
+ }
2521
3229
  void this.animation.animateTo(target.size, velocity).then(() => {
3230
+ this.opening = false;
2522
3231
  if (signal.aborted) return;
2523
3232
  this.scrollCache.restore(target.id, previousSize, target.size);
2524
3233
  this.updateAriaSlider();
2525
- this.emit("snap", { id: target.id, size: target.size });
3234
+ this.emit("snap", {
3235
+ id: target.id,
3236
+ size: target.size,
3237
+ progress: this.computeProgress(target.size)
3238
+ });
2526
3239
  this.haptic();
2527
- if (previousId === "closed" && target.size > 0) {
3240
+ if (previousSnapSize === 0 && target.size > 0) {
2528
3241
  this.emit("open", { id: target.id });
2529
3242
  this.handleOpen();
3243
+ notifyLinkedSheets(this.linkedSheets, this);
3244
+ this.emit("opened", { id: target.id });
2530
3245
  }
2531
3246
  if (target.size === 0) {
2532
3247
  this.emit("close", void 0);
2533
3248
  this.handleClose();
3249
+ this.emit("closed", void 0);
2534
3250
  }
2535
3251
  });
2536
3252
  }
@@ -2552,6 +3268,9 @@ var BottomSheetEngine = class {
2552
3268
  if (this.scrimParent) {
2553
3269
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2554
3270
  }
3271
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3272
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3273
+ }
2555
3274
  }
2556
3275
  const progress = this.computeProgress(size);
2557
3276
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2560,6 +3279,9 @@ var BottomSheetEngine = class {
2560
3279
  );
2561
3280
  if (progressChanged) {
2562
3281
  style.setProperty("--bs-progress", String(progress));
3282
+ if (this.anchorHost) {
3283
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3284
+ }
2563
3285
  }
2564
3286
  this.scrim.applyOpacity(progress, progressChanged);
2565
3287
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2574,6 +3296,7 @@ var BottomSheetEngine = class {
2574
3296
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2575
3297
  }
2576
3298
  handleOpen() {
3299
+ sheetStack.promote(this.id);
2577
3300
  try {
2578
3301
  this.lifecycle.install();
2579
3302
  } catch (err) {
@@ -2587,8 +3310,31 @@ var BottomSheetEngine = class {
2587
3310
  });
2588
3311
  }
2589
3312
  }
3313
+ resyncAfterResize() {
3314
+ if (this.destroyed) return;
3315
+ const target = this.snaps.findById(this.activeId);
3316
+ if (!target) return;
3317
+ this.updateAriaSlider();
3318
+ this.emit("snap", {
3319
+ id: target.id,
3320
+ size: target.size,
3321
+ progress: this.computeProgress(target.size)
3322
+ });
3323
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3324
+ this.emit("open", { id: target.id });
3325
+ this.handleOpen();
3326
+ notifyLinkedSheets(this.linkedSheets, this);
3327
+ this.emit("opened", { id: target.id });
3328
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3329
+ this.emit("close", void 0);
3330
+ this.handleClose();
3331
+ this.emit("closed", void 0);
3332
+ }
3333
+ }
2590
3334
  handleClose() {
3335
+ this.opening = false;
2591
3336
  this.lifecycle.release();
3337
+ sheetStack.update();
2592
3338
  }
2593
3339
  };
2594
3340
 
@@ -2651,12 +3397,26 @@ function useBottomSheet(opts) {
2651
3397
  state,
2652
3398
  snapTo: (id) => engine?.snapTo(id) ?? Promise.resolve(),
2653
3399
  open: (id) => engine?.open(id) ?? Promise.resolve(),
2654
- close: () => engine?.close() ?? Promise.resolve(),
3400
+ close: (reason) => engine?.close(reason) ?? Promise.resolve(),
2655
3401
  setAllowed: (ids, snap) => engine?.setAllowed(ids, snap),
2656
3402
  setSnapPoints: (points, allowed) => engine?.setSnapPoints(points, allowed),
2657
3403
  setScrim: (opts2) => engine?.setScrim(opts2),
2658
3404
  setScrimOverlay: (opts2) => engine?.setScrimOverlay(opts2) ?? (() => {
2659
3405
  }),
3406
+ addAnchor: (opts2) => engine?.addAnchor(opts2) ?? (() => {
3407
+ }),
3408
+ setScrimStages: (opts2) => engine?.setScrimStages(opts2) ?? (() => {
3409
+ }),
3410
+ recompute: () => engine?.recompute(),
3411
+ setScrimColor: (color) => engine?.setScrimColor(color),
3412
+ setBackdropRange: (range) => engine?.setBackdropRange(range),
3413
+ setRadius: (r) => engine?.setRadius(r),
3414
+ setMaxHeight: (h) => engine?.setMaxHeight(h),
3415
+ expand: () => engine?.expand() ?? Promise.resolve(),
3416
+ collapse: () => engine?.collapse() ?? Promise.resolve(),
3417
+ isTop: () => engine?.isTop() ?? false,
3418
+ depth: () => engine?.depth() ?? 0,
3419
+ canDismiss: () => engine?.canDismiss() ?? false,
2660
3420
  on: (event, fn) => {
2661
3421
  if (engine) return engine.on(event, fn);
2662
3422
  const entry = {
@@ -2792,6 +3552,35 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
2792
3552
  type: Boolean,
2793
3553
  required: false
2794
3554
  },
3555
+ persistent: {
3556
+ type: Boolean,
3557
+ required: false
3558
+ },
3559
+ disableClose: {
3560
+ type: Boolean,
3561
+ required: false
3562
+ },
3563
+ disableDrag: {
3564
+ type: Boolean,
3565
+ required: false
3566
+ },
3567
+ closeOnRouteChange: {
3568
+ type: Boolean,
3569
+ required: false
3570
+ },
3571
+ radius: {
3572
+ type: [String, Number],
3573
+ required: false
3574
+ },
3575
+ maxHeight: {
3576
+ type: [String, Number],
3577
+ required: false
3578
+ },
3579
+ returnFocusTo: {
3580
+ type: [String, Function],
3581
+ required: false,
3582
+ skipCheck: true
3583
+ },
2795
3584
  persistKey: {
2796
3585
  type: String,
2797
3586
  required: false
@@ -2808,6 +3597,10 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
2808
3597
  type: Boolean,
2809
3598
  required: false
2810
3599
  },
3600
+ stackEffect: {
3601
+ type: Boolean,
3602
+ required: false
3603
+ },
2811
3604
  snapPoints: {
2812
3605
  type: Array,
2813
3606
  required: true
@@ -2838,22 +3631,159 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
2838
3631
  type: String,
2839
3632
  required: false,
2840
3633
  default: "Bottom sheet"
3634
+ },
3635
+ open: {
3636
+ type: Boolean,
3637
+ required: false
3638
+ },
3639
+ snap: {
3640
+ type: null,
3641
+ required: false
3642
+ },
3643
+ teleport: {
3644
+ type: Boolean,
3645
+ required: false,
3646
+ default: true
3647
+ },
3648
+ teleportTo: {
3649
+ type: null,
3650
+ required: false,
3651
+ default: "body"
3652
+ },
3653
+ anchors: {
3654
+ type: Array,
3655
+ required: false
3656
+ },
3657
+ scrimStages: {
3658
+ type: [Object, null],
3659
+ required: false
3660
+ },
3661
+ backdropColor: {
3662
+ type: String,
3663
+ required: false
3664
+ },
3665
+ backdropOpacity: {
3666
+ type: Number,
3667
+ required: false
2841
3668
  }
2842
3669
  },
2843
3670
  emits: [
2844
3671
  "change",
2845
3672
  "snap",
2846
3673
  "open",
2847
- "close"
3674
+ "close",
3675
+ "opened",
3676
+ "closed",
3677
+ "before-close",
3678
+ "drag-start",
3679
+ "drag-end",
3680
+ "update:open",
3681
+ "update:snap"
2848
3682
  ],
2849
3683
  setup(__props, { expose: __expose, emit: __emit }) {
2850
3684
  const props = __props;
2851
3685
  const emit = __emit;
2852
- const { sheetRef, handleRef, contentRef, backdropRef, screenRef, state, snapTo, open, close, setAllowed, setSnapPoints, setScrim, setScrimOverlay, on, getEngine } = useBottomSheet(props);
3686
+ const { sheetRef, handleRef, contentRef, backdropRef, screenRef, state, snapTo, open, close, setAllowed, setSnapPoints, setScrim, setScrimOverlay, addAnchor, setScrimStages, recompute, setScrimColor, setBackdropRange, setRadius, setMaxHeight, expand, collapse, isTop, depth, canDismiss, on, getEngine } = useBottomSheet(props);
2853
3687
  watch(() => ({ ...state }), (next) => emit("change", next), { deep: false });
2854
- on("snap", (payload) => emit("snap", payload.id));
2855
- on("open", (payload) => emit("open", payload.id));
2856
- on("close", () => emit("close"));
3688
+ on("snap", (payload) => {
3689
+ emit("snap", payload.id);
3690
+ emit("update:snap", payload.id);
3691
+ });
3692
+ on("open", (payload) => {
3693
+ emit("open", payload.id);
3694
+ emit("update:open", true);
3695
+ });
3696
+ on("close", () => {
3697
+ emit("close");
3698
+ emit("update:open", false);
3699
+ });
3700
+ on("opened", (payload) => emit("opened", payload.id));
3701
+ on("closed", () => emit("closed"));
3702
+ on("before-close", (payload) => emit("before-close", payload));
3703
+ on("dragstart", (payload) => emit("drag-start", payload));
3704
+ on("dragend", (payload) => emit("drag-end", payload));
3705
+ watch(() => props.open, (next) => {
3706
+ if (next === void 0) return;
3707
+ const isOpen = state.size > 0;
3708
+ if (next && !isOpen) void open();
3709
+ else if (!next && isOpen) void close();
3710
+ });
3711
+ watch(() => props.snap, (next) => {
3712
+ if (next === void 0 || next === state.activeId) return;
3713
+ void snapTo(next);
3714
+ });
3715
+ watch(() => props.backdropColor, (next) => {
3716
+ if (next === void 0) return;
3717
+ setScrimColor(next);
3718
+ });
3719
+ watch(() => props.backdropOpacity, (next) => {
3720
+ if (next === void 0) return;
3721
+ setBackdropRange([0, next]);
3722
+ });
3723
+ watch(() => props.radius, (next) => {
3724
+ if (next === void 0) return;
3725
+ setRadius(next);
3726
+ });
3727
+ watch(() => props.maxHeight, (next) => {
3728
+ if (next === void 0) return;
3729
+ setMaxHeight(next);
3730
+ });
3731
+ const mounted = ref(false);
3732
+ const anchorHosts = shallowRef({});
3733
+ const anchorDetachers = ref([]);
3734
+ let stagesDetach = null;
3735
+ const teardownAnchors = () => {
3736
+ anchorDetachers.value.forEach((d) => d());
3737
+ anchorDetachers.value = [];
3738
+ anchorHosts.value = {};
3739
+ };
3740
+ const buildAnchors = () => {
3741
+ teardownAnchors();
3742
+ const engine = getEngine();
3743
+ if (!engine || !props.anchors?.length) return;
3744
+ const hosts = {};
3745
+ const detachers = [];
3746
+ for (const a of props.anchors) {
3747
+ const host = document.createElement("div");
3748
+ hosts[a.id] = host;
3749
+ const { id: _id, ...rest } = a;
3750
+ detachers.push(engine.addAnchor({
3751
+ ...rest,
3752
+ element: host
3753
+ }));
3754
+ }
3755
+ anchorHosts.value = hosts;
3756
+ anchorDetachers.value = detachers;
3757
+ };
3758
+ const anchorsKey = computed(() => JSON.stringify((props.anchors ?? []).map((a) => [
3759
+ a.id,
3760
+ a.position,
3761
+ a.inset,
3762
+ Array.isArray(a.showOn) ? a.showOn : a.showOn ? "fn" : null,
3763
+ a.fadeRange,
3764
+ a.interactive,
3765
+ typeof a.animation === "string" ? a.animation : a.animation ? "x" : null
3766
+ ])));
3767
+ const applyStages = () => {
3768
+ stagesDetach?.();
3769
+ stagesDetach = null;
3770
+ const engine = getEngine();
3771
+ if (!engine || !props.scrimStages) return;
3772
+ stagesDetach = engine.setScrimStages(props.scrimStages);
3773
+ };
3774
+ onMounted(() => {
3775
+ mounted.value = true;
3776
+ buildAnchors();
3777
+ applyStages();
3778
+ watch(anchorsKey, buildAnchors);
3779
+ watch(() => props.scrimStages, applyStages, { deep: true });
3780
+ if (props.open && state.size === 0) void open();
3781
+ });
3782
+ onBeforeUnmount(() => {
3783
+ teardownAnchors();
3784
+ stagesDetach?.();
3785
+ stagesDetach = null;
3786
+ });
2857
3787
  const isVerticalAxis = computed(() => {
2858
3788
  const mode = props.mode ?? "bottom";
2859
3789
  return mode === "bottom" || mode === "top";
@@ -2872,6 +3802,18 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
2872
3802
  setSnapPoints,
2873
3803
  setScrim,
2874
3804
  setScrimOverlay,
3805
+ addAnchor,
3806
+ setScrimStages,
3807
+ recompute,
3808
+ setScrimColor,
3809
+ setBackdropRange,
3810
+ setRadius,
3811
+ setMaxHeight,
3812
+ expand,
3813
+ collapse,
3814
+ isTop,
3815
+ depth,
3816
+ canDismiss,
2875
3817
  on,
2876
3818
  getEngine,
2877
3819
  state: readonly(state)
@@ -2892,8 +3834,33 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
2892
3834
  setSnapPoints,
2893
3835
  setScrim,
2894
3836
  setScrimOverlay,
3837
+ addAnchor,
3838
+ setScrimStages,
3839
+ recompute,
3840
+ setScrimColor,
3841
+ setBackdropRange,
3842
+ setRadius,
3843
+ setMaxHeight,
3844
+ expand,
3845
+ collapse,
3846
+ isTop,
3847
+ depth,
3848
+ canDismiss,
2895
3849
  on,
2896
3850
  getEngine,
3851
+ mounted,
3852
+ anchorHosts,
3853
+ anchorDetachers,
3854
+ get stagesDetach() {
3855
+ return stagesDetach;
3856
+ },
3857
+ set stagesDetach(v) {
3858
+ stagesDetach = v;
3859
+ },
3860
+ teardownAnchors,
3861
+ buildAnchors,
3862
+ anchorsKey,
3863
+ applyStages,
2897
3864
  isVerticalAxis,
2898
3865
  allowedIds,
2899
3866
  activeIdx,
@@ -2942,12 +3909,19 @@ var _hoisted_7 = {
2942
3909
  "aria-label": "Sheet content"
2943
3910
  };
2944
3911
  var _hoisted_8 = {
3912
+ key: 2,
3913
+ class: "bs-footer"
3914
+ };
3915
+ var _hoisted_9 = {
2945
3916
  class: "bs-sr-only",
2946
3917
  role: "status",
2947
3918
  "aria-live": "polite"
2948
3919
  };
2949
3920
  function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
2950
- return openBlock(), createElementBlock("div", _hoisted_1, [
3921
+ return openBlock(), createBlock(Teleport, {
3922
+ to: $props.teleportTo,
3923
+ disabled: !$props.teleport || !$setup.mounted
3924
+ }, [createElementVNode("div", _hoisted_1, [
2951
3925
  $props.backdrop ? (openBlock(), createElementBlock(
2952
3926
  "div",
2953
3927
  {
@@ -2955,13 +3929,12 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
2955
3929
  ref: "backdropRef",
2956
3930
  class: "bs-backdrop",
2957
3931
  "aria-hidden": "true",
2958
- onClick: _cache[0] || (_cache[0] = ($event) => $props.closeOnBackdrop ? $setup.close() : void 0)
3932
+ onClick: _cache[0] || (_cache[0] = ($event) => $props.closeOnBackdrop && $setup.canDismiss() ? $setup.close("backdrop") : void 0)
2959
3933
  },
2960
3934
  null,
2961
3935
  512
2962
3936
  /* NEED_PATCH */
2963
3937
  )) : createCommentVNode("v-if", true),
2964
- createCommentVNode("\n Scrim surface always renders so the engine can host scrim presets\n (color/blur via setScrim), overlay injections (setScrimOverlay),\n and progress-driven opacity. The `screen` slot, when used, renders\n inside as user content. Without a slot it stays an empty positioning\n context.\n "),
2965
3938
  createElementVNode(
2966
3939
  "div",
2967
3940
  _hoisted_2,
@@ -2999,17 +3972,30 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
2999
3972
  512
3000
3973
  /* NEED_PATCH */
3001
3974
  ),
3975
+ _ctx.$slots.footer ? (openBlock(), createElementBlock("div", _hoisted_8, [renderSlot(_ctx.$slots, "footer", { state: $setup.state })])) : createCommentVNode("v-if", true),
3002
3976
  createElementVNode(
3003
3977
  "span",
3004
- _hoisted_8,
3978
+ _hoisted_9,
3005
3979
  toDisplayString($setup.state.activeId),
3006
3980
  1
3007
3981
  /* TEXT */
3008
3982
  )
3009
3983
  ], 8, _hoisted_3)
3010
- ]);
3984
+ ]), (openBlock(true), createElementBlock(
3985
+ Fragment,
3986
+ null,
3987
+ renderList($setup.props.anchors ?? [], (a) => {
3988
+ return openBlock(), createBlock(Teleport, {
3989
+ key: a.id,
3990
+ to: $setup.anchorHosts[a.id],
3991
+ disabled: !$setup.anchorHosts[a.id]
3992
+ }, [renderSlot(_ctx.$slots, `anchor-${a.id}`, { state: $setup.state })], 8, ["to", "disabled"]);
3993
+ }),
3994
+ 128
3995
+ /* KEYED_FRAGMENT */
3996
+ ))], 8, ["to", "disabled"]);
3011
3997
  }
3012
- var BottomSheet_default = /* @__PURE__ */ export_helper_default(_sfc_main, [["render", _sfc_render], ["__file", "/Users/maksimkravcov/Desktop/\u043F\u0440\u043E\u0435\u043A\u0442\u044B/BottomSheet/src/vue/BottomSheet.vue"]]);
3998
+ var BottomSheet_default = /* @__PURE__ */ export_helper_default(_sfc_main, [["render", _sfc_render], ["__file", "/home/runner/work/BottomSheet/BottomSheet/src/vue/BottomSheet.vue"]]);
3013
3999
 
3014
4000
  export { BottomSheet_default as BottomSheet, useBottomSheet };
3015
4001
  //# sourceMappingURL=vue.js.map