@surdeddd/bottom-sheet 0.8.2 → 0.9.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 (46) hide show
  1. package/dist/{BottomSheetEngine-7BDK2OWX.d.cts → BottomSheetEngine-BcyruNP1.d.cts} +12 -6
  2. package/dist/{BottomSheetEngine-BFTb0-4Z.d.ts → BottomSheetEngine-CBRcBwV-.d.ts} +12 -6
  3. package/dist/element.cjs +530 -349
  4. package/dist/element.d.cts +2 -1
  5. package/dist/element.d.ts +2 -1
  6. package/dist/element.iife.global.js +2 -2
  7. package/dist/element.js +530 -349
  8. package/dist/index.cjs +438 -294
  9. package/dist/index.d.cts +3 -3
  10. package/dist/index.d.ts +3 -3
  11. package/dist/index.js +438 -294
  12. package/dist/integrations/formik.d.cts +1 -1
  13. package/dist/integrations/formik.d.ts +1 -1
  14. package/dist/integrations/react-hook-form.d.cts +1 -1
  15. package/dist/integrations/react-hook-form.d.ts +1 -1
  16. package/dist/overlay.cjs +160 -96
  17. package/dist/overlay.d.cts +2 -0
  18. package/dist/overlay.d.ts +2 -0
  19. package/dist/overlay.js +160 -96
  20. package/dist/preact.d.cts +1 -1
  21. package/dist/preact.d.ts +1 -1
  22. package/dist/qwik.cjs +387 -211
  23. package/dist/qwik.d.cts +4 -3
  24. package/dist/qwik.d.ts +4 -3
  25. package/dist/qwik.js +387 -211
  26. package/dist/react.cjs +472 -296
  27. package/dist/react.d.cts +3 -2
  28. package/dist/react.d.ts +3 -2
  29. package/dist/react.js +472 -296
  30. package/dist/solid.cjs +417 -215
  31. package/dist/solid.d.cts +21 -7
  32. package/dist/solid.d.ts +21 -7
  33. package/dist/solid.js +417 -215
  34. package/dist/solid.source.jsx +412 -209
  35. package/dist/svelte-core.cjs +328 -209
  36. package/dist/svelte-core.js +328 -209
  37. package/dist/svelte-src/BottomSheet.svelte +56 -0
  38. package/dist/svelte.cjs +365 -210
  39. package/dist/svelte.d.cts +4 -0
  40. package/dist/svelte.d.ts +4 -0
  41. package/dist/svelte.js +365 -210
  42. package/dist/vue.cjs +358 -211
  43. package/dist/vue.d.cts +6 -0
  44. package/dist/vue.d.ts +6 -0
  45. package/dist/vue.js +358 -211
  46. package/package.json +6 -6
package/dist/svelte.cjs CHANGED
@@ -288,18 +288,25 @@ var SnapResolver = class {
288
288
  __publicField(this, "resolved", []);
289
289
  __publicField(this, "maxAxisSize", 0);
290
290
  __publicField(this, "rangeCache", null);
291
+ __publicField(this, "notified", false);
291
292
  this.raw = raw;
292
293
  this.allowedIds = allowed ?? raw.map((p) => p.id);
293
294
  this.recompute();
294
295
  }
295
296
  recompute() {
296
- this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
297
- this.maxAxisSize = this.resolved.reduce(
297
+ const measure = this.measureFit;
298
+ let measured;
299
+ const measureOnce = measure ? () => measured ?? (measured = measure()) : void 0;
300
+ this.resolved = resolveSnapList(this.raw, this.mode, measureOnce);
301
+ const nextMax = this.resolved.reduce(
298
302
  (m, s) => s.size > m ? s.size : m,
299
303
  0
300
304
  );
301
305
  this.rangeCache = null;
302
- this.onMaxAxisSizeChange?.(this.maxAxisSize);
306
+ const changed = nextMax !== this.maxAxisSize || !this.notified;
307
+ this.maxAxisSize = nextMax;
308
+ this.notified = true;
309
+ if (changed) this.onMaxAxisSizeChange?.(this.maxAxisSize);
303
310
  }
304
311
  setRaw(raw) {
305
312
  this.raw = raw;
@@ -452,7 +459,10 @@ var ensureHandler = () => {
452
459
  target.consumed = true;
453
460
  const idx = closers.indexOf(target);
454
461
  if (idx !== -1) closers.splice(idx, 1);
455
- target.close();
462
+ const settled = target;
463
+ void Promise.resolve(settled.close()).then(() => {
464
+ if (settled.isOpen()) settled.restore();
465
+ });
456
466
  });
457
467
  };
458
468
  var programmaticBack = () => {
@@ -488,16 +498,7 @@ function installRoute(deps) {
488
498
  pushed = false;
489
499
  }
490
500
  };
