@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/solid.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createSignal, onMount, onCleanup, createMemo } from 'solid-js';
2
- import { jsxs, jsx } from 'solid-js/jsx-runtime';
1
+ import { insert, memo, use, effect, setAttribute, template } from 'solid-js/web';
2
+ import { createSignal, onMount, onCleanup, createEffect, on, createMemo } from 'solid-js';
3
3
 
4
4
  var __defProp = Object.defineProperty;
5
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -23,16 +23,52 @@ var SheetStack = class {
23
23
  this.entries.splice(idx, 1);
24
24
  this.recompute();
25
25
  }
26
+ promote(id) {
27
+ const idx = this.entries.findIndex((e) => e.id === id);
28
+ if (idx === -1) return;
29
+ if (idx !== this.entries.length - 1) {
30
+ const [entry] = this.entries.splice(idx, 1);
31
+ this.entries.push(entry);
32
+ }
33
+ this.recompute();
34
+ }
35
+ update() {
36
+ this.recompute();
37
+ }
26
38
  size() {
27
39
  return this.entries.length;
28
40
  }
41
+ depthOf(id) {
42
+ const open = [];
43
+ for (const entry of this.entries) {
44
+ if (!entry.isOpen || entry.isOpen()) open.push(entry);
45
+ }
46
+ const idx = open.findIndex((e) => e.id === id);
47
+ if (idx === -1) return 0;
48
+ return open.length - 1 - idx;
49
+ }
29
50
  clear() {
30
51
  this.entries = [];
31
52
  }
32
53
  recompute() {
54
+ let top = null;
55
+ const open = [];
56
+ for (const entry of this.entries) {
57
+ if (!entry.isOpen || entry.isOpen()) {
58
+ top = entry;
59
+ open.push(entry);
60
+ }
61
+ }
62
+ if (!top && this.entries.length > 0) {
63
+ top = this.entries[this.entries.length - 1];
64
+ }
33
65
  this.entries.forEach((entry, i) => {
34
66
  entry.setZIndex(BASE_Z + i * STEP);
35
- entry.setIsTop(i === this.entries.length - 1);
67
+ entry.setIsTop(entry === top);
68
+ if (entry.setDepth) {
69
+ const openIdx = open.indexOf(entry);
70
+ entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
71
+ }
36
72
  });
37
73
  }
38
74
  };
