@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.js CHANGED
@@ -266,18 +266,25 @@ var SnapResolver = class {
266
266
  __publicField(this, "resolved", []);
267
267
  __publicField(this, "maxAxisSize", 0);
268
268
  __publicField(this, "rangeCache", null);
269
+ __publicField(this, "notified", false);
269
270
  this.raw = raw;
270
271
  this.allowedIds = allowed ?? raw.map((p) => p.id);
271
272
  this.recompute();
272
273
  }
273
274
  recompute() {
274
- this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
275
- this.maxAxisSize = this.resolved.reduce(
275
+ const measure = this.measureFit;
276
+ let measured;
277
+ const measureOnce = measure ? () => measured ?? (measured = measure()) : void 0;
278
+ this.resolved = resolveSnapList(this.raw, this.mode, measureOnce);
279
+ const nextMax = this.resolved.reduce(
276
280
  (m, s) => s.size > m ? s.size : m,
277
281
  0
278
282
  );
279
283
  this.rangeCache = null;
280
- this.onMaxAxisSizeChange?.(this.maxAxisSize);
284
+ const changed = nextMax !== this.maxAxisSize || !this.notified;
285
+ this.maxAxisSize = nextMax;
286
+ this.notified = true;
287
+ if (changed) this.onMaxAxisSizeChange?.(this.maxAxisSize);
281
288
  }
282
289
  setRaw(raw) {
283
290
  this.raw = raw;
@@ -430,7 +437,10 @@ var ensureHandler = () => {
430
437
  target.consumed = true;
431
438
  const idx = closers.indexOf(target);
432
439
  if (idx !== -1) closers.splice(idx, 1);
433
- target.close();
440
+ const settled = target;
441
+ void Promise.resolve(settled.close()).then(() => {
442
+ if (settled.isOpen()) settled.restore();
443
+ });
434
444
  });
435
445
  };
436
446
  var programmaticBack = () => {
@@ -466,16 +476,7 @@ function installRoute(deps) {
466
476
  pushed = false;
467
477
  }
468
478
  };