491
- const onOpen = () => {
492
- if (closer) return;
493
- closer = {
494
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
495
- isTop: () => deps.isTopSheet(),
496
- close: () => void deps.close(),
497
- consumed: false
498
- };
499
- ensureHandler();
500
- closers.push(closer);
501
+ const pushEntry = () => {
501
502
  try {
502
503
  if (deps.routedTo !== void 0) {
503
504
  history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
@@ -509,6 +510,25 @@ function installRoute(deps) {
509
510
  pushed = false;
510
511
  }
511
512
  };
513
+ const onOpen = () => {
514
+ if (closer) return;
515
+ const self = {
516
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
517
+ isTop: () => deps.isTopSheet(),
518
+ close: () => deps.close(),
519
+ restore: () => {
520
+ if (deps.isDestroyed() || closer !== self) return;
521
+ self.consumed = false;
522
+ if (!closers.includes(self)) closers.push(self);
523
+ pushEntry();
524
+ },
525
+ consumed: false
526
+ };
527
+ closer = self;
528
+ ensureHandler();
529
+ closers.push(self);
530
+ pushEntry();
531
+ };
512
532
  const unsubscribeOpen = deps.on("open", onOpen);
513
533
  const unsubscribeClose = deps.on("close", teardownEntry);
514
534
  if (deps.getSize() > 0) onOpen();
@@ -555,11 +575,13 @@ var CONTENT_PULL_THRESHOLD = 60;
555
575
  function installContentSwipe(deps) {
556
576
  const { container } = deps;
557
577
  let startY = 0;
578
+ let startScrollTop = 0;
558
579
  let moved = false;
559
580
  const onTouchStart = (e) => {
560
581
  const t = e.touches[0];
561
582
  if (!t) return;
562
583
  startY = t.clientY;
584
+ startScrollTop = container.scrollTop;
563
585
  moved = false;
564
586
  };
565
587
  const onTouchMove = (e) => {
@@ -570,6 +592,7 @@ function installContentSwipe(deps) {
570
592
  const onTouchEnd = (e) => {
571
593
  if (!moved) return;
572
594
  if (deps.isDragging() || deps.isAnimating()) return;
595
+ if (container.scrollTop !== startScrollTop) return;
573
596
  const t = e.changedTouches[0];
574
597
  if (!t) return;
575
598
  const deltaY = t.clientY - startY;
@@ -681,6 +704,131 @@ function installResizeObserver(deps) {
681
704
  };
682
705
  }
683
706
 
707
+ // src/core/features/fit-observer.ts
708
+ var contentTargets = (scroller) => {
709
+ if (!scroller) return [];
710
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
711
+ const out = [];
712
+ for (const child2 of Array.from(scroller.children)) {
713
+ if (hasSlot && child2 instanceof HTMLSlotElement) {
714
+ for (const el of child2.assignedElements()) out.push(el);
715
+ } else {
716
+ out.push(child2);
717
+ }
718
+ }
719
+ return out;
720
+ };
721
+ function installFitObserver(deps) {
722
+ if (typeof ResizeObserver === "undefined") return () => {
723
+ };
724
+ if (!deps.hasFitSnap()) return () => {
725
+ };
726
+ let raf = 0;
727
+ const schedule = () => {
728
+ if (raf) return;
729
+ raf = requestAnimationFrame(() => {
730
+ raf = 0;
731
+ if (deps.isDestroyed() || deps.isDragging()) return;
732
+ deps.recompute();
733
+ });
734
+ };
735
+ const ro = new ResizeObserver(schedule);
736
+ ro.observe(deps.handle);
737
+ const scroller = deps.scrollContainer;
738
+ const observed = /* @__PURE__ */ new Set();
739
+ const slots = /* @__PURE__ */ new Set();
740
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
741
+ const onSlotChange = () => resync();
742
+ const trackSlots = () => {
743
+ if (!scroller || !hasSlot) return;
744
+ for (const child2 of Array.from(scroller.children)) {
745
+ if (child2 instanceof HTMLSlotElement && !slots.has(child2)) {
746
+ child2.addEventListener("slotchange", onSlotChange);
747
+ slots.add(child2);
748
+ }
749
+ }
750
+ };
751
+ const resync = () => {
752
+ trackSlots();
753
+ const targets = contentTargets(scroller);
754
+ for (const el of observed) {
755
+ if (!targets.includes(el)) {
756
+ ro.unobserve(el);
757
+ observed.delete(el);
758
+ }
759
+ }
760
+ for (const el of targets) {
761
+ if (!observed.has(el)) {
762
+ ro.observe(el);
763
+ observed.add(el);
764
+ }
765
+ }
766
+ schedule();
767
+ };
768
+ let mo;
769
+ if (scroller) {
770
+ resync();
771
+ if (typeof MutationObserver !== "undefined") {
772
+ mo = new MutationObserver(resync);
773
+ mo.observe(scroller, { childList: true });
774
+ }
775
+ }
776
+ return () => {
777
+ ro.disconnect();
778
+ mo?.disconnect();
779
+ for (const slot of slots) {
780
+ slot.removeEventListener("slotchange", onSlotChange);
781
+ }
782
+ if (raf) cancelAnimationFrame(raf);
783
+ };
784
+ }
785
+
786
+ // src/core/features/fit-measurement.ts
787
+ var measureSheetNatural = (element, content, vertical) => {
788
+ if (!content) {
789
+ return vertical ? element.offsetHeight : element.offsetWidth;
790
+ }
791
+ const axis = vertical ? "height" : "width";
792
+ const sheetStyle = element.style;
793
+ const cs = content.style;
794
+ const prevSheet = sheetStyle[axis];
795
+ const prevFlex = cs.flex;
796
+ const prevContent = vertical ? cs.height : cs.width;
797
+ sheetStyle[axis] = "auto";
798
+ cs.flex = "none";
799
+ if (vertical) cs.height = "auto";
800
+ else cs.width = "auto";
801
+ const poked = vertical ? element.offsetHeight : element.offsetWidth;
802
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
803
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
804
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
805
+ sheetStyle[axis] = prevSheet;
806
+ cs.flex = prevFlex;
807
+ if (vertical) cs.height = prevContent;
808
+ else cs.width = prevContent;
809
+ return natural;
810
+ };
811
+ var containingExtent = (element, vertical) => {
812
+ const op = element.offsetParent;
813
+ if (op && op !== document.body && op !== document.documentElement) {
814
+ return vertical ? op.clientHeight : op.clientWidth;
815
+ }
816
+ return vertical ? window.innerHeight : window.innerWidth;
817
+ };
818
+ var measureFitSize = (deps) => {
819
+ const vertical = layoutAxis(deps.mode) === "height";
820
+ const natural = measureSheetNatural(
821
+ deps.element,
822
+ deps.scrollContainer,
823
+ vertical
824
+ );
825
+ const cap = deps.getMaxHeightCap();
826
+ const capped = cap !== void 0 ? Math.min(natural, cap) : natural;
827
+ if (typeof window === "undefined") return capped;
828
+ const viewport = containingExtent(deps.element, vertical);
829
+ return viewport > 0 ? Math.min(capped, viewport) : capped;
830
+ };
831
+
684
832
  // src/core/features/slider-keyboard.ts
685
833
  function installSliderKeyboard(deps) {
686
834
  const isVerticalAxis = deps.mode === "bottom" || deps.mode === "top";
@@ -1167,6 +1315,9 @@ var ScrimController = class {
1167
1315
  const s = this.screenComponent.style;
1168
1316
  s.opacity = "";
1169
1317
  s.display = "";
1318
+ s.position = "";
1319
+ s.inset = "";
1320
+ s.pointerEvents = "";
1170
1321
  }
1171
1322
  if (this.backdrop) {
1172
1323
  this.backdrop.style.opacity = "";
@@ -1415,7 +1566,12 @@ var AnimationRunner = class {
1415
1566
  this.currentSpring?.cancel();
1416
1567
  }
1417
1568
  async animateTo(target, velocityPxPerMs) {
1418
- if (target === this.getSize()) return;
1569
+ if (target === this.getSize()) {
1570
+ if (!this.isAnimating && !this.isDragging()) {
1571
+ this.element.style.willChange = "";
1572
+ }
1573
+ return;
1574
+ }
1419
1575
  this.currentTween?.cancel();
1420
1576
  this.currentSpring?.cancel();
1421
1577
  this.getRootEl()?.setAttribute("data-animating", "true");
@@ -1423,7 +1579,7 @@ var AnimationRunner = class {
1423
1579
  if (this.reducedMotion) {
1424
1580
  this.applySizeFn(target);
1425
1581
  this.getRootEl()?.removeAttribute("data-animating");
1426
- if (!this.isDragging()) this.element.style.willChange = "auto";
1582
+ if (!this.isDragging()) this.element.style.willChange = "";
1427
1583
  return;
1428
1584
  }
1429
1585
  if (this.animationKind === "spring") {
@@ -1451,7 +1607,7 @@ var AnimationRunner = class {
1451
1607
  }
1452
1608
  if (!this.isAnimating) {
1453
1609
  this.getRootEl()?.removeAttribute("data-animating");
1454
- if (!this.isDragging()) this.element.style.willChange = "auto";
1610
+ if (!this.isDragging()) this.element.style.willChange = "";
1455
1611
  }
1456
1612
  }
1457
1613
  destroy() {
@@ -1482,9 +1638,35 @@ var isVisible = (el) => {
1482
1638
  }
1483
1639
  return el.offsetParent !== null;
1484
1640
  };
1485
- var focusables = (root2) => Array.from(root2.querySelectorAll(FOCUSABLE)).filter(
1641
+ var collectFocusables = (root2) => {
1642
+ const out = Array.from(root2.querySelectorAll(FOCUSABLE));
1643
+ if (typeof HTMLSlotElement !== "undefined") {
1644
+ for (const slot of Array.from(root2.querySelectorAll("slot"))) {
1645
+ for (const assigned of slot.assignedElements()) {
1646
+ if (assigned.matches(FOCUSABLE)) out.push(assigned);
1647
+ out.push(...collectFocusables(assigned));
1648
+ }
1649
+ }
1650
+ }
1651
+ return out;
1652
+ };
1653
+ var focusables = (root2) => collectFocusables(root2).filter(
1486
1654
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1487
1655
  );
1656
+ var containsComposed = (container, node) => {
1657
+ let n = node;
1658
+ while (n) {
1659
+ if (n === container) return true;
1660
+ const slot = n.assignedSlot;
1661
+ if (slot) {
1662
+ n = slot;
1663
+ continue;
1664
+ }
1665
+ const parent = n.parentNode;
1666
+ n = parent instanceof ShadowRoot ? parent.host : parent;
1667
+ }
1668
+ return false;
1669
+ };
1488
1670
  var trapStack = [];
1489
1671
  var installFocusTrap = (container, options = {}) => {
1490
1672
  if (typeof document === "undefined") return () => {
@@ -1494,7 +1676,13 @@ var installFocusTrap = (container, options = {}) => {
1494
1676
  const isActive = () => trapStack[trapStack.length - 1] === token;
1495
1677
  const previouslyFocused = document.activeElement;
1496
1678
  const focusInitial = () => {
1497
- const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
1679
+ const spec = options.initialFocus;
1680
+ if (spec === false) {
1681
+ if (!container.hasAttribute("tabindex")) container.tabIndex = -1;
1682
+ container.focus({ preventScroll: true });
1683
+ return;
1684
+ }
1685
+ const initial = typeof spec === "string" ? container.querySelector(spec) : spec ?? focusables(container)[0] ?? container;
1498
1686
  initial?.focus({ preventScroll: true });
1499
1687
  };
1500
1688
  focusInitial();
@@ -1524,7 +1712,7 @@ var installFocusTrap = (container, options = {}) => {
1524
1712
  const handleFocusIn = (e) => {
1525
1713
  if (!isActive()) return;
1526
1714
  const target = e.target;
1527
- if (target && !container.contains(target)) {
1715
+ if (target && !containsComposed(container, target)) {
1528
1716
  const list = focusables(container);
1529
1717
  list[0]?.focus({ preventScroll: true });
1530
1718
  }
@@ -1561,8 +1749,14 @@ var lockBodyScroll = () => {
1561
1749
  position: body.style.position,
1562
1750
  top: body.style.top,
1563
1751
  width: body.style.width,
1752
+ paddingRight: body.style.paddingRight,
1564
1753
  scrollY: window.scrollY
1565
1754
  };
1755
+ const scrollbarGap = window.innerWidth - document.documentElement.clientWidth;
1756
+ if (scrollbarGap > 0) {
1757
+ const current = parseFloat(getComputedStyle(body).paddingRight) || 0;
1758
+ body.style.paddingRight = `${current + scrollbarGap}px`;
1759
+ }
1566
1760
  body.style.overflow = "hidden";
1567
1761
  body.style.position = "fixed";
1568
1762
  body.style.top = `-${savedStyles.scrollY}px`;
@@ -1580,6 +1774,7 @@ var lockBodyScroll = () => {
1580
1774
  body.style.position = savedStyles.position;
1581
1775
  body.style.top = savedStyles.top;
1582
1776
  body.style.width = savedStyles.width;
1777
+ body.style.paddingRight = savedStyles.paddingRight;
1583
1778
  window.scrollTo(0, savedStyles.scrollY);
1584
1779
  savedStyles = null;
1585
1780
  }
@@ -1792,12 +1987,14 @@ var installGestures = (handle, mode, callbacks) => {
1792
1987
  handle.addEventListener("pointermove", onPointerMove);
1793
1988
  handle.addEventListener("pointerup", finishGesture);
1794
1989
  handle.addEventListener("pointercancel", onPointerCancel);
1990
+ const prevTouchAction = handle.style.touchAction;
1795
1991
  handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1796
1992
  return () => {
1797
1993
  handle.removeEventListener("pointerdown", onPointerDown);
1798
1994
  handle.removeEventListener("pointermove", onPointerMove);
1799
1995
  handle.removeEventListener("pointerup", finishGesture);
1800
1996
  handle.removeEventListener("pointercancel", onPointerCancel);
1997
+ handle.style.touchAction = prevTouchAction;
1801
1998
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1802
1999
  handle.releasePointerCapture(activePointerId);
1803
2000
  }
@@ -1924,7 +2121,7 @@ var GestureController = class {
1924
2121
  this.isDragging_ = false;
1925
2122
  this.keyboardDismissed = false;
1926
2123
  this.deps.getRoot()?.removeAttribute("data-dragging");
1927
- this.deps.element.style.willChange = "auto";
2124
+ this.deps.element.style.willChange = "";
1928
2125
  }
1929
2126
  };
1930
2127
 
@@ -2323,8 +2520,7 @@ function resolveEngineOptions(opts, extras) {
2323
2520
  };
2324
2521
  }
2325
2522
 
2326
- // src/core/BottomSheetEngine.ts
2327
- var HAPTIC_DURATION_MS = 8;
2523
+ // src/core/primitives/teardown-stack.ts
2328
2524
  var TeardownStack = class {
2329
2525
  constructor() {
2330
2526
  __publicField(this, "fns", []);
@@ -2344,6 +2540,9 @@ var TeardownStack = class {
2344
2540
  }
2345
2541
  }
2346
2542
  };
2543
+
2544
+ // src/core/BottomSheetEngine.ts
2545
+ var HAPTIC_DURATION_MS = 8;
2347
2546
  var BottomSheetEngine = class {
2348
2547
  constructor(opts) {
2349
2548
  __publicField(this, "id", nextInstanceId("bs"));
@@ -2368,6 +2567,7 @@ var BottomSheetEngine = class {
2368
2567
  __publicField(this, "disableDragFlag");
2369
2568
  __publicField(this, "closeOnRouteChange");
2370
2569
  __publicField(this, "maxHeightCap");
2570
+ __publicField(this, "maxHeightRaw");
2371
2571
  __publicField(this, "snapPointsRaw");
2372
2572
  __publicField(this, "snaps");
2373
2573
  __publicField(this, "activeId");
@@ -2403,6 +2603,7 @@ var BottomSheetEngine = class {
2403
2603
  __publicField(this, "detachScrimStages", null);
2404
2604
  __publicField(this, "stackEffectEnabled", false);
2405
2605
  __publicField(this, "stackEffectPrimed", false);
2606
+ __publicField(this, "fitDeps");
2406
2607
  __publicField(this, "transformTemplate");
2407
2608
  const resolved = resolveEngineOptions(opts, {
2408
2609
  shouldApplyInertSiblings: () => {
@@ -2475,12 +2676,19 @@ var BottomSheetEngine = class {
2475
2676
  }
2476
2677
  this.activeId = resolved.initialId;
2477
2678
  auditVhUsage(this.snapPointsRaw);
2679
+ this.auditDuplicateSnapIds(this.snapPointsRaw);
2478
2680
  this.rootEl = this.element.closest(".bs-root");
2681
+ this.fitDeps = {
2682
+ element: this.element,
2683
+ scrollContainer: this.scrollContainer,
2684
+ mode: this.mode,
2685
+ getMaxHeightCap: () => this.maxHeightCap
2686
+ };
2479
2687
  this.snaps = new SnapResolver(
2480
2688
  this.snapPointsRaw,
2481
2689
  resolved.initialAllowed,
2482
2690
  this.mode,
2483
- () => this.measureFitSize(),
2691
+ () => measureFitSize(this.fitDeps),
2484
2692
  (maxAxisSize) => {
2485
2693
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2486
2694
  }
@@ -2514,7 +2722,18 @@ var BottomSheetEngine = class {
2514
2722
  io.observe(this.element);
2515
2723
  this.teardowns.add(() => io.disconnect());
2516
2724
  }
2517
- this.installFitObserver();
2725
+ this.teardowns.add(
2726
+ installFitObserver({
2727
+ handle: this.handle,
2728
+ scrollContainer: this.scrollContainer,
2729
+ hasFitSnap: () => this.snapPointsRaw.some(
2730
+ (p) => p.size === "fit" || p.size === "content"
2731
+ ),
2732
+ isDestroyed: () => this.destroyed,
2733
+ isDragging: () => this.gesture?.isDragging ?? false,
2734
+ recompute: () => this.recompute()
2735
+ })
2736
+ );
2518
2737
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2519
2738
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2520
2739
  this.applyAutoAriaLabelledBy();
@@ -2665,27 +2884,33 @@ var BottomSheetEngine = class {
2665
2884
  } else {
2666
2885
  await this.animation.animateTo(target.size, velocityPxPerMs);
2667
2886
  }
2668
- this.opening = false;
2669
2887
  externalSignal?.removeEventListener("abort", onExternalAbort);
2670
2888
  if (signal.aborted) return;
2671
- this.scrollCache.restore(id, previousSize, target.size);
2889
+ this.opening = false;
2890
+ this.completeSnap(id, target.size, previousSize, wasClosed, false);
2891
+ }
2892
+ completeSnap(id, targetSize, previousSize, wasClosed, withHaptic) {
2893
+ this.scrollCache.restore(id, previousSize, targetSize);
2672
2894
  this.updateAriaSlider();
2673
2895
  this.emit("snap", {
2674
2896
  id,
2675
2897
  size: this.size,
2676
2898
  progress: this.computeProgress(this.size)
2677
2899
  });
2678
- if (wasClosed && target.size > 0) {
2679
- this.emit("open", { id });
2680
- this.handleOpen();
2681
- notifyLinkedSheets(this.linkedSheets, this);
2682
- this.emit("opened", { id });
2683
- }
2684
- if (target.size === 0) {
2685
- this.emit("close", void 0);
2686
- this.handleClose();
2687
- this.emit("closed", void 0);
2688
- }
2900
+ if (withHaptic) this.haptic();
2901
+ if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2902
+ if (targetSize === 0) this.emitCloseSequence();
2903
+ }
2904
+ emitOpenSequence(id) {
2905
+ this.emit("open", { id });
2906
+ this.handleOpen();
2907
+ notifyLinkedSheets(this.linkedSheets, this);
2908
+ this.emit("opened", { id });
2909
+ }
2910
+ emitCloseSequence() {
2911
+ this.emit("close", void 0);
2912
+ this.handleClose();
2913
+ this.emit("closed", void 0);
2689
2914
  }
2690
2915
  async dragTo(targetSize, velocityOrOpts = 0) {
2691
2916
  if (this.destroyed) return;
@@ -2706,19 +2931,31 @@ var BottomSheetEngine = class {
2706
2931
  }
2707
2932
  open(id) {
2708
2933
  if (this.destroyed) return Promise.resolve();
2709
- const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2934
+ const target = id ?? this.snaps.getAllowedIds().find((a) => (this.snaps.findById(a)?.size ?? 0) > 0) ?? this.activeId;
2710
2935
  return this.snapTo(target);
2711
2936
  }
2712
2937
  close(reason = "programmatic") {
2713
2938
  if (this.destroyed) return Promise.resolve();
2714
2939
  if (this.disableCloseFlag) return Promise.resolve();
2715
2940
  if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2716
- const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2941
+ const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds().find((a) => this.snaps.findById(a)?.size === 0) ?? this.snaps.getAllowedIds()[0];
2717
2942
  return this.snapTo(closedId ?? this.activeId);
2718
2943
  }
2719
2944
  canDismiss() {
2720
2945
  return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2721
2946
  }
2947
+ setPersistent(value) {
2948
+ if (this.destroyed) return;
2949
+ this.persistent = value;
2950
+ }
2951
+ setDisableClose(value) {
2952
+ if (this.destroyed) return;
2953
+ this.disableCloseFlag = value;
2954
+ }
2955
+ setDisableDrag(value) {
2956
+ if (this.destroyed) return;
2957
+ this.disableDragFlag = value;
2958
+ }
2722
2959
  isTop() {
2723
2960
  return this.isTopSheet;
2724
2961
  }
@@ -2755,18 +2992,23 @@ var BottomSheetEngine = class {
2755
2992
  axis === "height" ? "max-height" : "max-width",
2756
2993
  value
2757
2994
  );
2758
- if (typeof h === "number") {
2759
- this.maxHeightCap = h;
2760
- this.recompute();
2761
- } else if (typeof window !== "undefined") {
2762
- const probe2 = document.createElement("div");
2763
- probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2764
- document.body.appendChild(probe2);
2765
- const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2766
- probe2.remove();
2767
- this.maxHeightCap = measured > 0 ? measured : void 0;
2768
- this.recompute();
2995
+ this.maxHeightRaw = h;
2996
+ this.resolveMaxHeightCap();
2997
+ this.recompute();
2998
+ }
2999
+ resolveMaxHeightCap() {
3000
+ const raw = this.maxHeightRaw;
3001
+ if (raw === void 0) {
3002
+ this.maxHeightCap = void 0;
3003
+ return;
3004
+ }
3005
+ if (typeof raw === "number") {
3006
+ this.maxHeightCap = raw;
3007
+ return;
2769
3008
  }
3009
+ if (typeof window === "undefined") return;
3010
+ const measured = resolveSnap(raw, this.mode);
3011
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2770
3012
  }
2771
3013
  clampToMaxHeight() {
2772
3014
  if (this.maxHeightCap === void 0) return;
@@ -2897,6 +3139,7 @@ var BottomSheetEngine = class {
2897
3139
  this.newCycle();
2898
3140
  this.snapPointsRaw = points;
2899
3141
  auditVhUsage(points);
3142
+ this.auditDuplicateSnapIds(points);
2900
3143
  this.snaps.setRaw(points);
2901
3144
  if (allowed) this.snaps.setAllowedIds(allowed);
2902
3145
  this.scrollCache.clear();
@@ -2909,6 +3152,7 @@ var BottomSheetEngine = class {
2909
3152
  }
2910
3153
  recompute() {
2911
3154
  if (this.destroyed) return;
3155
+ this.resolveMaxHeightCap();
2912
3156
  this.snaps.recompute();
2913
3157
  this.clampToMaxHeight();
2914
3158
  this.scrim.invalidateOpacityCache();
@@ -2920,129 +3164,6 @@ var BottomSheetEngine = class {
2920
3164
  this.updateAriaSlider();
2921
3165
  }
2922
3166
  }
2923
- contentTargets() {
2924
- const scroller = this.scrollContainer;
2925
- if (!scroller) return [];
2926
- const hasSlot = typeof HTMLSlotElement !== "undefined";
2927
- const out = [];
2928
- for (const child2 of Array.from(scroller.children)) {
2929
- if (hasSlot && child2 instanceof HTMLSlotElement) {
2930
- for (const el of child2.assignedElements()) out.push(el);
2931
- } else {
2932
- out.push(child2);
2933
- }
2934
- }
2935
- return out;
2936
- }
2937
- measureSheetNatural(vertical) {
2938
- const el = this.element;
2939
- const content = this.scrollContainer;
2940
- if (!content) {
2941
- return vertical ? el.offsetHeight : el.offsetWidth;
2942
- }
2943
- const axis = vertical ? "height" : "width";
2944
- const sheetStyle = el.style;
2945
- const cs = content.style;
2946
- const prevSheet = sheetStyle[axis];
2947
- const prevFlex = cs.flex;
2948
- const prevContent = vertical ? cs.height : cs.width;
2949
- sheetStyle[axis] = "auto";
2950
- cs.flex = "none";
2951
- if (vertical) cs.height = "auto";
2952
- else cs.width = "auto";
2953
- const poked = vertical ? el.offsetHeight : el.offsetWidth;
2954
- const contentBox = vertical ? content.clientHeight : content.clientWidth;
2955
- const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
2956
- const natural = Math.max(poked, poked - contentBox + contentScroll);
2957
- sheetStyle[axis] = prevSheet;
2958
- cs.flex = prevFlex;
2959
- if (vertical) cs.height = prevContent;
2960
- else cs.width = prevContent;
2961
- return natural;
2962
- }
2963
- measureFitSize() {
2964
- const vertical = layoutAxis(this.mode) === "height";
2965
- const natural = this.measureSheetNatural(vertical);
2966
- const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
2967
- if (typeof window === "undefined") return capped;
2968
- const viewport = this.containingExtent(vertical);
2969
- return viewport > 0 ? Math.min(capped, viewport) : capped;
2970
- }
2971
- // The fit cap is the sheet's containing block, not always the window: a
2972
- // position:fixed sheet nested under a transform/filter/will-change ancestor
2973
- // is clipped to that ancestor, so capping by window.innerHeight oversizes the
2974
- // sheet and the content/footer get clipped. offsetParent resolves to that
2975
- // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
2976
- containingExtent(vertical) {
2977
- const op = this.element.offsetParent;
2978
- if (op && op !== document.body && op !== document.documentElement) {
2979
- return vertical ? op.clientHeight : op.clientWidth;
2980
- }
2981
- return vertical ? window.innerHeight : window.innerWidth;
2982
- }
2983
- installFitObserver() {
2984
- if (typeof ResizeObserver === "undefined") return;
2985
- if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2986
- return;
2987
- let raf = 0;
2988
- const schedule = () => {
2989
- if (raf) return;
2990
- raf = requestAnimationFrame(() => {
2991
- raf = 0;
2992
- if (this.destroyed || this.gesture?.isDragging) return;
2993
- this.recompute();
2994
- });
2995
- };
2996
- const ro = new ResizeObserver(schedule);
2997
- ro.observe(this.handle);
2998
- const scroller = this.scrollContainer;
2999
- const observed = /* @__PURE__ */ new Set();
3000
- const slots = /* @__PURE__ */ new Set();
3001
- const hasSlot = typeof HTMLSlotElement !== "undefined";
3002
- const onSlotChange = () => resync();
3003
- const trackSlots = () => {
3004
- if (!scroller || !hasSlot) return;
3005
- for (const child2 of Array.from(scroller.children)) {
3006
- if (child2 instanceof HTMLSlotElement && !slots.has(child2)) {
3007
- child2.addEventListener("slotchange", onSlotChange);
3008
- slots.add(child2);
3009
- }
3010
- }
3011
- };
3012
- const resync = () => {
3013
- trackSlots();
3014
- const targets = this.contentTargets();
3015
- for (const el of observed) {
3016
- if (!targets.includes(el)) {
3017
- ro.unobserve(el);
3018
- observed.delete(el);
3019
- }
3020
- }
3021
- for (const el of targets) {
3022
- if (!observed.has(el)) {
3023
- ro.observe(el);
3024
- observed.add(el);
3025
- }
3026
- }
3027
- schedule();
3028
- };
3029
- let mo;
3030
- if (scroller) {
3031
- resync();
3032
- if (typeof MutationObserver !== "undefined") {
3033
- mo = new MutationObserver(resync);
3034
- mo.observe(scroller, { childList: true });
3035
- }
3036
- }
3037
- this.teardowns.add(() => {
3038
- ro.disconnect();
3039
- mo?.disconnect();
3040
- for (const slot of slots) {
3041
- slot.removeEventListener("slotchange", onSlotChange);
3042
- }
3043
- if (raf) cancelAnimationFrame(raf);
3044
- });
3045
- }
3046
3167
  destroy() {
3047
3168
  this.destroyed = true;
3048
3169
  this.opening = false;
@@ -3060,9 +3181,10 @@ var BottomSheetEngine = class {
3060
3181
  this.backdrop.style.opacity = "";
3061
3182
  this.backdrop.style.pointerEvents = "";
3062
3183
  }
3063
- if (this.anchorHost) {
3064
- this.anchorHost.style.removeProperty("--bs-size");
3065
- this.anchorHost.style.removeProperty("--bs-progress");
3184
+ for (const host of [this.anchorHost, this.scrimParent, this.rootEl]) {
3185
+ if (!host) continue;
3186
+ host.style.removeProperty("--bs-size");
3187
+ host.style.removeProperty("--bs-progress");
3066
3188
  }
3067
3189
  if (this.screenComponent) {
3068
3190
  this.screenComponent.style.opacity = "";
@@ -3076,6 +3198,7 @@ var BottomSheetEngine = class {
3076
3198
  }
3077
3199
  newCycle() {
3078
3200
  this.allowOvershoot = false;
3201
+ this.opening = false;
3079
3202
  this.currentAbort.abort();
3080
3203
  this.currentAbort = new AbortController();
3081
3204
  return this.currentAbort.signal;
@@ -3131,8 +3254,14 @@ var BottomSheetEngine = class {
3131
3254
  this.element.setAttribute("aria-labelledby", titled.id);
3132
3255
  }
3133
3256
  recomputeSnaps() {
3257
+ this.resolveMaxHeightCap();
3258
+ this.snaps.setRaw(this.snapPointsRaw);
3259
+ this.clampToMaxHeight();
3260
+ this.scrim.invalidateOpacityCache();
3261
+ }
3262
+ auditDuplicateSnapIds(points) {
3134
3263
  const seen = /* @__PURE__ */ new Set();
3135
- for (const p of this.snapPointsRaw) {
3264
+ for (const p of points) {
3136
3265
  if (seen.has(p.id)) {
3137
3266
  console.warn(
3138
3267
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
@@ -3140,9 +3269,6 @@ var BottomSheetEngine = class {
3140
3269
  }
3141
3270
  seen.add(p.id);
3142
3271
  }
3143
- this.snaps.setRaw(this.snapPointsRaw);
3144
- this.clampToMaxHeight();
3145
- this.scrim.invalidateOpacityCache();
3146
3272
  }
3147
3273
  getAllowedRange() {
3148
3274
  return this.snaps.getAllowedRange();
@@ -3330,9 +3456,15 @@ var BottomSheetEngine = class {
3330
3456
  flickVelocity: this.flickVelocity,
3331
3457
  dragThreshold: this.dragThreshold
3332
3458
  });
3333
- if (!target) return;
3459
+ if (!target) {
3460
+ this.clearWillChangeIfIdle();
3461
+ return;
3462
+ }
3334
3463
  const previousId = this.activeId;
3335
- if (target.id === previousId && target.size === this.size) return;
3464
+ if (target.id === previousId && target.size === this.size) {
3465
+ this.clearWillChangeIfIdle();
3466
+ return;
3467
+ }
3336
3468
  if (this.emitBeforeSnap(target, previousId)) {
3337
3469
  const restore = this.snaps.findById(previousId);
3338
3470
  if (restore) {
@@ -3355,27 +3487,19 @@ var BottomSheetEngine = class {
3355
3487
  if (signal.aborted) return;
3356
3488
  this.allowOvershoot = false;
3357
3489
  this.opening = false;
3358
- this.scrollCache.restore(target.id, previousSize, target.size);
3359
- this.updateAriaSlider();
3360
- this.emit("snap", {
3361
- id: target.id,
3362
- size: this.size,
3363
- progress: this.computeProgress(this.size)
3364
- });
3365
- this.haptic();
3366
- if (previousSnapSize === 0 && target.size > 0) {
3367
- this.emit("open", { id: target.id });
3368
- this.handleOpen();
3369
- notifyLinkedSheets(this.linkedSheets, this);
3370
- this.emit("opened", { id: target.id });
3371
- }
3372
- if (target.size === 0) {
3373
- this.emit("close", void 0);
3374
- this.handleClose();
3375
- this.emit("closed", void 0);
3376
- }
3490
+ this.completeSnap(
3491
+ target.id,
3492
+ target.size,
3493
+ previousSize,
3494
+ previousSnapSize === 0,
3495
+ true
3496
+ );
3377
3497
  });
3378
3498
  }
3499
+ clearWillChangeIfIdle() {
3500
+ if (this.animation.isAnimating || this.gesture?.isDragging) return;
3501
+ this.element.style.willChange = "";
3502
+ }
3379
3503
  haptic() {
3380
3504
  if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3381
3505
  try {
@@ -3456,14 +3580,9 @@ var BottomSheetEngine = class {
3456
3580
  progress: this.computeProgress(this.size)
3457
3581
  });
3458
3582
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3459
- this.emit("open", { id: target.id });
3460
- this.handleOpen();
3461
- notifyLinkedSheets(this.linkedSheets, this);
3462
- this.emit("opened", { id: target.id });
3583
+ this.emitOpenSequence(target.id);
3463
3584
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3464
- this.emit("close", void 0);
3465
- this.handleClose();
3466
- this.emit("closed", void 0);
3585
+ this.emitCloseSequence();
3467
3586
  }
3468
3587
  }
3469
3588
  handleClose() {
@@ -3595,7 +3714,7 @@ var root_7 = $2__namespace.from_html(`<div class="bs-button-slot" data-side="rig
3595
3714
  var root = $2__namespace.from_html(`<div class="bs-root"><!> <div class="bs-screen"><!></div> <section class="bs-sheet" role="dialog"><div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0"></div> <!> <div class="bs-content" tabindex="0" role="region" aria-label="Sheet content"><!></div> <!> <span class="bs-sr-only" role="status" aria-live="polite"> </span></section> <!> <!></div>`);
3596
3715
  function BottomSheet($$anchor, $$props) {
3597
3716
  $2__namespace.push($$props, true);
3598
- let mode = $2__namespace.prop($$props, "mode", 3, "bottom"), animation = $2__namespace.prop($$props, "animation", 3, "spring"), focusTrap = $2__namespace.prop($$props, "focusTrap", 3, false), closeOnEscape = $2__namespace.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2__namespace.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2__namespace.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2__namespace.prop($$props, "rubberBand", 3, true), backdrop = $2__namespace.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2__namespace.prop($$props, "closeOnBackdrop", 3, true), persistent = $2__namespace.prop($$props, "persistent", 3, false), disableClose = $2__namespace.prop($$props, "disableClose", 3, false), disableDrag = $2__namespace.prop($$props, "disableDrag", 3, false), closeOnRouteChange = $2__namespace.prop($$props, "closeOnRouteChange", 3, false), openProp = $2__namespace.prop($$props, "open", 15), snap = $2__namespace.prop($$props, "snap", 15), ariaLabel = $2__namespace.prop($$props, "ariaLabel", 3, "Bottom sheet");
3717
+ let mode = $2__namespace.prop($$props, "mode", 3, "bottom"), animation = $2__namespace.prop($$props, "animation", 3, "spring"), focusTrap = $2__namespace.prop($$props, "focusTrap", 3, false), closeOnEscape = $2__namespace.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2__namespace.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2__namespace.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2__namespace.prop($$props, "rubberBand", 3, true), backdrop = $2__namespace.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2__namespace.prop($$props, "closeOnBackdrop", 3, true), persistent = $2__namespace.prop($$props, "persistent", 3, false), disableClose = $2__namespace.prop($$props, "disableClose", 3, false), disableDrag = $2__namespace.prop($$props, "disableDrag", 3, false), closeOnRouteChange = $2__namespace.prop($$props, "closeOnRouteChange", 3, false), stackEffect = $2__namespace.prop($$props, "stackEffect", 3, false), teleport = $2__namespace.prop($$props, "teleport", 3, false), openProp = $2__namespace.prop($$props, "open", 15), snap = $2__namespace.prop($$props, "snap", 15), ariaLabel = $2__namespace.prop($$props, "ariaLabel", 3, "Bottom sheet");
3599
3718
  let sheetEl = $2__namespace.state(void 0);
3600
3719
  let handleEl = $2__namespace.state(void 0);
3601
3720
  let contentEl = $2__namespace.state(void 0);
@@ -3609,6 +3728,7 @@ function BottomSheet($$anchor, $$props) {
3609
3728
  progress: 0
3610
3729
  }));
3611
3730
  let engine = null;
3731
+ let restoreTeleport = null;
3612
3732
  svelte.onMount(() => {
3613
3733
  if (!$2__namespace.get(sheetEl)) return;
3614
3734
  engine = new BottomSheetEngine({
@@ -3633,10 +3753,15 @@ function BottomSheet($$anchor, $$props) {
3633
3753
  disableClose: disableClose(),
3634
3754
  disableDrag: disableDrag(),
3635
3755
  closeOnRouteChange: closeOnRouteChange(),
3756
+ stackEffect: stackEffect(),
3636
3757
  radius: $$props.radius,
3637
3758
  maxHeight: $$props.maxHeight,
3638
3759
  returnFocusTo: $$props.returnFocusTo
3639
3760
  });
3761
+ const teleportTarget = teleport() === false ? null : resolveTeleportTarget($$props.teleportTo);
3762
+ if (teleportTarget) {
3763
+ restoreTeleport = teleportElements([$2__namespace.get(backdropEl), $2__namespace.get(screenEl), $2__namespace.get(sheetEl)], teleportTarget);
3764
+ }
3640
3765
  const sync = () => {
3641
3766
  if (!engine) return;
3642
3767
  $2__namespace.set(viewState, { ...engine.state }, true);
@@ -3659,6 +3784,7 @@ function BottomSheet($$anchor, $$props) {
3659
3784
  $$props.onclose?.();
3660
3785
  });
3661
3786
  engine.on("before-close", (payload) => $$props.onbeforeclose?.(payload));
3787
+ engine.on("before-snap", (payload) => $$props.onbeforesnap?.(payload));
3662
3788
  engine.on("opened", (payload) => $$props.onopened?.(payload.id));
3663
3789
  engine.on("closed", () => $$props.onclosed?.());
3664
3790
  engine.on("dragstart", () => {
@@ -3671,6 +3797,8 @@ function BottomSheet($$anchor, $$props) {
3671
3797
  });
3672
3798
  });
3673
3799
  svelte.onDestroy(() => {
3800
+ restoreTeleport?.();
3801
+ restoreTeleport = null;
3674
3802
  engine?.destroy();
3675
3803
  engine = null;
3676
3804
  });
@@ -3691,6 +3819,33 @@ function BottomSheet($$anchor, $$props) {
3691
3819
  const canDismiss = () => engine?.canDismiss() ?? false;
3692
3820
  const getState = () => engine?.state ?? $2__namespace.get(viewState);
3693
3821
  const getEngine = () => engine;
3822
+ let snapDefApplied = false;
3823
+ $2__namespace.user_effect(() => {
3824
+ const points = $$props.snapPoints;
3825
+ if (!snapDefApplied) {
3826
+ snapDefApplied = true;
3827
+ return;
3828
+ }
3829
+ engine?.setSnapPoints(points, svelte.untrack(() => $$props.allowed));
3830
+ });
3831
+ let allowedApplied = false;
3832
+ $2__namespace.user_effect(() => {
3833
+ const ids = $$props.allowed;
3834
+ if (!allowedApplied) {
3835
+ allowedApplied = true;
3836
+ return;
3837
+ }
3838
+ if (ids) engine?.setAllowed(ids);
3839
+ });
3840
+ $2__namespace.user_effect(() => {
3841
+ engine?.setPersistent(persistent());
3842
+ });
3843
+ $2__namespace.user_effect(() => {
3844
+ engine?.setDisableClose(disableClose());
3845
+ });
3846
+ $2__namespace.user_effect(() => {
3847
+ engine?.setDisableDrag(disableDrag());
3848
+ });
3694
3849
  $2__namespace.user_effect(() => {
3695
3850
  if ($$props.radius !== void 0) engine?.setRadius($$props.radius);
3696
3851
  });