@@ -129,7 +165,9 @@ var PERCENT = /^(-?\d+(?:\.\d+)?)%$/;
129
165
  var resolveSnap = (point, mode, measureFit) => {
130
166
  if (typeof point === "number") return Math.max(0, point);
131
167
  if (point === "full") return viewportSize(mode);
132
- if (point === "fit") return Math.max(0, measureFit?.() ?? 0);
168
+ if (point === "fit" || point === "content") {
169
+ return Math.max(0, measureFit?.() ?? 0);
170
+ }
133
171
  const pctMatch = point.match(PERCENT);
134
172
  if (pctMatch) {
135
173
  const pct = parseFloat(pctMatch[1]) / 100;
@@ -317,14 +355,17 @@ function installAutoCollapse(deps) {
317
355
  const fire = () => {
318
356
  if (deps.isDestroyed()) return;
319
357
  if (deps.isDragging()) return;
320
- const allowed = deps.getAllowedIds();
321
- const target = allowed.find((id) => {
358
+ const active = deps.resolveSnap(deps.getActiveId());
359
+ if (!active || active.size === 0) return;
360
+ let target = null;
361
+ for (const id of deps.getAllowedIds()) {
322
362
  const snap = deps.resolveSnap(id);
323
- return snap !== null && snap.size > 0;
324
- });
363
+ if (!snap || snap.size <= 0) continue;
364
+ if (!target || snap.size < target.size) target = snap;
365
+ }
325
366
  if (!target) return;
326
- if (target === deps.getActiveId()) return;
327
- deps.snapTo(target);
367
+ if (target.id === active.id || target.size >= active.size) return;
368
+ deps.snapTo(target.id);
328
369
  };
329
370
  const reset = () => {
330
371
  clearTimer();
@@ -335,6 +376,7 @@ function installAutoCollapse(deps) {
335
376
  }, ms);
336
377
  };
337
378
  const unsubscribe = deps.on("snap", () => reset());
379
+ if ((deps.resolveSnap(deps.getActiveId())?.size ?? 0) > 0) reset();
338
380
  return () => {
339
381
  clearTimer();
340
382
  unsubscribe();
@@ -347,6 +389,7 @@ function notifyLinkedSheets(sheets, self) {
347
389
  for (const linked of sheets) {
348
390
  if (linked === self) continue;
349
391
  try {
392
+ if (linked.state.size === 0) continue;
350
393
  const ids = linked.getAllowedIds();
351
394
  const target = ids.find((id) => id !== "closed");
352
395
  if (target && target !== linked.state.activeId) {
@@ -361,6 +404,42 @@ function notifyLinkedSheets(sheets, self) {
361
404
  }
362
405
 
363
406
  // src/core/features/route.ts
407
+ var closers = [];
408
+ var handlerInstalled = false;
409
+ var suppress = 0;
410
+ var ensureHandler = () => {
411
+ if (handlerInstalled || typeof window === "undefined") return;
412
+ handlerInstalled = true;
413
+ window.addEventListener("popstate", () => {
414
+ if (suppress > 0) {
415
+ suppress -= 1;
416
+ return;
417
+ }
418
+ let target = null;
419
+ for (let i = closers.length - 1; i >= 0; i -= 1) {
420
+ const c = closers[i];
421
+ if (!c.isOpen()) continue;
422
+ if (!target) target = c;
423
+ if (c.isTop()) {
424
+ target = c;
425
+ break;
426
+ }
427
+ }
428
+ if (!target) return;
429
+ target.consumed = true;
430
+ const idx = closers.indexOf(target);
431
+ if (idx !== -1) closers.splice(idx, 1);
432
+ target.close();
433
+ });
434
+ };
435
+ var programmaticBack = () => {
436
+ suppress += 1;
437
+ try {
438
+ history.back();
439
+ } catch {
440
+ suppress = Math.max(0, suppress - 1);
441
+ }
442
+ };
364
443
  function installRoute(deps) {
365
444
  if (typeof window === "undefined") {
366
445
  return () => {
@@ -370,82 +449,81 @@ function installRoute(deps) {
370
449
  return () => {
371
450
  };
372
451
  }
373
- let routePushed = false;
374
- let closeOnBackPushed = false;
375
- let detachPopstate = null;
376
- const popOurMarker = (key, expected) => {
377
- const state = history.state;
378
- if (state && state[key] === expected) {
379
- try {
380
- history.back();
381
- } catch {
382
- }
452
+ let pushed = false;
453
+ let closer = null;
454
+ const teardownEntry = () => {
455
+ if (closer) {
456
+ const idx = closers.indexOf(closer);
457
+ if (idx !== -1) closers.splice(idx, 1);
458
+ }
459
+ const wasConsumed = closer?.consumed ?? false;
460
+ closer = null;
461
+ if (pushed && !wasConsumed) {
462
+ pushed = false;
463
+ programmaticBack();
464
+ } else {
465
+ pushed = false;
383
466
  }
384
467
  };
385
468
  const onOpen = () => {
386
- if (deps.routedTo && !detachPopstate) {
387
- const target = deps.routedTo;
388
- try {
389
- history.pushState({ __bsRouted: target }, "", target);
390
- routePushed = true;
391
- } catch {
392
- }
393
- const onPop = (event) => {
394
- if (deps.isDestroyed()) return;
395
- const state = event.state;
396
- if (state && state.__bsRouted === target && deps.getSize() > 0) {
397
- routePushed = false;
398
- void deps.close();
399
- }
400
- };
401
- window.addEventListener("popstate", onPop);
402
- detachPopstate = () => window.removeEventListener("popstate", onPop);
403
- } else if (deps.closeOnBack && !detachPopstate) {
404
- try {
469
+ if (closer) return;
470
+ closer = {
471
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
472
+ isTop: () => deps.isTopSheet(),
473
+ close: () => void deps.close(),
474
+ consumed: false
475
+ };
476
+ ensureHandler();
477
+ closers.push(closer);
478
+ try {
479
+ if (deps.routedTo !== void 0) {
480
+ history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
481
+ } else {
405
482
  history.pushState({ __bsSheet: deps.sheetId }, "");
406
- closeOnBackPushed = true;
407
- } catch {
408
483
  }
409
- const onPop = () => {
410
- if (deps.isDestroyed()) return;
411
- if (deps.getSize() > 0 && deps.isTopSheet()) {
412
- const state = history.state;
413
- if (state?.__bsSheet !== deps.sheetId) closeOnBackPushed = false;
414
- void deps.close();
415
- }
416
- };
417
- window.addEventListener("popstate", onPop);
418
- detachPopstate = () => window.removeEventListener("popstate", onPop);
419
- }
420
- };
421
- const onClose = () => {
422
- detachPopstate?.();
423
- detachPopstate = null;
424
- if (routePushed) {
425
- routePushed = false;
426
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
427
- }
428
- if (closeOnBackPushed) {
429
- closeOnBackPushed = false;
430
- popOurMarker("__bsSheet", deps.sheetId);
484
+ pushed = true;
485
+ } catch {
486
+ pushed = false;
431
487
  }
432
488
  };
433
489
  const unsubscribeOpen = deps.on("open", onOpen);
434
- const unsubscribeClose = deps.on("close", onClose);
490
+ const unsubscribeClose = deps.on("close", teardownEntry);
435
491
  if (deps.getSize() > 0) onOpen();
436
492
  return () => {
437
493
  unsubscribeOpen();
438
494
  unsubscribeClose();
439
- detachPopstate?.();
440
- detachPopstate = null;
441
- if (routePushed) {
442
- routePushed = false;
443
- if (deps.routedTo !== void 0) popOurMarker("__bsRouted", deps.routedTo);
444
- }
445
- if (closeOnBackPushed) {
446
- closeOnBackPushed = false;
447
- popOurMarker("__bsSheet", deps.sheetId);
448
- }
495
+ teardownEntry();
496
+ };
497
+ }
498
+ function installRouteChange(deps) {
499
+ if (typeof window === "undefined" || typeof history === "undefined") {
500
+ return () => {
501
+ };
502
+ }
503
+ const onChange = () => {
504
+ if (deps.isDestroyed() || deps.getSize() <= 0) return;
505
+ void deps.close();
506
+ };
507
+ const wrap = (key) => {
508
+ const original = history[key];
509
+ const patched = function(...args) {
510
+ original.apply(this, args);
511
+ onChange();
512
+ };
513
+ history[key] = patched;
514
+ return () => {
515
+ if (history[key] === patched) {
516
+ history[key] = original;
517
+ }
518
+ };
519
+ };
520
+ window.addEventListener("popstate", onChange);
521
+ const restorePush = wrap("pushState");
522
+ const restoreReplace = wrap("replaceState");
523
+ return () => {
524
+ window.removeEventListener("popstate", onChange);
525
+ restorePush();
526
+ restoreReplace();
449
527
  };
450
528
  }
451
529
 
@@ -547,13 +625,17 @@ function installResizeObserver(deps) {
547
625
  if (viewportSize2 > 0 && viewportSize2 < max) {
548
626
  deps.setMaxAxisSize(viewportSize2);
549
627
  }
628
+ const wasAnimating = deps.isAnimating?.() ?? false;
550
629
  deps.cancelInFlight();
551
630
  deps.newCycle();
552
631
  const current = deps.resolveActiveSnap();
553
632
  const newMax = deps.getMaxAxisSize();
554
633
  const targetSize = current ? Math.min(current.size, newMax) : Math.min(deps.getSize(), newMax);
555
634
  deps.setSize(targetSize);
556
- if (current && !deps.isDragging()) deps.applySize(targetSize);
635
+ if (current && !deps.isDragging()) {
636
+ deps.applySize(targetSize);
637
+ if (wasAnimating) deps.resyncAfterCancel?.();
638
+ }
557
639
  };
558
640
  let resizeObserver = null;
559
641
  if (typeof ResizeObserver !== "undefined") {
@@ -668,8 +750,88 @@ function devWarn(...args) {
668
750
  }
669
751
  }
670
752
 
753
+ // src/core/primitives/overlay-position.ts
754
+ function resolveSheetAnchoredStyle(mode, position, inset) {
755
+ const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
756
+ switch (mode) {
757
+ case "bottom":
758
+ if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
759
+ if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
760
+ return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
761
+ case "top":
762
+ if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
763
+ if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
764
+ return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
765
+ case "left":
766
+ if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
767
+ if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
768
+ return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
769
+ case "right":
770
+ if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
771
+ if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
772
+ return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
773
+ }
774
+ }
775
+ function applyOverlayPosition(ws, mode, position, inset) {
776
+ switch (position) {
777
+ case "top-left":
778
+ ws.top = inset;
779
+ ws.left = inset;
780
+ break;
781
+ case "top-center":
782
+ ws.top = inset;
783
+ ws.left = "50%";
784
+ ws.transform = "translateX(-50%)";
785
+ break;
786
+ case "top-right":
787
+ ws.top = inset;
788
+ ws.right = inset;
789
+ break;
790
+ case "center-left":
791
+ ws.top = "50%";
792
+ ws.left = inset;
793
+ ws.transform = "translateY(-50%)";
794
+ break;
795
+ case "center":
796
+ ws.top = "50%";
797
+ ws.left = "50%";
798
+ ws.transform = "translate(-50%, -50%)";
799
+ break;
800
+ case "center-right":
801
+ ws.top = "50%";
802
+ ws.right = inset;
803
+ ws.transform = "translateY(-50%)";
804
+ break;
805
+ case "bottom-left":
806
+ ws.bottom = inset;
807
+ ws.left = inset;
808
+ break;
809
+ case "bottom-center":
810
+ ws.bottom = inset;
811
+ ws.left = "50%";
812
+ ws.transform = "translateX(-50%)";
813
+ break;
814
+ case "bottom-right":
815
+ ws.bottom = inset;
816
+ ws.right = inset;
817
+ break;
818
+ case "sheet-top-left":
819
+ case "sheet-top-center":
820
+ case "sheet-top-right": {
821
+ const style = resolveSheetAnchoredStyle(mode, position, inset);
822
+ if (style.top !== void 0) ws.top = style.top;
823
+ if (style.bottom !== void 0) ws.bottom = style.bottom;
824
+ if (style.left !== void 0) ws.left = style.left;
825
+ if (style.right !== void 0) ws.right = style.right;
826
+ if (style.transform !== void 0) ws.transform = style.transform;
827
+ break;
828
+ }
829
+ }
830
+ }
831
+
671
832
  // src/core/controllers/scrim-controller.ts
672
833
  var VALID_CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|em|rem|%)$/;
834
+ var DEFAULT_SCRIM_DIM = "rgba(0, 0, 0, 0.4)";
673
835
  var ScrimController = class {
674
836
  constructor(deps, opts) {
675
837
  __publicField(this, "screenComponent");
@@ -680,6 +842,8 @@ var ScrimController = class {
680
842
  __publicField(this, "getAllowedIds");
681
843
  __publicField(this, "getResolvedSnaps");
682
844
  __publicField(this, "snapToFn");
845
+ __publicField(this, "closeFn");
846
+ __publicField(this, "scrimInteractive", false);
683
847
  __publicField(this, "scrimMode");
684
848
  __publicField(this, "backdropRange");
685
849
  __publicField(this, "screenRange");
@@ -700,14 +864,17 @@ var ScrimController = class {
700
864
  this.getAllowedIds = deps.getAllowedIds;
701
865
  this.getResolvedSnaps = deps.getResolvedSnaps;
702
866
  this.snapToFn = deps.snapTo;
867
+ this.closeFn = deps.close;
703
868
  this.scrimMode = opts.scrimMode ?? "full";
704
869
  const preset = opts.scrimPreset ? SCRIM_PRESETS[opts.scrimPreset] : null;
705
870
  this.backdropRange = opts.backdropRange ?? preset?.range ?? [0, 1];
706
871
  this.screenRange = opts.screenRange ?? preset?.range ?? [0, 1];
707
872
  this.scrimTapToCloseEnabled = opts.scrimTapToClose ?? false;
873
+ const explicitColor = opts.scrimColor ?? preset?.color;
874
+ const effectiveColor = explicitColor ?? (this.scrimMode !== "off" && !this.backdrop ? DEFAULT_SCRIM_DIM : void 0);
708
875
  if (this.screenComponent) {
709
876
  this.applyScrimStyles({
710
- color: opts.scrimColor ?? preset?.color,
877
+ color: effectiveColor,
711
878
  blur: opts.scrimBlur ?? preset?.blur,
712
879
  interactive: opts.scrimInteractive ?? preset?.interactive ?? false
713
880
  });
@@ -841,11 +1008,15 @@ var ScrimController = class {
841
1008
  }
842
1009
  const onClick = () => {
843
1010
  if (this.isDestroyed()) return;
1011
+ if (this.closeFn) {
1012
+ this.closeFn();
1013
+ return;
1014
+ }
844
1015
  const allowed = this.getAllowedIds();
845
1016
  const snaps = this.getResolvedSnaps();
846
1017
  const fallback = allowed.find((id) => {
847
1018
  const snap = findById(id, snaps);
848
- return snap !== null && snap.size > 0;
1019
+ return snap !== null && snap.size === 0;
849
1020
  });
850
1021
  if (fallback) this.snapToFn(fallback);
851
1022
  };
@@ -862,6 +1033,7 @@ var ScrimController = class {
862
1033
  if (this.isDestroyed()) return;
863
1034
  if (enabled === this.scrimEnabled) return;
864
1035
  this.scrimEnabled = enabled;
1036
+ this.invalidateOpacityCache();
865
1037
  if (!enabled) {
866
1038
  this.savedScrimRanges = {
867
1039
  screen: this.screenRange,
@@ -885,7 +1057,12 @@ var ScrimController = class {
885
1057
  blur: cfg.blur ?? null,
886
1058
  interactive: cfg.interactive
887
1059
  });
888
- this.screenRange = cfg.range;
1060
+ if (!this.scrimEnabled && this.savedScrimRanges) {
1061
+ this.savedScrimRanges = { screen: cfg.range, backdrop: cfg.range };
1062
+ } else {
1063
+ this.screenRange = cfg.range;
1064
+ this.backdropRange = cfg.range;
1065
+ }
889
1066
  }
890
1067
  const noop = () => {
891
1068
  };
@@ -935,59 +1112,7 @@ var ScrimController = class {
935
1112
  wrapper.className = "bs-scrim-overlay";
936
1113
  const ws = wrapper.style;
937
1114
  ws.position = "absolute";
938
- switch (position) {
939
- case "top-left":
940
- ws.top = inset;
941
- ws.left = inset;
942
- break;
943
- case "top-center":
944
- ws.top = inset;
945
- ws.left = "50%";
946
- ws.transform = "translateX(-50%)";
947
- break;
948
- case "top-right":
949
- ws.top = inset;
950
- ws.right = inset;
951
- break;
952
- case "center-left":
953
- ws.top = "50%";
954
- ws.left = inset;
955
- ws.transform = "translateY(-50%)";
956
- break;
957
- case "center":
958
- ws.top = "50%";
959
- ws.left = "50%";
960
- ws.transform = "translate(-50%, -50%)";
961
- break;
962
- case "center-right":
963
- ws.top = "50%";
964
- ws.right = inset;
965
- ws.transform = "translateY(-50%)";
966
- break;
967
- case "bottom-left":
968
- ws.bottom = inset;
969
- ws.left = inset;
970
- break;
971
- case "bottom-center":
972
- ws.bottom = inset;
973
- ws.left = "50%";
974
- ws.transform = "translateX(-50%)";
975
- break;
976
- case "bottom-right":
977
- ws.bottom = inset;
978
- ws.right = inset;
979
- break;
980
- case "sheet-top-left":
981
- case "sheet-top-center":
982
- case "sheet-top-right":
983
- applySheetAnchoredPosition(
984
- ws,
985
- this.mode,
986
- position,
987
- inset
988
- );
989
- break;
990
- }
1115
+ applyOverlayPosition(ws, this.mode, position, inset);
991
1116
  if (interactive) {
992
1117
  ws.pointerEvents = "auto";
993
1118
  }
@@ -1046,6 +1171,7 @@ var ScrimController = class {
1046
1171
  }
1047
1172
  }
1048
1173
  if (opts.interactive !== void 0) {
1174
+ this.scrimInteractive = opts.interactive;
1049
1175
  s.pointerEvents = opts.interactive ? "auto" : "none";
1050
1176
  }
1051
1177
  }
@@ -1060,38 +1186,9 @@ var ScrimController = class {
1060
1186
  const s = this.screenComponent.style;
1061
1187
  s.position = "fixed";
1062
1188
  s.inset = insetByMode[this.mode];
1063
- s.pointerEvents = "none";
1189
+ s.pointerEvents = this.scrimInteractive || this.scrimTapToCloseEnabled ? "auto" : "none";
1064
1190
  }
1065
1191
  };
1066
- function resolveSheetAnchoredStyle(mode, position, inset) {
1067
- const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
1068
- switch (mode) {
1069
- case "bottom":
1070
- if (position === "sheet-top-left") return { bottom: sizeOffset, left: inset };
1071
- if (position === "sheet-top-right") return { bottom: sizeOffset, right: inset };
1072
- return { bottom: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1073
- case "top":
1074
- if (position === "sheet-top-left") return { top: sizeOffset, left: inset };
1075
- if (position === "sheet-top-right") return { top: sizeOffset, right: inset };
1076
- return { top: sizeOffset, left: "50%", transform: "translateX(-50%)" };
1077
- case "left":
1078
- if (position === "sheet-top-left") return { left: sizeOffset, top: inset };
1079
- if (position === "sheet-top-right") return { left: sizeOffset, bottom: inset };
1080
- return { left: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1081
- case "right":
1082
- if (position === "sheet-top-left") return { right: sizeOffset, top: inset };
1083
- if (position === "sheet-top-right") return { right: sizeOffset, bottom: inset };
1084
- return { right: sizeOffset, top: "50%", transform: "translateY(-50%)" };
1085
- }
1086
- }
1087
- function applySheetAnchoredPosition(ws, mode, position, inset) {
1088
- const style = resolveSheetAnchoredStyle(mode, position, inset);
1089
- if (style.top !== void 0) ws.top = style.top;
1090
- if (style.bottom !== void 0) ws.bottom = style.bottom;
1091
- if (style.left !== void 0) ws.left = style.left;
1092
- if (style.right !== void 0) ws.right = style.right;
1093
- if (style.transform !== void 0) ws.transform = style.transform;
1094
- }
1095
1192
 
1096
1193
  // src/core/animation/animation.ts
1097
1194
  var easeOutBack = (t) => {
@@ -1143,6 +1240,10 @@ var tween = ({
1143
1240
  promise
1144
1241
  };
1145
1242
  };
1243
+ var prefersReducedMotion = () => {
1244
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1245
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1246
+ };
1146
1247
 
1147
1248
  // src/core/animation/spring.ts
1148
1249
  var DEFAULT_SPRING = {
@@ -1173,14 +1274,20 @@ var runSpring = ({
1173
1274
  if (cancelled) return;
1174
1275
  const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
1175
1276
  lastTime = now;
1176
- const subDt = Math.min(dt, 1 / 240);
1177
- let stepsLeft = Math.max(1, Math.ceil(dt / subDt));
1277
+ const steps = Math.max(1, Math.ceil(dt * 240));
1278
+ const subDt = dt / steps;
1279
+ let stepsLeft = steps;
1178
1280
  while (stepsLeft-- > 0) {
1179
1281
  const force = -cfg.stiffness * (x - to) - cfg.damping * v;
1180
1282
  const accel = force / cfg.mass;
1181
1283
  v += accel * subDt;
1182
1284
  x += v * subDt;
1183
1285
  }
1286
+ if (!Number.isFinite(x) || !Number.isFinite(v)) {
1287
+ onUpdate(to, 0);
1288
+ resolve();
1289
+ return;
1290
+ }
1184
1291
  onUpdate(x, v);
1185
1292
  const isAtRest = Math.abs(to - x) < (cfg.restDelta ?? 0.5) && Math.abs(v) < (cfg.restSpeed ?? 0.5);
1186
1293
  if (isAtRest) {
@@ -1355,9 +1462,13 @@ var isVisible = (el) => {
1355
1462
  var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1356
1463
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1357
1464
  );
1465
+ var trapStack = [];
1358
1466
  var installFocusTrap = (container, options = {}) => {
1359
1467
  if (typeof document === "undefined") return () => {
1360
1468
  };
1469
+ const token = {};
1470
+ trapStack.push(token);
1471
+ const isActive = () => trapStack[trapStack.length - 1] === token;
1361
1472
  const previouslyFocused = document.activeElement;
1362
1473
  const focusInitial = () => {
1363
1474
  const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
@@ -1365,6 +1476,7 @@ var installFocusTrap = (container, options = {}) => {
1365
1476
  };
1366
1477
  focusInitial();
1367
1478
  const handleKey = (e) => {
1479
+ if (!isActive()) return;
1368
1480
  if (e.key === "Escape" && options.onEscape) {
1369
1481
  options.onEscape();
1370
1482
  return;
@@ -1387,6 +1499,7 @@ var installFocusTrap = (container, options = {}) => {
1387
1499
  }
1388
1500
  };
1389
1501
  const handleFocusIn = (e) => {
1502
+ if (!isActive()) return;
1390
1503
  const target = e.target;
1391
1504
  if (target && !container.contains(target)) {
1392
1505
  const list = focusables(container);
@@ -1399,6 +1512,8 @@ var installFocusTrap = (container, options = {}) => {
1399
1512
  return () => {
1400
1513
  if (released) return;
1401
1514
  released = true;
1515
+ const idx = trapStack.indexOf(token);
1516
+ if (idx !== -1) trapStack.splice(idx, 1);
1402
1517
  document.removeEventListener("keydown", handleKey, true);
1403
1518
  document.removeEventListener("focusin", handleFocusIn, true);
1404
1519
  if (previouslyFocused && document.contains(previouslyFocused)) {
@@ -1454,11 +1569,17 @@ function createInertSiblings(rootProvider) {
1454
1569
  return {
1455
1570
  apply() {
1456
1571
  if (typeof document === "undefined") return;
1457
- const root = rootProvider();
1458
- const parent = root.parentElement ?? document.body;
1572
+ let top = rootProvider();
1573
+ while (top.parentElement && top.parentElement !== document.body) {
1574
+ top = top.parentElement;
1575
+ }
1576
+ const parent = top.parentElement ?? document.body;
1459
1577
  for (const child of Array.from(parent.children)) {
1460
- if (child === root) continue;
1578
+ if (child === top) continue;
1461
1579
  if (child.hasAttribute("inert")) continue;
1580
+ if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
1581
+ continue;
1582
+ }
1462
1583
  child.setAttribute("inert", "");
1463
1584
  tracked.push(child);
1464
1585
  }
@@ -1474,7 +1595,6 @@ function createInertSiblings(rootProvider) {
1474
1595
  var LifecycleController = class {
1475
1596
  constructor(deps, opts) {
1476
1597
  __publicField(this, "element");
1477
- __publicField(this, "closeFn");
1478
1598
  __publicField(this, "focusTrapEnabled");
1479
1599
  __publicField(this, "initialFocus");
1480
1600
  __publicField(this, "closeOnEscape");
@@ -1488,7 +1608,6 @@ var LifecycleController = class {
1488
1608
  __publicField(this, "installed", false);
1489
1609
  __publicField(this, "destroyed", false);
1490
1610
  this.element = deps.element;
1491
- this.closeFn = deps.close;
1492
1611
  this.focusTrapEnabled = opts.focusTrap ?? false;
1493
1612
  this.initialFocus = opts.initialFocus;
1494
1613
  this.closeOnEscape = opts.closeOnEscape ?? true;
@@ -1498,6 +1617,9 @@ var LifecycleController = class {
1498
1617
  this.returnFocus = opts.returnFocus;
1499
1618
  this.inertSiblings = createInertSiblings(() => this.element);
1500
1619
  }
1620
+ get isInstalled() {
1621
+ return this.installed;
1622
+ }
1501
1623
  install() {
1502
1624
  if (this.destroyed) return;
1503
1625
  this.installed = true;
@@ -1506,8 +1628,7 @@ var LifecycleController = class {
1506
1628
  }
1507
1629
  if (this.focusTrapEnabled && !this.releaseFocusTrap) {
1508
1630
  this.releaseFocusTrap = installFocusTrap(this.element, {
1509
- initialFocus: this.initialFocus,
1510
- onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
1631
+ initialFocus: this.initialFocus
1511
1632
  });
1512
1633
  }
1513
1634
  if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
@@ -1527,7 +1648,7 @@ var LifecycleController = class {
1527
1648
  this.releaseScrollLock = null;
1528
1649
  this.inertSiblings.remove();
1529
1650
  if (this.returnFocus) {
1530
- const target = typeof this.returnFocus === "function" ? this.returnFocus() : this.returnFocus;
1651
+ const target = typeof this.returnFocus === "function" ? this.returnFocus() : typeof this.returnFocus === "string" ? typeof document !== "undefined" ? document.querySelector(this.returnFocus) : null : this.returnFocus;
1531
1652
  target?.focus?.();
1532
1653
  }
1533
1654
  }
@@ -1661,10 +1782,11 @@ var installGestures = (handle, mode, callbacks) => {
1661
1782
  };
1662
1783
 
1663
1784
  // src/core/primitives/rubber-band.ts
1664
- var RUBBER_BAND_FACTOR = 0.55;
1665
1785
  function rubberBand(overshoot, maxAxisSize) {
1666
1786
  const cap = Math.min(maxAxisSize * 0.15, 80);
1667
- return Math.sign(overshoot) * Math.pow(Math.abs(overshoot), RUBBER_BAND_FACTOR) * Math.min(6, cap / Math.max(Math.abs(overshoot), 1));
1787
+ if (cap <= 0) return 0;
1788
+ const abs = Math.abs(overshoot);
1789
+ return Math.sign(overshoot) * (cap * abs / (abs + cap));
1668
1790
  }
1669
1791
 
1670
1792
  // src/core/features/soft-keyboard.ts
@@ -1693,6 +1815,7 @@ var GestureController = class {
1693
1815
  __publicField(this, "dragStartSize", 0);
1694
1816
  __publicField(this, "currentPointerKind", "touch");
1695
1817
  __publicField(this, "keyboardDismissed", false);
1818
+ __publicField(this, "dragSuppressed", false);
1696
1819
  __publicField(this, "dragPayload", { size: 0, delta: 0 });
1697
1820
  this.deps = deps;
1698
1821
  }
@@ -1703,6 +1826,11 @@ var GestureController = class {
1703
1826
  if (this.detach) return this.detach;
1704
1827
  this.detach = installGestures(this.deps.handle, this.deps.mode, {
1705
1828
  onStart: (_coord, kind) => {
1829
+ if (this.deps.getDisableDrag?.()) {
1830
+ this.dragSuppressed = true;
1831
+ return;
1832
+ }
1833
+ this.dragSuppressed = false;
1706
1834
  this.deps.cancelAnimation();
1707
1835
  this.isDragging_ = true;
1708
1836
  this.dragStartSize = this.deps.getDragContext().size;
@@ -1713,6 +1841,7 @@ var GestureController = class {
1713
1841
  this.deps.emit("dragstart", { size: this.dragStartSize });
1714
1842
  },
1715
1843
  onMove: (delta) => {
1844
+ if (this.dragSuppressed) return;
1716
1845
  const ctx = this.deps.getDragContext();
1717
1846
  const { min, max } = ctx.range;
1718
1847
  const maxAxis = ctx.maxAxisSize;
@@ -1736,6 +1865,10 @@ var GestureController = class {
1736
1865
  }
1737
1866
  },
1738
1867
  onEnd: (delta, velocity, kind) => {
1868
+ if (this.dragSuppressed) {
1869
+ this.dragSuppressed = false;
1870
+ return;
1871
+ }
1739
1872
  this.isDragging_ = false;
1740
1873
  this.keyboardDismissed = false;
1741
1874
  this.deps.getRoot()?.removeAttribute("data-dragging");
@@ -1746,9 +1879,17 @@ var GestureController = class {
1746
1879
  this.deps.settleAfterDrag(delta, velocity, kind);
1747
1880
  },
1748
1881
  onCancel: () => {
1882
+ if (this.dragSuppressed) {
1883
+ this.dragSuppressed = false;
1884
+ return;
1885
+ }
1749
1886
  this.isDragging_ = false;
1750
1887
  this.keyboardDismissed = false;
1751
1888
  this.deps.getRoot()?.removeAttribute("data-dragging");
1889
+ this.deps.emit("dragend", {
1890
+ size: this.deps.getDragContext().size,
1891
+ velocity: 0
1892
+ });
1752
1893
  const restoreTo = this.dragStartSize;
1753
1894
  void this.deps.animateTo(restoreTo, 0);
1754
1895
  }
@@ -1822,6 +1963,287 @@ function createScrollCache(deps) {
1822
1963
  };
1823
1964
  }
1824
1965
 
1966
+ // src/core/primitives/anchor-animations.ts
1967
+ var DEFAULT_DURATION3 = 200;
1968
+ var DEFAULT_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
1969
+ var PRESET_FRAMES = {
1970
+ fade: {
1971
+ enter: [{ opacity: 0 }, { opacity: 1 }],
1972
+ exit: [{ opacity: 1 }, { opacity: 0 }]
1973
+ },
1974
+ scale: {
1975
+ enter: [
1976
+ { opacity: 0, transform: "scale(0.85)" },
1977
+ { opacity: 1, transform: "scale(1)" }
1978
+ ],
1979
+ exit: [
1980
+ { opacity: 1, transform: "scale(1)" },
1981
+ { opacity: 0, transform: "scale(0.85)" }
1982
+ ]
1983
+ },
1984
+ slide: {
1985
+ enter: [
1986
+ { opacity: 0, transform: "translateY(10px)" },
1987
+ { opacity: 1, transform: "translateY(0)" }
1988
+ ],
1989
+ exit: [
1990
+ { opacity: 1, transform: "translateY(0)" },
1991
+ { opacity: 0, transform: "translateY(10px)" }
1992
+ ]
1993
+ },
1994
+ pop: {
1995
+ enter: [
1996
+ { opacity: 0, transform: "scale(0.6)", offset: 0 },
1997
+ { opacity: 1, transform: "scale(1.06)", offset: 0.7 },
1998
+ { opacity: 1, transform: "scale(1)", offset: 1 }
1999
+ ],
2000
+ exit: [
2001
+ { opacity: 1, transform: "scale(1)" },
2002
+ { opacity: 0, transform: "scale(0.6)" }
2003
+ ]
2004
+ }
2005
+ };
2006
+ var INSTANT = {
2007
+ finished: Promise.resolve(),
2008
+ cancel: () => {
2009
+ }
2010
+ };
2011
+ function resolveTransitionFrames(spec, phase) {
2012
+ const fallback = { frames: null, duration: 0, easing: DEFAULT_EASING };
2013
+ if (spec === "none") return fallback;
2014
+ if (spec === void 0 || typeof spec === "string") {
2015
+ const preset2 = PRESET_FRAMES[spec ?? "fade"];
2016
+ return {
2017
+ frames: preset2[phase],
2018
+ duration: DEFAULT_DURATION3,
2019
+ easing: DEFAULT_EASING
2020
+ };
2021
+ }
2022
+ const custom = phase === "enter" ? spec.enter : spec.exit;
2023
+ if (custom) {
2024
+ return {
2025
+ frames: custom,
2026
+ duration: spec.duration ?? DEFAULT_DURATION3,
2027
+ easing: spec.easing ?? DEFAULT_EASING
2028
+ };
2029
+ }
2030
+ if (spec.preset === "none") return fallback;
2031
+ const preset = PRESET_FRAMES[spec.preset ?? "fade"];
2032
+ return {
2033
+ frames: preset[phase],
2034
+ duration: spec.duration ?? DEFAULT_DURATION3,
2035
+ easing: spec.easing ?? DEFAULT_EASING
2036
+ };
2037
+ }
2038
+ function runAnchorTransition(el, spec, phase) {
2039
+ const respectReduced = typeof spec === "object" ? spec.respectReducedMotion !== false : true;
2040
+ if (respectReduced && prefersReducedMotion()) return INSTANT;
2041
+ const { frames, duration, easing } = resolveTransitionFrames(spec, phase);
2042
+ if (!frames || duration <= 0) return INSTANT;
2043
+ if (typeof el.animate !== "function") return INSTANT;
2044
+ const animation = el.animate(frames, {
2045
+ duration,
2046
+ easing,
2047
+ fill: "both"
2048
+ });
2049
+ const finished = animation.finished.then(() => void 0).catch(() => void 0);
2050
+ return {
2051
+ finished,
2052
+ cancel: () => {
2053
+ try {
2054
+ animation.cancel();
2055
+ } catch {
2056
+ return;
2057
+ }
2058
+ }
2059
+ };
2060
+ }
2061
+
2062
+ // src/core/features/sheet-anchors.ts
2063
+ function attachAnchor(deps, opts) {
2064
+ const doc = deps.host.ownerDocument;
2065
+ const wrapper = doc.createElement("div");
2066
+ wrapper.className = "bs-anchor";
2067
+ const interactive = opts.interactive ?? true;
2068
+ const position = opts.position ?? "sheet-top-right";
2069
+ const isDock = position === "dock-bottom" || position === "dock-top";
2070
+ const ws = wrapper.style;
2071
+ ws.position = "fixed";
2072
+ if (isDock) {
2073
+ ws.left = "0";
2074
+ ws.right = "0";
2075
+ if (position === "dock-bottom") {
2076
+ ws.bottom = "0";
2077
+ } else {
2078
+ ws.top = "0";
2079
+ }
2080
+ } else {
2081
+ applyOverlayPosition(ws, deps.mode, position, opts.inset ?? "16px");
2082
+ }
2083
+ if (opts.fadeRange) {
2084
+ const [r0, r1] = opts.fadeRange;
2085
+ const span = Math.max(r1 - r0, 1e-4);
2086
+ ws.opacity = `clamp(0, calc((var(--bs-progress, 0) - ${r0}) / ${span}), 1)`;
2087
+ }
2088
+ wrapper.appendChild(opts.element);
2089
+ deps.host.appendChild(wrapper);
2090
+ let visible = null;
2091
+ let inFlight = null;
2092
+ const shouldShow = (state) => {
2093
+ if (typeof opts.showOn === "function") return opts.showOn(state);
2094
+ if (Array.isArray(opts.showOn)) return opts.showOn.includes(state.activeId);
2095
+ return isDock || state.size > 0;
2096
+ };
2097
+ const applyVisibility = (next, animate) => {
2098
+ if (next === visible) return;
2099
+ visible = next;
2100
+ inFlight?.cancel();
2101
+ inFlight = null;
2102
+ wrapper.style.pointerEvents = next && interactive ? "auto" : "none";
2103
+ if (next) {
2104
+ wrapper.style.visibility = "";
2105
+ if (animate) {
2106
+ inFlight = runAnchorTransition(opts.element, opts.animation, "enter");
2107
+ }
2108
+ return;
2109
+ }
2110
+ if (!animate) {
2111
+ wrapper.style.visibility = "hidden";
2112
+ return;
2113
+ }
2114
+ const handle = runAnchorTransition(opts.element, opts.animation, "exit");
2115
+ inFlight = handle;
2116
+ void handle.finished.then(() => {
2117
+ if (inFlight === handle && visible === false) {
2118
+ wrapper.style.visibility = "hidden";
2119
+ }
2120
+ });
2121
+ };
2122
+ const evaluate = (animate) => {
2123
+ if (deps.isDestroyed()) return;
2124
+ applyVisibility(shouldShow(deps.getState()), animate);
2125
+ };
2126
+ const offs = [
2127
+ deps.on("snap", () => evaluate(true)),
2128
+ deps.on("open", () => evaluate(true)),
2129
+ deps.on("close", () => evaluate(true))
2130
+ ];
2131
+ evaluate(false);
2132
+ return {
2133
+ wrapper,
2134
+ detach: () => {
2135
+ offs.forEach((off) => off());
2136
+ inFlight?.cancel();
2137
+ inFlight = null;
2138
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
2139
+ },
2140
+ syncZ: (z) => {
2141
+ wrapper.style.zIndex = String(z);
2142
+ }
2143
+ };
2144
+ }
2145
+
2146
+ // src/core/features/scrim-stages.ts
2147
+ var matchesId = (def, activeId) => {
2148
+ if (def.for === void 0) return false;
2149
+ return Array.isArray(def.for) ? def.for.includes(activeId) : def.for === activeId;
2150
+ };
2151
+ var matchesRange = (def, progress) => {
2152
+ if (!def.forRange) return false;
2153
+ const [from, to] = def.forRange;
2154
+ return progress >= from && progress <= to;
2155
+ };
2156
+ function installScrimStages(deps, opts) {
2157
+ const doc = deps.host.ownerDocument;
2158
+ const entries = opts.stages.map((def) => {
2159
+ const wrapper = doc.createElement("div");
2160
+ wrapper.className = "bs-scrim-stage";
2161
+ const ws = wrapper.style;
2162
+ ws.position = "absolute";
2163
+ applyOverlayPosition(
2164
+ ws,
2165
+ deps.mode,
2166
+ def.position ?? opts.position ?? "center",
2167
+ def.inset ?? opts.inset ?? "16px"
2168
+ );
2169
+ ws.visibility = "hidden";
2170
+ ws.pointerEvents = "none";
2171
+ wrapper.appendChild(def.element);
2172
+ deps.host.appendChild(wrapper);
2173
+ return { def, wrapper, inFlight: null };
2174
+ });
2175
+ let active = null;
2176
+ const resolveActive = () => {
2177
+ const state = deps.getState();
2178
+ if (state.size === 0) {
2179
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2180
+ }
2181
+ return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2182
+ };
2183
+ const hide = (entry, animate) => {
2184
+ entry.inFlight?.cancel();
2185
+ entry.wrapper.style.pointerEvents = "none";
2186
+ if (!animate) {
2187
+ entry.inFlight = null;
2188
+ entry.wrapper.style.visibility = "hidden";
2189
+ return;
2190
+ }
2191
+ const handle = runAnchorTransition(
2192
+ entry.def.element,
2193
+ entry.def.animation ?? opts.animation,
2194
+ "exit"
2195
+ );
2196
+ entry.inFlight = handle;
2197
+ void handle.finished.then(() => {
2198
+ if (entry.inFlight === handle && active !== entry) {
2199
+ entry.wrapper.style.visibility = "hidden";
2200
+ }
2201
+ });
2202
+ };
2203
+ const show = (entry, animate) => {
2204
+ entry.inFlight?.cancel();
2205
+ entry.inFlight = null;
2206
+ entry.wrapper.style.visibility = "";
2207
+ entry.wrapper.style.pointerEvents = entry.def.interactive ?? opts.interactive ?? false ? "auto" : "none";
2208
+ if (animate) {
2209
+ entry.inFlight = runAnchorTransition(
2210
+ entry.def.element,
2211
+ entry.def.animation ?? opts.animation,
2212
+ "enter"
2213
+ );
2214
+ }
2215
+ };
2216
+ const update = (animate) => {
2217
+ if (deps.isDestroyed()) return;
2218
+ const next = resolveActive();
2219
+ if (next === active) return;
2220
+ const prev = active;
2221
+ active = next;
2222
+ if (prev) hide(prev, animate);
2223
+ if (next) show(next, animate);
2224
+ };
2225
+ const offs = [
2226
+ deps.on("snap", () => update(true)),
2227
+ deps.on("open", () => update(true)),
2228
+ deps.on("close", () => update(true))
2229
+ ];
2230
+ if (entries.some((e) => e.def.forRange)) {
2231
+ offs.push(deps.on("progress", () => update(true)));
2232
+ }
2233
+ update(false);
2234
+ return () => {
2235
+ offs.forEach((off) => off());
2236
+ for (const entry of entries) {
2237
+ entry.inFlight?.cancel();
2238
+ entry.inFlight = null;
2239
+ if (entry.wrapper.parentNode) {
2240
+ entry.wrapper.parentNode.removeChild(entry.wrapper);
2241
+ }
2242
+ }
2243
+ active = null;
2244
+ };
2245
+ }
2246
+
1825
2247
  // src/core/primitives/engine-options.ts
1826
2248
  var DEFAULT_FLICK_VELOCITY = 0.65;
1827
2249
  var DEFAULT_DRAG_THRESHOLD = 18;
@@ -1840,6 +2262,12 @@ function resolveEngineOptions(opts, extras) {
1840
2262
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
1841
2263
  rubberBandEnabled: opts.rubberBand ?? true,
1842
2264
  closeOnBack: opts.closeOnBack ?? false,
2265
+ persistent: opts.persistent ?? false,
2266
+ disableClose: opts.disableClose ?? false,
2267
+ disableDrag: opts.disableDrag ?? false,
2268
+ closeOnRouteChange: opts.closeOnRouteChange ?? false,
2269
+ radius: opts.radius,
2270
+ maxHeight: opts.maxHeight,
1843
2271
  initialAllowed,
1844
2272
  initialId,
1845
2273
  scrim: {
@@ -1866,7 +2294,8 @@ function resolveEngineOptions(opts, extras) {
1866
2294
  closeOnEscape: opts.closeOnEscape,
1867
2295
  lockBodyScroll: opts.lockBodyScroll,
1868
2296
  inertSiblings: opts.inertSiblings,
1869
- shouldApplyInertSiblings: extras?.shouldApplyInertSiblings
2297
+ shouldApplyInertSiblings: extras?.shouldApplyInertSiblings,
2298
+ returnFocus: opts.returnFocusTo
1870
2299
  }
1871
2300
  };
1872
2301
  }
@@ -1911,6 +2340,11 @@ var BottomSheetEngine = class {
1911
2340
  __publicField(this, "lifecycle");
1912
2341
  __publicField(this, "closeOnBack");
1913
2342
  __publicField(this, "routedTo");
2343
+ __publicField(this, "persistent");
2344
+ __publicField(this, "disableCloseFlag");
2345
+ __publicField(this, "disableDragFlag");
2346
+ __publicField(this, "closeOnRouteChange");
2347
+ __publicField(this, "maxHeightCap");
1914
2348
  __publicField(this, "snapPointsRaw");
1915
2349
  __publicField(this, "snaps");
1916
2350
  __publicField(this, "activeId");
@@ -1923,6 +2357,7 @@ var BottomSheetEngine = class {
1923
2357
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
1924
2358
  __publicField(this, "progressWriteSentinel", new WriteSentinel());
1925
2359
  __publicField(this, "isTopSheet", true);
2360
+ __publicField(this, "opening", false);
1926
2361
  __publicField(this, "progressPayload", {
1927
2362
  value: 0,
1928
2363
  size: 0
@@ -1938,6 +2373,12 @@ var BottomSheetEngine = class {
1938
2373
  __publicField(this, "linkedSheets", []);
1939
2374
  __publicField(this, "scrollCache");
1940
2375
  __publicField(this, "teardowns", new TeardownStack());
2376
+ __publicField(this, "anchors", []);
2377
+ __publicField(this, "anchorHost", null);
2378
+ __publicField(this, "stackZ", 100);
2379
+ __publicField(this, "detachScrimStages", null);
2380
+ __publicField(this, "stackEffectEnabled", false);
2381
+ __publicField(this, "stackEffectPrimed", false);
1941
2382
  __publicField(this, "transformTemplate");
1942
2383
  const resolved = resolveEngineOptions(opts, {
1943
2384
  shouldApplyInertSiblings: () => {
@@ -1971,6 +2412,10 @@ var BottomSheetEngine = class {
1971
2412
  getResolvedSnaps: () => this.snaps.getResolvedSnaps().slice(),
1972
2413
  snapTo: (id) => {
1973
2414
  void this.snapTo(id);
2415
+ },
2416
+ close: () => {
2417
+ if (!this.canDismiss()) return;
2418
+ void this.close("backdrop");
1974
2419
  }
1975
2420
  },
1976
2421
  resolved.scrim
@@ -1994,6 +2439,11 @@ var BottomSheetEngine = class {
1994
2439
  );
1995
2440
  this.closeOnBack = resolved.closeOnBack;
1996
2441
  this.routedTo = opts.routedTo;
2442
+ this.persistent = resolved.persistent;
2443
+ this.disableCloseFlag = resolved.disableClose;
2444
+ this.disableDragFlag = resolved.disableDrag;
2445
+ this.closeOnRouteChange = resolved.closeOnRouteChange;
2446
+ this.stackEffectEnabled = opts.stackEffect ?? false;
1997
2447
  this.snapPointsRaw = opts.snapPoints;
1998
2448
  this.persistKey = opts.persistKey;
1999
2449
  if (opts.linkedSheets) {
@@ -2006,7 +2456,7 @@ var BottomSheetEngine = class {
2006
2456
  this.snapPointsRaw,
2007
2457
  resolved.initialAllowed,
2008
2458
  this.mode,
2009
- () => this.handle.offsetHeight,
2459
+ () => this.measureFitSize(),
2010
2460
  (maxAxisSize) => {
2011
2461
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2012
2462
  }
@@ -2040,6 +2490,19 @@ var BottomSheetEngine = class {
2040
2490
  io.observe(this.element);
2041
2491
  this.teardowns.add(() => io.disconnect());
2042
2492
  }
2493
+ this.installFitObserver();
2494
+ if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2495
+ if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2496
+ this.applyAutoAriaLabelledBy();
2497
+ if (this.closeOnRouteChange) {
2498
+ this.teardowns.add(
2499
+ installRouteChange({
2500
+ isDestroyed: () => this.destroyed,
2501
+ getSize: () => this.size,
2502
+ close: () => this.close()
2503
+ })
2504
+ );
2505
+ }
2043
2506
  if (this.persistKey) {
2044
2507
  this.teardowns.add(installPersist(this, this.persistKey));
2045
2508
  }
@@ -2064,7 +2527,7 @@ var BottomSheetEngine = class {
2064
2527
  isTopSheet: () => this.isTopSheet,
2065
2528
  getSize: () => this.size,
2066
2529
  isDestroyed: () => this.destroyed,
2067
- close: () => this.close(),
2530
+ close: () => this.close("back"),
2068
2531
  on: (event, fn) => this.on(event, fn),
2069
2532
  sheetId: this.id
2070
2533
  })
@@ -2158,6 +2621,10 @@ var BottomSheetEngine = class {
2158
2621
  const previousSize = this.size;
2159
2622
  this.scrollCache.cache(previousId, previousSize, target.size);
2160
2623
  this.activeId = id;
2624
+ if (wasClosed && target.size > 0) {
2625
+ this.opening = true;
2626
+ sheetStack.promote(this.id);
2627
+ }
2161
2628
  if (this.animation.viewTransitionsAvailable) {
2162
2629
  this.currentViewTransition?.skipTransition?.();
2163
2630
  const vt = document.startViewTransition(() => {
@@ -2172,19 +2639,26 @@ var BottomSheetEngine = class {
2172
2639
  } else {
2173
2640
  await this.animation.animateTo(target.size, velocityPxPerMs);
2174
2641
  }
2642
+ this.opening = false;
2175
2643
  externalSignal?.removeEventListener("abort", onExternalAbort);
2176
2644
  if (signal.aborted) return;
2177
2645
  this.scrollCache.restore(id, previousSize, target.size);
2178
2646
  this.updateAriaSlider();
2179
- this.emit("snap", { id, size: target.size });
2647
+ this.emit("snap", {
2648
+ id,
2649
+ size: target.size,
2650
+ progress: this.computeProgress(target.size)
2651
+ });
2180
2652
  if (wasClosed && target.size > 0) {
2181
2653
  this.emit("open", { id });
2182
2654
  this.handleOpen();
2183
2655
  notifyLinkedSheets(this.linkedSheets, this);
2656
+ this.emit("opened", { id });
2184
2657
  }
2185
2658
  if (target.size === 0) {
2186
2659
  this.emit("close", void 0);
2187
2660
  this.handleClose();
2661
+ this.emit("closed", void 0);
2188
2662
  }
2189
2663
  }
2190
2664
  async dragTo(targetSize, velocityOrOpts = 0) {
@@ -2209,18 +2683,76 @@ var BottomSheetEngine = class {
2209
2683
  const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2210
2684
  return this.snapTo(target);
2211
2685
  }
2212
- close() {
2686
+ close(reason = "programmatic") {
2213
2687
  if (this.destroyed) return Promise.resolve();
2688
+ if (this.disableCloseFlag) return Promise.resolve();
2689
+ if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2214
2690
  const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2215
2691
  return this.snapTo(closedId ?? this.activeId);
2216
2692
  }
2217
- setAllowed(ids, snap) {
2693
+ canDismiss() {
2694
+ return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2695
+ }
2696
+ isTop() {
2697
+ return this.isTopSheet;
2698
+ }
2699
+ depth() {
2700
+ return sheetStack.depthOf(this.id);
2701
+ }
2702
+ expand() {
2703
+ if (this.destroyed) return Promise.resolve();
2704
+ const ids = this.allowedIdsBySize();
2705
+ const target = ids[ids.length - 1];
2706
+ if (!target) return Promise.resolve();
2707
+ return this.snapTo(target);
2708
+ }
2709
+ collapse() {
2710
+ if (this.destroyed) return Promise.resolve();
2711
+ const ids = this.allowedIdsBySize();
2712
+ const nonZero = ids.find((id) => (this.snaps.findById(id)?.size ?? 0) > 0);
2713
+ const target = nonZero ?? ids[0];
2714
+ if (!target) return Promise.resolve();
2715
+ return this.snapTo(target);
2716
+ }
2717
+ setRadius(r) {
2218
2718
  if (this.destroyed) return;
2219
- const willSnap = snap !== void 0 && ids.includes(snap) || !ids.includes(this.activeId) && ids.length > 0;
2220
- if (!willSnap) {
2221
- this.animation.cancel();
2222
- this.newCycle();
2719
+ this.element.style.setProperty(
2720
+ "--bs-radius",
2721
+ typeof r === "number" ? `${r}px` : r
2722
+ );
2723
+ }
2724
+ setMaxHeight(h) {
2725
+ if (this.destroyed) return;
2726
+ const value = typeof h === "number" ? `${h}px` : h;
2727
+ const axis = layoutAxis(this.mode);
2728
+ this.element.style.setProperty(
2729
+ axis === "height" ? "maxHeight" : "maxWidth",
2730
+ value
2731
+ );
2732
+ if (typeof h === "number") {
2733
+ this.maxHeightCap = h;
2734
+ this.clampToMaxHeight();
2735
+ } else if (typeof window !== "undefined") {
2736
+ const probe2 = document.createElement("div");
2737
+ probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2738
+ document.body.appendChild(probe2);
2739
+ const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2740
+ probe2.remove();
2741
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2742
+ this.clampToMaxHeight();
2743
+ }
2744
+ }
2745
+ clampToMaxHeight() {
2746
+ if (this.maxHeightCap === void 0) return;
2747
+ if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2748
+ this.snaps.setMaxAxisSize(this.maxHeightCap);
2223
2749
  }
2750
+ if (this.size > this.maxHeightCap && !this.gesture?.isDragging) {
2751
+ this.applySize(this.maxHeightCap);
2752
+ }
2753
+ }
2754
+ setAllowed(ids, snap) {
2755
+ if (this.destroyed) return;
2224
2756
  this.snaps.setAllowedIds(ids);
2225
2757
  this.updateAriaSlider();
2226
2758
  if (snap && ids.includes(snap)) {
@@ -2260,6 +2792,79 @@ var BottomSheetEngine = class {
2260
2792
  setScrimOverlay(opts) {
2261
2793
  return this.scrim.setScrimOverlay(opts);
2262
2794
  }
2795
+ addAnchor(opts) {
2796
+ if (this.destroyed || typeof document === "undefined") return () => {
2797
+ };
2798
+ if (!this.anchorHost) {
2799
+ this.anchorHost = this.rootEl ?? this.element.parentElement ?? document.body;
2800
+ this.anchorHost.style.setProperty("--bs-size", `${this.size}px`);
2801
+ this.anchorHost.style.setProperty(
2802
+ "--bs-progress",
2803
+ String(this.computeProgress(this.size))
2804
+ );
2805
+ }
2806
+ const handle = attachAnchor(
2807
+ {
2808
+ mode: this.mode,
2809
+ host: this.anchorHost,
2810
+ getState: () => ({
2811
+ activeId: this.activeId,
2812
+ size: this.size,
2813
+ progress: this.computeProgress(this.size)
2814
+ }),
2815
+ on: (event, fn) => this.on(event, fn),
2816
+ isDestroyed: () => this.destroyed
2817
+ },
2818
+ opts
2819
+ );
2820
+ handle.syncZ(this.stackZ + 1);
2821
+ this.anchors.push(handle);
2822
+ const detach = () => {
2823
+ const idx = this.anchors.indexOf(handle);
2824
+ if (idx === -1) return;
2825
+ this.anchors.splice(idx, 1);
2826
+ handle.detach();
2827
+ };
2828
+ this.teardowns.add(detach);
2829
+ return detach;
2830
+ }
2831
+ setScrimStages(opts) {
2832
+ if (this.destroyed || typeof document === "undefined") return () => {
2833
+ };
2834
+ this.detachScrimStages?.();
2835
+ this.detachScrimStages = null;
2836
+ if (!opts) return () => {
2837
+ };
2838
+ const host = this.screenComponent?.parentElement;
2839
+ if (!host) {
2840
+ console.warn(
2841
+ "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
2842
+ );
2843
+ return () => {
2844
+ };
2845
+ }
2846
+ const detachInstalled = installScrimStages(
2847
+ {
2848
+ mode: this.mode,
2849
+ host,
2850
+ getState: () => ({
2851
+ activeId: this.activeId,
2852
+ size: this.size,
2853
+ progress: this.computeProgress(this.size)
2854
+ }),
2855
+ on: (event, fn) => this.on(event, fn),
2856
+ isDestroyed: () => this.destroyed
2857
+ },
2858
+ opts
2859
+ );
2860
+ const detach = () => {
2861
+ if (this.detachScrimStages === detach) this.detachScrimStages = null;
2862
+ detachInstalled();
2863
+ };
2864
+ this.detachScrimStages = detach;
2865
+ this.teardowns.add(() => this.detachScrimStages?.());
2866
+ return detach;
2867
+ }
2263
2868
  getScrimState() {
2264
2869
  return { mode: this.scrim.scrimMode, enabled: this.scrim.scrimEnabled };
2265
2870
  }
@@ -2279,8 +2884,53 @@ var BottomSheetEngine = class {
2279
2884
  }
2280
2885
  this.updateAriaSlider();
2281
2886
  }
2887
+ recompute() {
2888
+ if (this.destroyed) return;
2889
+ this.snaps.recompute();
2890
+ this.scrim.invalidateOpacityCache();
2891
+ if (this.gesture?.isDragging) return;
2892
+ const current = this.snaps.findById(this.activeId);
2893
+ if (current) {
2894
+ this.size = current.size;
2895
+ this.applySize(this.size);
2896
+ this.updateAriaSlider();
2897
+ }
2898
+ }
2899
+ measureFitSize() {
2900
+ const vertical = layoutAxis(this.mode) === "height";
2901
+ const handleSize = vertical ? this.handle.offsetHeight : this.handle.offsetWidth;
2902
+ const content = this.scrollContainer;
2903
+ const natural = content ? handleSize + (vertical ? content.scrollHeight : content.scrollWidth) : handleSize;
2904
+ if (typeof window === "undefined") return natural;
2905
+ const viewport = vertical ? window.innerHeight : window.innerWidth;
2906
+ return viewport > 0 ? Math.min(natural, viewport) : natural;
2907
+ }
2908
+ installFitObserver() {
2909
+ if (typeof ResizeObserver === "undefined") return;
2910
+ if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2911
+ return;
2912
+ const targets = [this.handle];
2913
+ if (this.scrollContainer && this.scrollContainer !== this.handle) {
2914
+ targets.push(this.scrollContainer);
2915
+ }
2916
+ let raf = 0;
2917
+ const ro = new ResizeObserver(() => {
2918
+ if (raf) return;
2919
+ raf = requestAnimationFrame(() => {
2920
+ raf = 0;
2921
+ if (this.destroyed || this.gesture?.isDragging) return;
2922
+ this.recompute();
2923
+ });
2924
+ });
2925
+ for (const t of targets) ro.observe(t);
2926
+ this.teardowns.add(() => {
2927
+ ro.disconnect();
2928
+ if (raf) cancelAnimationFrame(raf);
2929
+ });
2930
+ }
2282
2931
  destroy() {
2283
2932
  this.destroyed = true;
2933
+ this.opening = false;
2284
2934
  this.currentAbort.abort();
2285
2935
  this.currentViewTransition?.skipTransition?.();
2286
2936
  this.currentViewTransition = null;
@@ -2330,6 +2980,36 @@ var BottomSheetEngine = class {
2330
2980
  frozen = true;
2331
2981
  return cancelled;
2332
2982
  }
2983
+ emitBeforeClose(reason) {
2984
+ let cancelled = false;
2985
+ let frozen = false;
2986
+ this.emit("before-close", {
2987
+ reason,
2988
+ cancel: () => {
2989
+ if (frozen) {
2990
+ console.warn(
2991
+ "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
2992
+ );
2993
+ return;
2994
+ }
2995
+ cancelled = true;
2996
+ }
2997
+ });
2998
+ frozen = true;
2999
+ return cancelled;
3000
+ }
3001
+ applyAutoAriaLabelledBy() {
3002
+ if (typeof document === "undefined") return;
3003
+ if (!this.lifecycle.focusTrapEnabled) return;
3004
+ if (this.element.getAttribute("aria-labelledby")) return;
3005
+ const scope = this.handle ?? this.element;
3006
+ const titled = scope.querySelector("[data-bs-title]") ?? scope.querySelector("h1,h2,h3,h4,h5,h6");
3007
+ if (!titled) return;
3008
+ if (!titled.id) {
3009
+ titled.id = nextInstanceId("bs-title");
3010
+ }
3011
+ this.element.setAttribute("aria-labelledby", titled.id);
3012
+ }
2333
3013
  recomputeSnaps() {
2334
3014
  const seen = /* @__PURE__ */ new Set();
2335
3015
  for (const p of this.snapPointsRaw) {
@@ -2346,24 +3026,42 @@ var BottomSheetEngine = class {
2346
3026
  getAllowedRange() {
2347
3027
  return this.snaps.getAllowedRange();
2348
3028
  }
3029
+ allowedIdsBySize() {
3030
+ return this.snaps.getAllowedIds().slice().sort(
3031
+ (a, b) => (this.snaps.findById(a)?.size ?? 0) - (this.snaps.findById(b)?.size ?? 0)
3032
+ );
3033
+ }
2349
3034
  updateAriaSlider() {
2350
- this.aria.setValue(this.snaps.getAllowedIds(), this.activeId);
3035
+ this.aria.setValue(this.allowedIdsBySize(), this.activeId);
2351
3036
  }
2352
3037
  registerInStack() {
2353
3038
  this.teardowns.add(sheetStack.push({
2354
3039
  id: this.id,
2355
3040
  setZIndex: (z) => {
3041
+ this.stackZ = z;
2356
3042
  this.element.style.zIndex = String(z);
2357
3043
  if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
3044
+ for (const anchor of this.anchors) anchor.syncZ(z + 1);
2358
3045
  },
2359
3046
  setIsTop: (isTop) => {
2360
3047
  this.isTopSheet = isTop;
2361
- if (this.backdrop) {
2362
- this.backdrop.style.display = isTop ? "" : "none";
2363
- }
2364
- }
3048
+ },
3049
+ isOpen: () => this.size > 0 || this.opening,
3050
+ setDepth: (depth) => this.applyStackDepth(depth)
2365
3051
  }));
2366
3052
  }
3053
+ applyStackDepth(depth) {
3054
+ if (!this.stackEffectEnabled) return;
3055
+ this.element.setAttribute("data-stack-depth", String(depth));
3056
+ if (!this.stackEffectPrimed) {
3057
+ this.stackEffectPrimed = true;
3058
+ const isVertical = this.mode === "bottom" || this.mode === "top";
3059
+ this.element.style.transformOrigin = isVertical ? "50% 100%" : "0% 50%";
3060
+ this.element.style.transition = "scale 320ms cubic-bezier(0.29, 1.04, 0.84, 0.99)";
3061
+ }
3062
+ const scale = Math.max(1 - depth * 0.04, 0.86);
3063
+ this.element.style.scale = scale === 1 ? "" : String(scale);
3064
+ }
2367
3065
  attach() {
2368
3066
  this.gesture = new GestureController({
2369
3067
  handle: this.handle,
@@ -2380,6 +3078,7 @@ var BottomSheetEngine = class {
2380
3078
  buf.rubberBandEnabled = this.rubberBandEnabled;
2381
3079
  return buf;
2382
3080
  },
3081
+ getDisableDrag: () => this.disableDragFlag,
2383
3082
  cancelAnimation: () => this.animation.cancel(),
2384
3083
  applySize: (size) => this.applySize(size),
2385
3084
  animateTo: (size, velocity) => this.animation.animateTo(size, velocity),
@@ -2423,7 +3122,7 @@ var BottomSheetEngine = class {
2423
3122
  container: this.scrollContainer,
2424
3123
  isDragging: () => this.gesture?.isDragging ?? false,
2425
3124
  isAnimating: () => this.animation.isAnimating,
2426
- getAllowedIds: () => this.snaps.getAllowedIds(),
3125
+ getAllowedIds: () => this.allowedIdsBySize(),
2427
3126
  getActiveId: () => this.activeId,
2428
3127
  snapTo: (id) => {
2429
3128
  void this.snapTo(id);
@@ -2452,13 +3151,16 @@ var BottomSheetEngine = class {
2452
3151
  },
2453
3152
  newCycle: () => {
2454
3153
  this.newCycle();
2455
- }
3154
+ },
3155
+ isAnimating: () => this.animation.isAnimating,
3156
+ resyncAfterCancel: () => this.resyncAfterResize()
2456
3157
  }));
2457
3158
  if (this.lifecycle.closeOnEscape) {
2458
3159
  const onKey = (e) => {
2459
- if (this.destroyed) return;
3160
+ if (this.destroyed || e.defaultPrevented) return;
3161
+ if (!this.canDismiss()) return;
2460
3162
  if (e.key === "Escape" && this.isTopSheet && this.size > 0) {
2461
- void this.close();
3163
+ void this.close("escape");
2462
3164
  }
2463
3165
  };
2464
3166
  document.addEventListener("keydown", onKey);
@@ -2488,7 +3190,7 @@ var BottomSheetEngine = class {
2488
3190
  handle: this.handle,
2489
3191
  mode: this.mode,
2490
3192
  isDestroyed: () => this.destroyed,
2491
- getAllowedIds: () => this.snaps.getAllowedIds(),
3193
+ getAllowedIds: () => this.allowedIdsBySize(),
2492
3194
  getActiveId: () => this.activeId,
2493
3195
  snapTo: (id) => {
2494
3196
  void this.snapTo(id);
@@ -2509,6 +3211,7 @@ var BottomSheetEngine = class {
2509
3211
  });
2510
3212
  if (!target) return;
2511
3213
  const previousId = this.activeId;
3214
+ if (target.id === previousId && target.size === this.size) return;
2512
3215
  if (this.emitBeforeSnap(target, previousId)) {
2513
3216
  const restore = this.snaps.findById(previousId);
2514
3217
  if (restore) {
@@ -2517,21 +3220,34 @@ var BottomSheetEngine = class {
2517
3220
  return;
2518
3221
  }
2519
3222
  const previousSize = this.size;
3223
+ const previousSnapSize = this.snaps.findById(previousId)?.size;
2520
3224
  this.scrollCache.cache(previousId, previousSize, target.size);
2521
3225
  this.activeId = target.id;
3226
+ if (previousSnapSize === 0 && target.size > 0) {
3227
+ this.opening = true;
3228
+ sheetStack.promote(this.id);
3229
+ }
2522
3230
  void this.animation.animateTo(target.size, velocity).then(() => {
3231
+ this.opening = false;
2523
3232
  if (signal.aborted) return;
2524
3233
  this.scrollCache.restore(target.id, previousSize, target.size);
2525
3234
  this.updateAriaSlider();
2526
- this.emit("snap", { id: target.id, size: target.size });
3235
+ this.emit("snap", {
3236
+ id: target.id,
3237
+ size: target.size,
3238
+ progress: this.computeProgress(target.size)
3239
+ });
2527
3240
  this.haptic();
2528
- if (previousId === "closed" && target.size > 0) {
3241
+ if (previousSnapSize === 0 && target.size > 0) {
2529
3242
  this.emit("open", { id: target.id });
2530
3243
  this.handleOpen();
3244
+ notifyLinkedSheets(this.linkedSheets, this);
3245
+ this.emit("opened", { id: target.id });
2531
3246
  }
2532
3247
  if (target.size === 0) {
2533
3248
  this.emit("close", void 0);
2534
3249
  this.handleClose();
3250
+ this.emit("closed", void 0);
2535
3251
  }
2536
3252
  });
2537
3253
  }
@@ -2553,6 +3269,9 @@ var BottomSheetEngine = class {
2553
3269
  if (this.scrimParent) {
2554
3270
  this.scrimParent.style.setProperty("--bs-size", `${size}px`);
2555
3271
  }
3272
+ if (this.anchorHost && this.anchorHost !== this.scrimParent) {
3273
+ this.anchorHost.style.setProperty("--bs-size", `${size}px`);
3274
+ }
2556
3275
  }
2557
3276
  const progress = this.computeProgress(size);
2558
3277
  const progressChanged = this.progressWriteSentinel.shouldWrite(
@@ -2561,6 +3280,9 @@ var BottomSheetEngine = class {
2561
3280
  );
2562
3281
  if (progressChanged) {
2563
3282
  style.setProperty("--bs-progress", String(progress));
3283
+ if (this.anchorHost) {
3284
+ this.anchorHost.style.setProperty("--bs-progress", String(progress));
3285
+ }
2564
3286
  }
2565
3287
  this.scrim.applyOpacity(progress, progressChanged);
2566
3288
  if (progressChanged && this.bus.listenerCount("progress") > 0) {
@@ -2575,6 +3297,7 @@ var BottomSheetEngine = class {
2575
3297
  return Math.min(Math.max((size - min) / (max - min), 0), 1);
2576
3298
  }
2577
3299
  handleOpen() {
3300
+ sheetStack.promote(this.id);
2578
3301
  try {
2579
3302
  this.lifecycle.install();
2580
3303
  } catch (err) {
@@ -2588,10 +3311,37 @@ var BottomSheetEngine = class {
2588
3311
  });
2589
3312
  }
2590
3313
  }
3314
+ resyncAfterResize() {
3315
+ if (this.destroyed) return;
3316
+ const target = this.snaps.findById(this.activeId);
3317
+ if (!target) return;
3318
+ this.updateAriaSlider();
3319
+ this.emit("snap", {
3320
+ id: target.id,
3321
+ size: target.size,
3322
+ progress: this.computeProgress(target.size)
3323
+ });
3324
+ if (target.size > 0 && !this.lifecycle.isInstalled) {
3325
+ this.emit("open", { id: target.id });
3326
+ this.handleOpen();
3327
+ notifyLinkedSheets(this.linkedSheets, this);
3328
+ this.emit("opened", { id: target.id });
3329
+ } else if (target.size === 0 && this.lifecycle.isInstalled) {
3330
+ this.emit("close", void 0);
3331
+ this.handleClose();
3332
+ this.emit("closed", void 0);
3333
+ }
3334
+ }
2591
3335
  handleClose() {
3336
+ this.opening = false;
2592
3337
  this.lifecycle.release();
3338
+ sheetStack.update();
2593
3339
  }
2594
3340
  };
3341
+
3342
+ // src/solid/index.tsx
3343
+ var _tmpl$ = /* @__PURE__ */ template(`<div class=bs-root><div class=bs-screen></div><section class=bs-sheet role=dialog><div class=bs-handle role=slider tabindex=0 aria-label="Resize sheet"></div><div class=bs-content tabindex=0 role=region aria-label="Sheet content"></div><span class=bs-sr-only role=status aria-live=polite>`);
3344
+ var _tmpl$2 = /* @__PURE__ */ template(`<div class=bs-backdrop>`);
2595
3345
  var BottomSheet = (props) => {
2596
3346
  let sheetEl;
2597
3347
  let handleEl;
@@ -2633,7 +3383,9 @@ var BottomSheet = (props) => {
2633
3383
  const sync = () => {
2634
3384
  if (!engine) return;
2635
3385
  const s = engine.state;
2636
- setState({ ...s });
3386
+ setState({
3387
+ ...s
3388
+ });
2637
3389
  props.onChange?.(s);
2638
3390
  };
2639
3391
  sync();
@@ -2658,50 +3410,54 @@ var BottomSheet = (props) => {
2658
3410
  engine = null;
2659
3411
  props.engineRef?.(null);
2660
3412
  });
3413
+ createEffect(on(() => props.snapPoints, (points, prev) => {
3414
+ if (!engine || prev === void 0) return;
3415
+ engine.setSnapPoints(points, props.allowed);
3416
+ }, {
3417
+ defer: true
3418
+ }));
3419
+ createEffect(on(() => props.allowed, (allowed, prev) => {
3420
+ if (!engine || prev === void 0) return;
3421
+ if (allowed) engine.setAllowed(allowed);
3422
+ }, {
3423
+ defer: true
3424
+ }));
2661
3425
  const showBackdrop = createMemo(() => props.backdrop !== false);
2662
3426
  const mode = createMemo(() => props.mode ?? "bottom");
2663
3427
  const ariaLabel = createMemo(() => props.ariaLabel ?? "Bottom sheet");
2664
- return /* @__PURE__ */ jsxs("div", { class: "bs-root", children: [
2665
- showBackdrop() ? /* @__PURE__ */ jsx("div", { class: "bs-backdrop", ref: (el) => backdropEl = el }) : null,
2666
- /* @__PURE__ */ jsx("div", { class: "bs-screen", ref: (el) => scrimEl = el, children: props.screen }),
2667
- /* @__PURE__ */ jsxs(
2668
- "section",
2669
- {
2670
- class: "bs-sheet",
2671
- ref: (el) => sheetEl = el,
2672
- "data-mode": mode(),
2673
- "data-active": state().activeId,
2674
- role: "dialog",
2675
- "aria-modal": props.focusTrap ? "true" : void 0,
2676
- "aria-label": ariaLabel(),
2677
- children: [
2678
- /* @__PURE__ */ jsx(
2679
- "div",
2680
- {
2681
- class: "bs-handle",
2682
- ref: (el) => handleEl = el,
2683
- role: "slider",
2684
- tabIndex: 0,
2685
- "aria-label": "Resize sheet",
2686
- children: props.header
2687
- }
2688
- ),
2689
- /* @__PURE__ */ jsx(
2690
- "div",
2691
- {
2692
- class: "bs-content",
2693
- ref: (el) => contentEl = el,
2694
- tabIndex: 0,
2695
- role: "region",
2696
- "aria-label": "Sheet content",
2697
- children: props.children
2698
- }
2699
- ),
2700
- /* @__PURE__ */ jsx("span", { class: "bs-sr-only", role: "status", "aria-live": "polite", children: state().activeId })
2701
- ]
2702
- }
2703
- )
2704
- ] });
3428
+ return (() => {
3429
+ var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$6 = _el$5.nextSibling;
3430
+ insert(_el$, (() => {
3431
+ var _c$ = memo(() => !!showBackdrop());
3432
+ return () => _c$() ? (() => {
3433
+ var _el$7 = _tmpl$2();
3434
+ use((el) => backdropEl = el, _el$7);
3435
+ return _el$7;
3436
+ })() : null;
3437
+ })(), _el$2);
3438
+ use((el) => scrimEl = el, _el$2);
3439
+ insert(_el$2, () => props.screen);
3440
+ use((el) => sheetEl = el, _el$3);
3441
+ use((el) => handleEl = el, _el$4);
3442
+ insert(_el$4, () => props.header);
3443
+ use((el) => contentEl = el, _el$5);
3444
+ insert(_el$5, () => props.children);
3445
+ insert(_el$6, () => state().activeId);
3446
+ effect((_p$) => {
3447
+ var _v$ = mode(), _v$2 = state().activeId, _v$3 = props.focusTrap ? "true" : void 0, _v$4 = ariaLabel();
3448
+ _v$ !== _p$.e && setAttribute(_el$3, "data-mode", _p$.e = _v$);
3449
+ _v$2 !== _p$.t && setAttribute(_el$3, "data-active", _p$.t = _v$2);
3450
+ _v$3 !== _p$.a && setAttribute(_el$3, "aria-modal", _p$.a = _v$3);
3451
+ _v$4 !== _p$.o && setAttribute(_el$3, "aria-label", _p$.o = _v$4);
3452
+ return _p$;
3453
+ }, {
3454
+ e: void 0,
3455
+ t: void 0,
3456
+ a: void 0,
3457
+ o: void 0
3458
+ });
3459
+ return _el$;
3460
+ })();
2705
3461
  };
2706
3462
 
2707
3463
  export { BottomSheet };