469
- const onOpen = () => {
470
- if (closer) return;
471
- closer = {
472
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
473
- isTop: () => deps.isTopSheet(),
474
- close: () => void deps.close(),
475
- consumed: false
476
- };
477
- ensureHandler();
478
- closers.push(closer);
479
+ const pushEntry = () => {
479
480
  try {
480
481
  if (deps.routedTo !== void 0) {
481
482
  history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
@@ -487,6 +488,25 @@ function installRoute(deps) {
487
488
  pushed = false;
488
489
  }
489
490
  };
491
+ const onOpen = () => {
492
+ if (closer) return;
493
+ const self = {
494
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
495
+ isTop: () => deps.isTopSheet(),
496
+ close: () => deps.close(),
497
+ restore: () => {
498
+ if (deps.isDestroyed() || closer !== self) return;
499
+ self.consumed = false;
500
+ if (!closers.includes(self)) closers.push(self);
501
+ pushEntry();
502
+ },
503
+ consumed: false
504
+ };
505
+ closer = self;
506
+ ensureHandler();
507
+ closers.push(self);
508
+ pushEntry();
509
+ };
490
510
  const unsubscribeOpen = deps.on("open", onOpen);
491
511
  const unsubscribeClose = deps.on("close", teardownEntry);
492
512
  if (deps.getSize() > 0) onOpen();
@@ -533,11 +553,13 @@ var CONTENT_PULL_THRESHOLD = 60;
533
553
  function installContentSwipe(deps) {
534
554
  const { container } = deps;
535
555
  let startY = 0;
556
+ let startScrollTop = 0;
536
557
  let moved = false;
537
558
  const onTouchStart = (e) => {
538
559
  const t = e.touches[0];
539
560
  if (!t) return;
540
561
  startY = t.clientY;
562
+ startScrollTop = container.scrollTop;
541
563
  moved = false;
542
564
  };
543
565
  const onTouchMove = (e) => {
@@ -548,6 +570,7 @@ function installContentSwipe(deps) {
548
570
  const onTouchEnd = (e) => {
549
571
  if (!moved) return;
550
572
  if (deps.isDragging() || deps.isAnimating()) return;
573
+ if (container.scrollTop !== startScrollTop) return;
551
574
  const t = e.changedTouches[0];
552
575
  if (!t) return;
553
576
  const deltaY = t.clientY - startY;
@@ -659,6 +682,131 @@ function installResizeObserver(deps) {
659
682
  };
660
683
  }
661
684
 
685
+ // src/core/features/fit-observer.ts
686
+ var contentTargets = (scroller) => {
687
+ if (!scroller) return [];
688
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
689
+ const out = [];
690
+ for (const child2 of Array.from(scroller.children)) {
691
+ if (hasSlot && child2 instanceof HTMLSlotElement) {
692
+ for (const el of child2.assignedElements()) out.push(el);
693
+ } else {
694
+ out.push(child2);
695
+ }
696
+ }
697
+ return out;
698
+ };
699
+ function installFitObserver(deps) {
700
+ if (typeof ResizeObserver === "undefined") return () => {
701
+ };
702
+ if (!deps.hasFitSnap()) return () => {
703
+ };
704
+ let raf = 0;
705
+ const schedule = () => {
706
+ if (raf) return;
707
+ raf = requestAnimationFrame(() => {
708
+ raf = 0;
709
+ if (deps.isDestroyed() || deps.isDragging()) return;
710
+ deps.recompute();
711
+ });
712
+ };
713
+ const ro = new ResizeObserver(schedule);
714
+ ro.observe(deps.handle);
715
+ const scroller = deps.scrollContainer;
716
+ const observed = /* @__PURE__ */ new Set();
717
+ const slots = /* @__PURE__ */ new Set();
718
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
719
+ const onSlotChange = () => resync();
720
+ const trackSlots = () => {
721
+ if (!scroller || !hasSlot) return;
722
+ for (const child2 of Array.from(scroller.children)) {
723
+ if (child2 instanceof HTMLSlotElement && !slots.has(child2)) {
724
+ child2.addEventListener("slotchange", onSlotChange);
725
+ slots.add(child2);
726
+ }
727
+ }
728
+ };
729
+ const resync = () => {
730
+ trackSlots();
731
+ const targets = contentTargets(scroller);
732
+ for (const el of observed) {
733
+ if (!targets.includes(el)) {
734
+ ro.unobserve(el);
735
+ observed.delete(el);
736
+ }
737
+ }
738
+ for (const el of targets) {
739
+ if (!observed.has(el)) {
740
+ ro.observe(el);
741
+ observed.add(el);
742
+ }
743
+ }
744
+ schedule();
745
+ };
746
+ let mo;
747
+ if (scroller) {
748
+ resync();
749
+ if (typeof MutationObserver !== "undefined") {
750
+ mo = new MutationObserver(resync);
751
+ mo.observe(scroller, { childList: true });
752
+ }
753
+ }
754
+ return () => {
755
+ ro.disconnect();
756
+ mo?.disconnect();
757
+ for (const slot of slots) {
758
+ slot.removeEventListener("slotchange", onSlotChange);
759
+ }
760
+ if (raf) cancelAnimationFrame(raf);
761
+ };
762
+ }
763
+
764
+ // src/core/features/fit-measurement.ts
765
+ var measureSheetNatural = (element, content, vertical) => {
766
+ if (!content) {
767
+ return vertical ? element.offsetHeight : element.offsetWidth;
768
+ }
769
+ const axis = vertical ? "height" : "width";
770
+ const sheetStyle = element.style;
771
+ const cs = content.style;
772
+ const prevSheet = sheetStyle[axis];
773
+ const prevFlex = cs.flex;
774
+ const prevContent = vertical ? cs.height : cs.width;
775
+ sheetStyle[axis] = "auto";
776
+ cs.flex = "none";
777
+ if (vertical) cs.height = "auto";
778
+ else cs.width = "auto";
779
+ const poked = vertical ? element.offsetHeight : element.offsetWidth;
780
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
781
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
782
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
783
+ sheetStyle[axis] = prevSheet;
784
+ cs.flex = prevFlex;
785
+ if (vertical) cs.height = prevContent;
786
+ else cs.width = prevContent;
787
+ return natural;
788
+ };
789
+ var containingExtent = (element, vertical) => {
790
+ const op = element.offsetParent;
791
+ if (op && op !== document.body && op !== document.documentElement) {
792
+ return vertical ? op.clientHeight : op.clientWidth;
793
+ }
794
+ return vertical ? window.innerHeight : window.innerWidth;
795
+ };
796
+ var measureFitSize = (deps) => {
797
+ const vertical = layoutAxis(deps.mode) === "height";
798
+ const natural = measureSheetNatural(
799
+ deps.element,
800
+ deps.scrollContainer,
801
+ vertical
802
+ );
803
+ const cap = deps.getMaxHeightCap();
804
+ const capped = cap !== void 0 ? Math.min(natural, cap) : natural;
805
+ if (typeof window === "undefined") return capped;
806
+ const viewport = containingExtent(deps.element, vertical);
807
+ return viewport > 0 ? Math.min(capped, viewport) : capped;
808
+ };
809
+
662
810
  // src/core/features/slider-keyboard.ts
663
811
  function installSliderKeyboard(deps) {
664
812
  const isVerticalAxis = deps.mode === "bottom" || deps.mode === "top";
@@ -1145,6 +1293,9 @@ var ScrimController = class {
1145
1293
  const s = this.screenComponent.style;
1146
1294
  s.opacity = "";
1147
1295
  s.display = "";
1296
+ s.position = "";
1297
+ s.inset = "";
1298
+ s.pointerEvents = "";
1148
1299
  }
1149
1300
  if (this.backdrop) {
1150
1301
  this.backdrop.style.opacity = "";
@@ -1393,7 +1544,12 @@ var AnimationRunner = class {
1393
1544
  this.currentSpring?.cancel();
1394
1545
  }
1395
1546
  async animateTo(target, velocityPxPerMs) {
1396
- if (target === this.getSize()) return;
1547
+ if (target === this.getSize()) {
1548
+ if (!this.isAnimating && !this.isDragging()) {
1549
+ this.element.style.willChange = "";
1550
+ }
1551
+ return;
1552
+ }
1397
1553
  this.currentTween?.cancel();
1398
1554
  this.currentSpring?.cancel();
1399
1555
  this.getRootEl()?.setAttribute("data-animating", "true");
@@ -1401,7 +1557,7 @@ var AnimationRunner = class {
1401
1557
  if (this.reducedMotion) {
1402
1558
  this.applySizeFn(target);
1403
1559
  this.getRootEl()?.removeAttribute("data-animating");
1404
- if (!this.isDragging()) this.element.style.willChange = "auto";
1560
+ if (!this.isDragging()) this.element.style.willChange = "";
1405
1561
  return;
1406
1562
  }
1407
1563
  if (this.animationKind === "spring") {
@@ -1429,7 +1585,7 @@ var AnimationRunner = class {
1429
1585
  }
1430
1586
  if (!this.isAnimating) {
1431
1587
  this.getRootEl()?.removeAttribute("data-animating");
1432
- if (!this.isDragging()) this.element.style.willChange = "auto";
1588
+ if (!this.isDragging()) this.element.style.willChange = "";
1433
1589
  }
1434
1590
  }
1435
1591
  destroy() {
@@ -1460,9 +1616,35 @@ var isVisible = (el) => {
1460
1616
  }
1461
1617
  return el.offsetParent !== null;
1462
1618
  };
1463
- var focusables = (root2) => Array.from(root2.querySelectorAll(FOCUSABLE)).filter(
1619
+ var collectFocusables = (root2) => {
1620
+ const out = Array.from(root2.querySelectorAll(FOCUSABLE));
1621
+ if (typeof HTMLSlotElement !== "undefined") {
1622
+ for (const slot of Array.from(root2.querySelectorAll("slot"))) {
1623
+ for (const assigned of slot.assignedElements()) {
1624
+ if (assigned.matches(FOCUSABLE)) out.push(assigned);
1625
+ out.push(...collectFocusables(assigned));
1626
+ }
1627
+ }
1628
+ }
1629
+ return out;
1630
+ };
1631
+ var focusables = (root2) => collectFocusables(root2).filter(
1464
1632
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1465
1633
  );
1634
+ var containsComposed = (container, node) => {
1635
+ let n = node;
1636
+ while (n) {
1637
+ if (n === container) return true;
1638
+ const slot = n.assignedSlot;
1639
+ if (slot) {
1640
+ n = slot;
1641
+ continue;
1642
+ }
1643
+ const parent = n.parentNode;
1644
+ n = parent instanceof ShadowRoot ? parent.host : parent;
1645
+ }
1646
+ return false;
1647
+ };
1466
1648
  var trapStack = [];
1467
1649
  var installFocusTrap = (container, options = {}) => {
1468
1650
  if (typeof document === "undefined") return () => {
@@ -1472,7 +1654,13 @@ var installFocusTrap = (container, options = {}) => {
1472
1654
  const isActive = () => trapStack[trapStack.length - 1] === token;
1473
1655
  const previouslyFocused = document.activeElement;
1474
1656
  const focusInitial = () => {
1475
- const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
1657
+ const spec = options.initialFocus;
1658
+ if (spec === false) {
1659
+ if (!container.hasAttribute("tabindex")) container.tabIndex = -1;
1660
+ container.focus({ preventScroll: true });
1661
+ return;
1662
+ }
1663
+ const initial = typeof spec === "string" ? container.querySelector(spec) : spec ?? focusables(container)[0] ?? container;
1476
1664
  initial?.focus({ preventScroll: true });
1477
1665
  };
1478
1666
  focusInitial();
@@ -1502,7 +1690,7 @@ var installFocusTrap = (container, options = {}) => {
1502
1690
  const handleFocusIn = (e) => {
1503
1691
  if (!isActive()) return;
1504
1692
  const target = e.target;
1505
- if (target && !container.contains(target)) {
1693
+ if (target && !containsComposed(container, target)) {
1506
1694
  const list = focusables(container);
1507
1695
  list[0]?.focus({ preventScroll: true });
1508
1696
  }
@@ -1539,8 +1727,14 @@ var lockBodyScroll = () => {
1539
1727
  position: body.style.position,
1540
1728
  top: body.style.top,
1541
1729
  width: body.style.width,
1730
+ paddingRight: body.style.paddingRight,
1542
1731
  scrollY: window.scrollY
1543
1732
  };
1733
+ const scrollbarGap = window.innerWidth - document.documentElement.clientWidth;
1734
+ if (scrollbarGap > 0) {
1735
+ const current = parseFloat(getComputedStyle(body).paddingRight) || 0;
1736
+ body.style.paddingRight = `${current + scrollbarGap}px`;
1737
+ }
1544
1738
  body.style.overflow = "hidden";
1545
1739
  body.style.position = "fixed";
1546
1740
  body.style.top = `-${savedStyles.scrollY}px`;
@@ -1558,6 +1752,7 @@ var lockBodyScroll = () => {
1558
1752
  body.style.position = savedStyles.position;
1559
1753
  body.style.top = savedStyles.top;
1560
1754
  body.style.width = savedStyles.width;
1755
+ body.style.paddingRight = savedStyles.paddingRight;
1561
1756
  window.scrollTo(0, savedStyles.scrollY);
1562
1757
  savedStyles = null;
1563
1758
  }
@@ -1770,12 +1965,14 @@ var installGestures = (handle, mode, callbacks) => {
1770
1965
  handle.addEventListener("pointermove", onPointerMove);
1771
1966
  handle.addEventListener("pointerup", finishGesture);
1772
1967
  handle.addEventListener("pointercancel", onPointerCancel);
1968
+ const prevTouchAction = handle.style.touchAction;
1773
1969
  handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1774
1970
  return () => {
1775
1971
  handle.removeEventListener("pointerdown", onPointerDown);
1776
1972
  handle.removeEventListener("pointermove", onPointerMove);
1777
1973
  handle.removeEventListener("pointerup", finishGesture);
1778
1974
  handle.removeEventListener("pointercancel", onPointerCancel);
1975
+ handle.style.touchAction = prevTouchAction;
1779
1976
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1780
1977
  handle.releasePointerCapture(activePointerId);
1781
1978
  }
@@ -1902,7 +2099,7 @@ var GestureController = class {
1902
2099
  this.isDragging_ = false;
1903
2100
  this.keyboardDismissed = false;
1904
2101
  this.deps.getRoot()?.removeAttribute("data-dragging");
1905
- this.deps.element.style.willChange = "auto";
2102
+ this.deps.element.style.willChange = "";
1906
2103
  }
1907
2104
  };
1908
2105
 
@@ -2301,8 +2498,7 @@ function resolveEngineOptions(opts, extras) {
2301
2498
  };
2302
2499
  }
2303
2500
 
2304
- // src/core/BottomSheetEngine.ts
2305
- var HAPTIC_DURATION_MS = 8;
2501
+ // src/core/primitives/teardown-stack.ts
2306
2502
  var TeardownStack = class {
2307
2503
  constructor() {
2308
2504
  __publicField(this, "fns", []);
@@ -2322,6 +2518,9 @@ var TeardownStack = class {
2322
2518
  }
2323
2519
  }
2324
2520
  };
2521
+
2522
+ // src/core/BottomSheetEngine.ts
2523
+ var HAPTIC_DURATION_MS = 8;
2325
2524
  var BottomSheetEngine = class {
2326
2525
  constructor(opts) {
2327
2526
  __publicField(this, "id", nextInstanceId("bs"));
@@ -2346,6 +2545,7 @@ var BottomSheetEngine = class {
2346
2545
  __publicField(this, "disableDragFlag");
2347
2546
  __publicField(this, "closeOnRouteChange");
2348
2547
  __publicField(this, "maxHeightCap");
2548
+ __publicField(this, "maxHeightRaw");
2349
2549
  __publicField(this, "snapPointsRaw");
2350
2550
  __publicField(this, "snaps");
2351
2551
  __publicField(this, "activeId");
@@ -2381,6 +2581,7 @@ var BottomSheetEngine = class {
2381
2581
  __publicField(this, "detachScrimStages", null);
2382
2582
  __publicField(this, "stackEffectEnabled", false);
2383
2583
  __publicField(this, "stackEffectPrimed", false);
2584
+ __publicField(this, "fitDeps");
2384
2585
  __publicField(this, "transformTemplate");
2385
2586
  const resolved = resolveEngineOptions(opts, {
2386
2587
  shouldApplyInertSiblings: () => {
@@ -2453,12 +2654,19 @@ var BottomSheetEngine = class {
2453
2654
  }
2454
2655
  this.activeId = resolved.initialId;
2455
2656
  auditVhUsage(this.snapPointsRaw);
2657
+ this.auditDuplicateSnapIds(this.snapPointsRaw);
2456
2658
  this.rootEl = this.element.closest(".bs-root");
2659
+ this.fitDeps = {
2660
+ element: this.element,
2661
+ scrollContainer: this.scrollContainer,
2662
+ mode: this.mode,
2663
+ getMaxHeightCap: () => this.maxHeightCap
2664
+ };
2457
2665
  this.snaps = new SnapResolver(
2458
2666
  this.snapPointsRaw,
2459
2667
  resolved.initialAllowed,
2460
2668
  this.mode,
2461
- () => this.measureFitSize(),
2669
+ () => measureFitSize(this.fitDeps),
2462
2670
  (maxAxisSize) => {
2463
2671
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2464
2672
  }
@@ -2492,7 +2700,18 @@ var BottomSheetEngine = class {
2492
2700
  io.observe(this.element);
2493
2701
  this.teardowns.add(() => io.disconnect());
2494
2702
  }
2495
- this.installFitObserver();
2703
+ this.teardowns.add(
2704
+ installFitObserver({
2705
+ handle: this.handle,
2706
+ scrollContainer: this.scrollContainer,
2707
+ hasFitSnap: () => this.snapPointsRaw.some(
2708
+ (p) => p.size === "fit" || p.size === "content"
2709
+ ),
2710
+ isDestroyed: () => this.destroyed,
2711
+ isDragging: () => this.gesture?.isDragging ?? false,
2712
+ recompute: () => this.recompute()
2713
+ })
2714
+ );
2496
2715
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2497
2716
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2498
2717
  this.applyAutoAriaLabelledBy();
@@ -2643,27 +2862,33 @@ var BottomSheetEngine = class {
2643
2862
  } else {
2644
2863
  await this.animation.animateTo(target.size, velocityPxPerMs);
2645
2864
  }
2646
- this.opening = false;
2647
2865
  externalSignal?.removeEventListener("abort", onExternalAbort);
2648
2866
  if (signal.aborted) return;
2649
- this.scrollCache.restore(id, previousSize, target.size);
2867
+ this.opening = false;
2868
+ this.completeSnap(id, target.size, previousSize, wasClosed, false);
2869
+ }
2870
+ completeSnap(id, targetSize, previousSize, wasClosed, withHaptic) {
2871
+ this.scrollCache.restore(id, previousSize, targetSize);
2650
2872
  this.updateAriaSlider();
2651
2873
  this.emit("snap", {
2652
2874
  id,
2653
2875
  size: this.size,
2654
2876
  progress: this.computeProgress(this.size)
2655
2877
  });
2656
- if (wasClosed && target.size > 0) {
2657
- this.emit("open", { id });
2658
- this.handleOpen();
2659
- notifyLinkedSheets(this.linkedSheets, this);
2660
- this.emit("opened", { id });
2661
- }
2662
- if (target.size === 0) {
2663
- this.emit("close", void 0);
2664
- this.handleClose();
2665
- this.emit("closed", void 0);
2666
- }
2878
+ if (withHaptic) this.haptic();
2879
+ if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2880
+ if (targetSize === 0) this.emitCloseSequence();
2881
+ }
2882
+ emitOpenSequence(id) {
2883
+ this.emit("open", { id });
2884
+ this.handleOpen();
2885
+ notifyLinkedSheets(this.linkedSheets, this);
2886
+ this.emit("opened", { id });
2887
+ }
2888
+ emitCloseSequence() {
2889
+ this.emit("close", void 0);
2890
+ this.handleClose();
2891
+ this.emit("closed", void 0);
2667
2892
  }
2668
2893
  async dragTo(targetSize, velocityOrOpts = 0) {
2669
2894
  if (this.destroyed) return;
@@ -2684,19 +2909,31 @@ var BottomSheetEngine = class {
2684
2909
  }
2685
2910
  open(id) {
2686
2911
  if (this.destroyed) return Promise.resolve();
2687
- const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2912
+ const target = id ?? this.snaps.getAllowedIds().find((a) => (this.snaps.findById(a)?.size ?? 0) > 0) ?? this.activeId;
2688
2913
  return this.snapTo(target);
2689
2914
  }
2690
2915
  close(reason = "programmatic") {
2691
2916
  if (this.destroyed) return Promise.resolve();
2692
2917
  if (this.disableCloseFlag) return Promise.resolve();
2693
2918
  if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2694
- const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2919
+ 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];
2695
2920
  return this.snapTo(closedId ?? this.activeId);
2696
2921
  }
2697
2922
  canDismiss() {
2698
2923
  return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2699
2924
  }
2925
+ setPersistent(value) {
2926
+ if (this.destroyed) return;
2927
+ this.persistent = value;
2928
+ }
2929
+ setDisableClose(value) {
2930
+ if (this.destroyed) return;
2931
+ this.disableCloseFlag = value;
2932
+ }
2933
+ setDisableDrag(value) {
2934
+ if (this.destroyed) return;
2935
+ this.disableDragFlag = value;
2936
+ }
2700
2937
  isTop() {
2701
2938
  return this.isTopSheet;
2702
2939
  }
@@ -2733,18 +2970,23 @@ var BottomSheetEngine = class {
2733
2970
  axis === "height" ? "max-height" : "max-width",
2734
2971
  value
2735
2972
  );
2736
- if (typeof h === "number") {
2737
- this.maxHeightCap = h;
2738
- this.recompute();
2739
- } else if (typeof window !== "undefined") {
2740
- const probe2 = document.createElement("div");
2741
- probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2742
- document.body.appendChild(probe2);
2743
- const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2744
- probe2.remove();
2745
- this.maxHeightCap = measured > 0 ? measured : void 0;
2746
- this.recompute();
2973
+ this.maxHeightRaw = h;
2974
+ this.resolveMaxHeightCap();
2975
+ this.recompute();
2976
+ }
2977
+ resolveMaxHeightCap() {
2978
+ const raw = this.maxHeightRaw;
2979
+ if (raw === void 0) {
2980
+ this.maxHeightCap = void 0;
2981
+ return;
2747
2982
  }
2983
+ if (typeof raw === "number") {
2984
+ this.maxHeightCap = raw;
2985
+ return;
2986
+ }
2987
+ if (typeof window === "undefined") return;
2988
+ const measured = resolveSnap(raw, this.mode);
2989
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2748
2990
  }
2749
2991
  clampToMaxHeight() {
2750
2992
  if (this.maxHeightCap === void 0) return;
@@ -2875,6 +3117,7 @@ var BottomSheetEngine = class {
2875
3117
  this.newCycle();
2876
3118
  this.snapPointsRaw = points;
2877
3119
  auditVhUsage(points);
3120
+ this.auditDuplicateSnapIds(points);
2878
3121
  this.snaps.setRaw(points);
2879
3122
  if (allowed) this.snaps.setAllowedIds(allowed);
2880
3123
  this.scrollCache.clear();
@@ -2887,6 +3130,7 @@ var BottomSheetEngine = class {
2887
3130
  }
2888
3131
  recompute() {
2889
3132
  if (this.destroyed) return;
3133
+ this.resolveMaxHeightCap();
2890
3134
  this.snaps.recompute();
2891
3135
  this.clampToMaxHeight();
2892
3136
  this.scrim.invalidateOpacityCache();
@@ -2898,129 +3142,6 @@ var BottomSheetEngine = class {
2898
3142
  this.updateAriaSlider();
2899
3143
  }
2900
3144
  }
2901
- contentTargets() {
2902
- const scroller = this.scrollContainer;
2903
- if (!scroller) return [];
2904
- const hasSlot = typeof HTMLSlotElement !== "undefined";
2905
- const out = [];
2906
- for (const child2 of Array.from(scroller.children)) {
2907
- if (hasSlot && child2 instanceof HTMLSlotElement) {
2908
- for (const el of child2.assignedElements()) out.push(el);
2909
- } else {
2910
- out.push(child2);
2911
- }
2912
- }
2913
- return out;
2914
- }
2915
- measureSheetNatural(vertical) {
2916
- const el = this.element;
2917
- const content = this.scrollContainer;
2918
- if (!content) {
2919
- return vertical ? el.offsetHeight : el.offsetWidth;
2920
- }
2921
- const axis = vertical ? "height" : "width";
2922
- const sheetStyle = el.style;
2923
- const cs = content.style;
2924
- const prevSheet = sheetStyle[axis];
2925
- const prevFlex = cs.flex;
2926
- const prevContent = vertical ? cs.height : cs.width;
2927
- sheetStyle[axis] = "auto";
2928
- cs.flex = "none";
2929
- if (vertical) cs.height = "auto";
2930
- else cs.width = "auto";
2931
- const poked = vertical ? el.offsetHeight : el.offsetWidth;
2932
- const contentBox = vertical ? content.clientHeight : content.clientWidth;
2933
- const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
2934
- const natural = Math.max(poked, poked - contentBox + contentScroll);
2935
- sheetStyle[axis] = prevSheet;
2936
- cs.flex = prevFlex;
2937
- if (vertical) cs.height = prevContent;
2938
- else cs.width = prevContent;
2939
- return natural;
2940
- }
2941
- measureFitSize() {
2942
- const vertical = layoutAxis(this.mode) === "height";
2943
- const natural = this.measureSheetNatural(vertical);
2944
- const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
2945
- if (typeof window === "undefined") return capped;
2946
- const viewport = this.containingExtent(vertical);
2947
- return viewport > 0 ? Math.min(capped, viewport) : capped;
2948
- }
2949
- // The fit cap is the sheet's containing block, not always the window: a
2950
- // position:fixed sheet nested under a transform/filter/will-change ancestor
2951
- // is clipped to that ancestor, so capping by window.innerHeight oversizes the
2952
- // sheet and the content/footer get clipped. offsetParent resolves to that
2953
- // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
2954
- containingExtent(vertical) {
2955
- const op = this.element.offsetParent;
2956
- if (op && op !== document.body && op !== document.documentElement) {
2957
- return vertical ? op.clientHeight : op.clientWidth;
2958
- }
2959
- return vertical ? window.innerHeight : window.innerWidth;
2960
- }
2961
- installFitObserver() {
2962
- if (typeof ResizeObserver === "undefined") return;
2963
- if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2964
- return;
2965
- let raf = 0;
2966
- const schedule = () => {
2967
- if (raf) return;
2968
- raf = requestAnimationFrame(() => {
2969
- raf = 0;
2970
- if (this.destroyed || this.gesture?.isDragging) return;
2971
- this.recompute();
2972
- });
2973
- };
2974
- const ro = new ResizeObserver(schedule);
2975
- ro.observe(this.handle);
2976
- const scroller = this.scrollContainer;
2977
- const observed = /* @__PURE__ */ new Set();
2978
- const slots = /* @__PURE__ */ new Set();
2979
- const hasSlot = typeof HTMLSlotElement !== "undefined";
2980
- const onSlotChange = () => resync();
2981
- const trackSlots = () => {
2982
- if (!scroller || !hasSlot) return;
2983
- for (const child2 of Array.from(scroller.children)) {
2984
- if (child2 instanceof HTMLSlotElement && !slots.has(child2)) {
2985
- child2.addEventListener("slotchange", onSlotChange);
2986
- slots.add(child2);
2987
- }
2988
- }
2989
- };
2990
- const resync = () => {
2991
- trackSlots();
2992
- const targets = this.contentTargets();
2993
- for (const el of observed) {
2994
- if (!targets.includes(el)) {
2995
- ro.unobserve(el);
2996
- observed.delete(el);
2997
- }
2998
- }
2999
- for (const el of targets) {
3000
- if (!observed.has(el)) {
3001
- ro.observe(el);
3002
- observed.add(el);
3003
- }
3004
- }
3005
- schedule();
3006
- };
3007
- let mo;
3008
- if (scroller) {
3009
- resync();
3010
- if (typeof MutationObserver !== "undefined") {
3011
- mo = new MutationObserver(resync);
3012
- mo.observe(scroller, { childList: true });
3013
- }
3014
- }
3015
- this.teardowns.add(() => {
3016
- ro.disconnect();
3017
- mo?.disconnect();
3018
- for (const slot of slots) {
3019
- slot.removeEventListener("slotchange", onSlotChange);
3020
- }
3021
- if (raf) cancelAnimationFrame(raf);
3022
- });
3023
- }
3024
3145
  destroy() {
3025
3146
  this.destroyed = true;
3026
3147
  this.opening = false;
@@ -3038,9 +3159,10 @@ var BottomSheetEngine = class {
3038
3159
  this.backdrop.style.opacity = "";
3039
3160
  this.backdrop.style.pointerEvents = "";
3040
3161
  }
3041
- if (this.anchorHost) {
3042
- this.anchorHost.style.removeProperty("--bs-size");
3043
- this.anchorHost.style.removeProperty("--bs-progress");
3162
+ for (const host of [this.anchorHost, this.scrimParent, this.rootEl]) {
3163
+ if (!host) continue;
3164
+ host.style.removeProperty("--bs-size");
3165
+ host.style.removeProperty("--bs-progress");
3044
3166
  }
3045
3167
  if (this.screenComponent) {
3046
3168
  this.screenComponent.style.opacity = "";
@@ -3054,6 +3176,7 @@ var BottomSheetEngine = class {
3054
3176
  }
3055
3177
  newCycle() {
3056
3178
  this.allowOvershoot = false;
3179
+ this.opening = false;
3057
3180
  this.currentAbort.abort();
3058
3181
  this.currentAbort = new AbortController();
3059
3182
  return this.currentAbort.signal;
@@ -3109,8 +3232,14 @@ var BottomSheetEngine = class {
3109
3232
  this.element.setAttribute("aria-labelledby", titled.id);
3110
3233
  }
3111
3234
  recomputeSnaps() {
3235
+ this.resolveMaxHeightCap();
3236
+ this.snaps.setRaw(this.snapPointsRaw);
3237
+ this.clampToMaxHeight();
3238
+ this.scrim.invalidateOpacityCache();
3239
+ }
3240
+ auditDuplicateSnapIds(points) {
3112
3241
  const seen = /* @__PURE__ */ new Set();
3113
- for (const p of this.snapPointsRaw) {
3242
+ for (const p of points) {
3114
3243
  if (seen.has(p.id)) {
3115
3244
  console.warn(
3116
3245
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
@@ -3118,9 +3247,6 @@ var BottomSheetEngine = class {
3118
3247
  }
3119
3248
  seen.add(p.id);
3120
3249
  }
3121
- this.snaps.setRaw(this.snapPointsRaw);
3122
- this.clampToMaxHeight();
3123
- this.scrim.invalidateOpacityCache();
3124
3250
  }
3125
3251
  getAllowedRange() {
3126
3252
  return this.snaps.getAllowedRange();
@@ -3308,9 +3434,15 @@ var BottomSheetEngine = class {
3308
3434
  flickVelocity: this.flickVelocity,
3309
3435
  dragThreshold: this.dragThreshold
3310
3436
  });
3311
- if (!target) return;
3437
+ if (!target) {
3438
+ this.clearWillChangeIfIdle();
3439
+ return;
3440
+ }
3312
3441
  const previousId = this.activeId;
3313
- if (target.id === previousId && target.size === this.size) return;
3442
+ if (target.id === previousId && target.size === this.size) {
3443
+ this.clearWillChangeIfIdle();
3444
+ return;
3445
+ }
3314
3446
  if (this.emitBeforeSnap(target, previousId)) {
3315
3447
  const restore = this.snaps.findById(previousId);
3316
3448
  if (restore) {
@@ -3333,27 +3465,19 @@ var BottomSheetEngine = class {
3333
3465
  if (signal.aborted) return;
3334
3466
  this.allowOvershoot = false;
3335
3467
  this.opening = false;
3336
- this.scrollCache.restore(target.id, previousSize, target.size);
3337
- this.updateAriaSlider();
3338
- this.emit("snap", {
3339
- id: target.id,
3340
- size: this.size,
3341
- progress: this.computeProgress(this.size)
3342
- });
3343
- this.haptic();
3344
- if (previousSnapSize === 0 && target.size > 0) {
3345
- this.emit("open", { id: target.id });
3346
- this.handleOpen();
3347
- notifyLinkedSheets(this.linkedSheets, this);
3348
- this.emit("opened", { id: target.id });
3349
- }
3350
- if (target.size === 0) {
3351
- this.emit("close", void 0);
3352
- this.handleClose();
3353
- this.emit("closed", void 0);
3354
- }
3468
+ this.completeSnap(
3469
+ target.id,
3470
+ target.size,
3471
+ previousSize,
3472
+ previousSnapSize === 0,
3473
+ true
3474
+ );
3355
3475
  });
3356
3476
  }
3477
+ clearWillChangeIfIdle() {
3478
+ if (this.animation.isAnimating || this.gesture?.isDragging) return;
3479
+ this.element.style.willChange = "";
3480
+ }
3357
3481
  haptic() {
3358
3482
  if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3359
3483
  try {
@@ -3434,14 +3558,9 @@ var BottomSheetEngine = class {
3434
3558
  progress: this.computeProgress(this.size)
3435
3559
  });
3436
3560
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3437
- this.emit("open", { id: target.id });
3438
- this.handleOpen();
3439
- notifyLinkedSheets(this.linkedSheets, this);
3440
- this.emit("opened", { id: target.id });
3561
+ this.emitOpenSequence(target.id);
3441
3562
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3442
- this.emit("close", void 0);
3443
- this.handleClose();
3444
- this.emit("closed", void 0);
3563
+ this.emitCloseSequence();
3445
3564
  }
3446
3565
  }
3447
3566
  handleClose() {
@@ -3573,7 +3692,7 @@ var root_7 = $2.from_html(`<div class="bs-button-slot" data-side="right"><!></di
3573
3692
  var root = $2.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>`);
3574
3693
  function BottomSheet($$anchor, $$props) {
3575
3694
  $2.push($$props, true);
3576
- let mode = $2.prop($$props, "mode", 3, "bottom"), animation = $2.prop($$props, "animation", 3, "spring"), focusTrap = $2.prop($$props, "focusTrap", 3, false), closeOnEscape = $2.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2.prop($$props, "rubberBand", 3, true), backdrop = $2.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2.prop($$props, "closeOnBackdrop", 3, true), persistent = $2.prop($$props, "persistent", 3, false), disableClose = $2.prop($$props, "disableClose", 3, false), disableDrag = $2.prop($$props, "disableDrag", 3, false), closeOnRouteChange = $2.prop($$props, "closeOnRouteChange", 3, false), openProp = $2.prop($$props, "open", 15), snap = $2.prop($$props, "snap", 15), ariaLabel = $2.prop($$props, "ariaLabel", 3, "Bottom sheet");
3695
+ let mode = $2.prop($$props, "mode", 3, "bottom"), animation = $2.prop($$props, "animation", 3, "spring"), focusTrap = $2.prop($$props, "focusTrap", 3, false), closeOnEscape = $2.prop($$props, "closeOnEscape", 3, true), closeOnBack = $2.prop($$props, "closeOnBack", 3, false), lockBodyScroll2 = $2.prop($$props, "lockBodyScroll", 3, true), rubberBand2 = $2.prop($$props, "rubberBand", 3, true), backdrop = $2.prop($$props, "backdrop", 3, true), closeOnBackdrop = $2.prop($$props, "closeOnBackdrop", 3, true), persistent = $2.prop($$props, "persistent", 3, false), disableClose = $2.prop($$props, "disableClose", 3, false), disableDrag = $2.prop($$props, "disableDrag", 3, false), closeOnRouteChange = $2.prop($$props, "closeOnRouteChange", 3, false), stackEffect = $2.prop($$props, "stackEffect", 3, false), teleport = $2.prop($$props, "teleport", 3, false), openProp = $2.prop($$props, "open", 15), snap = $2.prop($$props, "snap", 15), ariaLabel = $2.prop($$props, "ariaLabel", 3, "Bottom sheet");
3577
3696
  let sheetEl = $2.state(void 0);
3578
3697
  let handleEl = $2.state(void 0);
3579
3698
  let contentEl = $2.state(void 0);
@@ -3587,6 +3706,7 @@ function BottomSheet($$anchor, $$props) {
3587
3706
  progress: 0
3588
3707
  }));
3589
3708
  let engine = null;
3709
+ let restoreTeleport = null;
3590
3710
  onMount(() => {
3591
3711
  if (!$2.get(sheetEl)) return;
3592
3712
  engine = new BottomSheetEngine({
@@ -3611,10 +3731,15 @@ function BottomSheet($$anchor, $$props) {
3611
3731
  disableClose: disableClose(),
3612
3732
  disableDrag: disableDrag(),
3613
3733
  closeOnRouteChange: closeOnRouteChange(),
3734
+ stackEffect: stackEffect(),
3614
3735
  radius: $$props.radius,
3615
3736
  maxHeight: $$props.maxHeight,
3616
3737
  returnFocusTo: $$props.returnFocusTo
3617
3738
  });
3739
+ const teleportTarget = teleport() === false ? null : resolveTeleportTarget($$props.teleportTo);
3740
+ if (teleportTarget) {
3741
+ restoreTeleport = teleportElements([$2.get(backdropEl), $2.get(screenEl), $2.get(sheetEl)], teleportTarget);
3742
+ }
3618
3743
  const sync = () => {
3619
3744
  if (!engine) return;
3620
3745
  $2.set(viewState, { ...engine.state }, true);
@@ -3637,6 +3762,7 @@ function BottomSheet($$anchor, $$props) {
3637
3762
  $$props.onclose?.();
3638
3763
  });
3639
3764
  engine.on("before-close", (payload) => $$props.onbeforeclose?.(payload));
3765
+ engine.on("before-snap", (payload) => $$props.onbeforesnap?.(payload));
3640
3766
  engine.on("opened", (payload) => $$props.onopened?.(payload.id));
3641
3767
  engine.on("closed", () => $$props.onclosed?.());
3642
3768
  engine.on("dragstart", () => {
@@ -3649,6 +3775,8 @@ function BottomSheet($$anchor, $$props) {
3649
3775
  });
3650
3776
  });
3651
3777
  onDestroy(() => {
3778
+ restoreTeleport?.();
3779
+ restoreTeleport = null;
3652
3780
  engine?.destroy();
3653
3781
  engine = null;
3654
3782
  });
@@ -3669,6 +3797,33 @@ function BottomSheet($$anchor, $$props) {
3669
3797
  const canDismiss = () => engine?.canDismiss() ?? false;
3670
3798
  const getState = () => engine?.state ?? $2.get(viewState);
3671
3799
  const getEngine = () => engine;
3800
+ let snapDefApplied = false;
3801
+ $2.user_effect(() => {
3802
+ const points = $$props.snapPoints;
3803
+ if (!snapDefApplied) {
3804
+ snapDefApplied = true;
3805
+ return;
3806
+ }
3807
+ engine?.setSnapPoints(points, untrack(() => $$props.allowed));
3808
+ });
3809
+ let allowedApplied = false;
3810
+ $2.user_effect(() => {
3811
+ const ids = $$props.allowed;
3812
+ if (!allowedApplied) {
3813
+ allowedApplied = true;
3814
+ return;
3815
+ }
3816
+ if (ids) engine?.setAllowed(ids);
3817
+ });
3818
+ $2.user_effect(() => {
3819
+ engine?.setPersistent(persistent());
3820
+ });
3821
+ $2.user_effect(() => {
3822
+ engine?.setDisableClose(disableClose());
3823
+ });
3824
+ $2.user_effect(() => {
3825
+ engine?.setDisableDrag(disableDrag());
3826
+ });
3672
3827
  $2.user_effect(() => {
3673
3828
  if ($$props.radius !== void 0) engine?.setRadius($$props.radius);
3674
3829
  });