@surdeddd/bottom-sheet 0.8.1 → 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 +365 -217
  43. package/dist/vue.d.cts +6 -0
  44. package/dist/vue.d.ts +6 -0
  45. package/dist/vue.js +365 -217
  46. package/package.json +6 -6
package/dist/element.js CHANGED
@@ -304,18 +304,25 @@ var init_snap_resolver = __esm({
304
304
  __publicField(this, "resolved", []);
305
305
  __publicField(this, "maxAxisSize", 0);
306
306
  __publicField(this, "rangeCache", null);
307
+ __publicField(this, "notified", false);
307
308
  this.raw = raw;
308
309
  this.allowedIds = allowed ?? raw.map((p) => p.id);
309
310
  this.recompute();
310
311
  }
311
312
  recompute() {
312
- this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
313
- this.maxAxisSize = this.resolved.reduce(
313
+ const measure = this.measureFit;
314
+ let measured;
315
+ const measureOnce = measure ? () => measured ?? (measured = measure()) : void 0;
316
+ this.resolved = resolveSnapList(this.raw, this.mode, measureOnce);
317
+ const nextMax = this.resolved.reduce(
314
318
  (m, s) => s.size > m ? s.size : m,
315
319
  0
316
320
  );
317
321
  this.rangeCache = null;
318
- this.onMaxAxisSizeChange?.(this.maxAxisSize);
322
+ const changed = nextMax !== this.maxAxisSize || !this.notified;
323
+ this.maxAxisSize = nextMax;
324
+ this.notified = true;
325
+ if (changed) this.onMaxAxisSizeChange?.(this.maxAxisSize);
319
326
  }
320
327
  setRaw(raw) {
321
328
  this.raw = raw;
@@ -482,16 +489,7 @@ function installRoute(deps) {
482
489
  pushed = false;
483
490
  }
484
491
  };
485
- const onOpen = () => {
486
- if (closer) return;
487
- closer = {
488
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
489
- isTop: () => deps.isTopSheet(),
490
- close: () => void deps.close(),
491
- consumed: false
492
- };
493
- ensureHandler();
494
- closers.push(closer);
492
+ const pushEntry = () => {
495
493
  try {
496
494
  if (deps.routedTo !== void 0) {
497
495
  history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
@@ -503,6 +501,25 @@ function installRoute(deps) {
503
501
  pushed = false;
504
502
  }
505
503
  };
504
+ const onOpen = () => {
505
+ if (closer) return;
506
+ const self = {
507
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
508
+ isTop: () => deps.isTopSheet(),
509
+ close: () => deps.close(),
510
+ restore: () => {
511
+ if (deps.isDestroyed() || closer !== self) return;
512
+ self.consumed = false;
513
+ if (!closers.includes(self)) closers.push(self);
514
+ pushEntry();
515
+ },
516
+ consumed: false
517
+ };
518
+ closer = self;
519
+ ensureHandler();
520
+ closers.push(self);
521
+ pushEntry();
522
+ };
506
523
  const unsubscribeOpen = deps.on("open", onOpen);
507
524
  const unsubscribeClose = deps.on("close", teardownEntry);
508
525
  if (deps.getSize() > 0) onOpen();
@@ -571,7 +588,10 @@ var init_route = __esm({
571
588
  target.consumed = true;
572
589
  const idx = closers.indexOf(target);
573
590
  if (idx !== -1) closers.splice(idx, 1);
574
- target.close();
591
+ const settled = target;
592
+ void Promise.resolve(settled.close()).then(() => {
593
+ if (settled.isOpen()) settled.restore();
594
+ });
575
595
  });
576
596
  };
577
597
  programmaticBack = () => {
@@ -589,11 +609,13 @@ var init_route = __esm({
589
609
  function installContentSwipe(deps) {
590
610
  const { container } = deps;
591
611
  let startY = 0;
612
+ let startScrollTop = 0;
592
613
  let moved = false;
593
614
  const onTouchStart = (e) => {
594
615
  const t = e.touches[0];
595
616
  if (!t) return;
596
617
  startY = t.clientY;
618
+ startScrollTop = container.scrollTop;
597
619
  moved = false;
598
620
  };
599
621
  const onTouchMove = (e) => {
@@ -604,6 +626,7 @@ function installContentSwipe(deps) {
604
626
  const onTouchEnd = (e) => {
605
627
  if (!moved) return;
606
628
  if (deps.isDragging() || deps.isAnimating()) return;
629
+ if (container.scrollTop !== startScrollTop) return;
607
630
  const t = e.changedTouches[0];
608
631
  if (!t) return;
609
632
  const deltaY = t.clientY - startY;
@@ -730,6 +753,142 @@ var init_resize_observer = __esm({
730
753
  }
731
754
  });
732
755
 
756
+ // src/core/features/fit-observer.ts
757
+ function installFitObserver(deps) {
758
+ if (typeof ResizeObserver === "undefined") return () => {
759
+ };
760
+ if (!deps.hasFitSnap()) return () => {
761
+ };
762
+ let raf = 0;
763
+ const schedule = () => {
764
+ if (raf) return;
765
+ raf = requestAnimationFrame(() => {
766
+ raf = 0;
767
+ if (deps.isDestroyed() || deps.isDragging()) return;
768
+ deps.recompute();
769
+ });
770
+ };
771
+ const ro = new ResizeObserver(schedule);
772
+ ro.observe(deps.handle);
773
+ const scroller = deps.scrollContainer;
774
+ const observed = /* @__PURE__ */ new Set();
775
+ const slots = /* @__PURE__ */ new Set();
776
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
777
+ const onSlotChange = () => resync();
778
+ const trackSlots = () => {
779
+ if (!scroller || !hasSlot) return;
780
+ for (const child of Array.from(scroller.children)) {
781
+ if (child instanceof HTMLSlotElement && !slots.has(child)) {
782
+ child.addEventListener("slotchange", onSlotChange);
783
+ slots.add(child);
784
+ }
785
+ }
786
+ };
787
+ const resync = () => {
788
+ trackSlots();
789
+ const targets = contentTargets(scroller);
790
+ for (const el of observed) {
791
+ if (!targets.includes(el)) {
792
+ ro.unobserve(el);
793
+ observed.delete(el);
794
+ }
795
+ }
796
+ for (const el of targets) {
797
+ if (!observed.has(el)) {
798
+ ro.observe(el);
799
+ observed.add(el);
800
+ }
801
+ }
802
+ schedule();
803
+ };
804
+ let mo;
805
+ if (scroller) {
806
+ resync();
807
+ if (typeof MutationObserver !== "undefined") {
808
+ mo = new MutationObserver(resync);
809
+ mo.observe(scroller, { childList: true });
810
+ }
811
+ }
812
+ return () => {
813
+ ro.disconnect();
814
+ mo?.disconnect();
815
+ for (const slot of slots) {
816
+ slot.removeEventListener("slotchange", onSlotChange);
817
+ }
818
+ if (raf) cancelAnimationFrame(raf);
819
+ };
820
+ }
821
+ var contentTargets;
822
+ var init_fit_observer = __esm({
823
+ "src/core/features/fit-observer.ts"() {
824
+ contentTargets = (scroller) => {
825
+ if (!scroller) return [];
826
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
827
+ const out = [];
828
+ for (const child of Array.from(scroller.children)) {
829
+ if (hasSlot && child instanceof HTMLSlotElement) {
830
+ for (const el of child.assignedElements()) out.push(el);
831
+ } else {
832
+ out.push(child);
833
+ }
834
+ }
835
+ return out;
836
+ };
837
+ }
838
+ });
839
+
840
+ // src/core/features/fit-measurement.ts
841
+ var measureSheetNatural, containingExtent, measureFitSize;
842
+ var init_fit_measurement = __esm({
843
+ "src/core/features/fit-measurement.ts"() {
844
+ init_transform();
845
+ measureSheetNatural = (element, content, vertical) => {
846
+ if (!content) {
847
+ return vertical ? element.offsetHeight : element.offsetWidth;
848
+ }
849
+ const axis = vertical ? "height" : "width";
850
+ const sheetStyle = element.style;
851
+ const cs = content.style;
852
+ const prevSheet = sheetStyle[axis];
853
+ const prevFlex = cs.flex;
854
+ const prevContent = vertical ? cs.height : cs.width;
855
+ sheetStyle[axis] = "auto";
856
+ cs.flex = "none";
857
+ if (vertical) cs.height = "auto";
858
+ else cs.width = "auto";
859
+ const poked = vertical ? element.offsetHeight : element.offsetWidth;
860
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
861
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
862
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
863
+ sheetStyle[axis] = prevSheet;
864
+ cs.flex = prevFlex;
865
+ if (vertical) cs.height = prevContent;
866
+ else cs.width = prevContent;
867
+ return natural;
868
+ };
869
+ containingExtent = (element, vertical) => {
870
+ const op = element.offsetParent;
871
+ if (op && op !== document.body && op !== document.documentElement) {
872
+ return vertical ? op.clientHeight : op.clientWidth;
873
+ }
874
+ return vertical ? window.innerHeight : window.innerWidth;
875
+ };
876
+ measureFitSize = (deps) => {
877
+ const vertical = layoutAxis(deps.mode) === "height";
878
+ const natural = measureSheetNatural(
879
+ deps.element,
880
+ deps.scrollContainer,
881
+ vertical
882
+ );
883
+ const cap = deps.getMaxHeightCap();
884
+ const capped = cap !== void 0 ? Math.min(natural, cap) : natural;
885
+ if (typeof window === "undefined") return capped;
886
+ const viewport = containingExtent(deps.element, vertical);
887
+ return viewport > 0 ? Math.min(capped, viewport) : capped;
888
+ };
889
+ }
890
+ });
891
+
733
892
  // src/core/features/slider-keyboard.ts
734
893
  function installSliderKeyboard(deps) {
735
894
  const isVerticalAxis = deps.mode === "bottom" || deps.mode === "top";
@@ -1252,6 +1411,9 @@ var init_scrim_controller = __esm({
1252
1411
  const s = this.screenComponent.style;
1253
1412
  s.opacity = "";
1254
1413
  s.display = "";
1414
+ s.position = "";
1415
+ s.inset = "";
1416
+ s.pointerEvents = "";
1255
1417
  }
1256
1418
  if (this.backdrop) {
1257
1419
  this.backdrop.style.opacity = "";
@@ -1524,7 +1686,12 @@ var init_animation_runner = __esm({
1524
1686
  this.currentSpring?.cancel();
1525
1687
  }
1526
1688
  async animateTo(target, velocityPxPerMs) {
1527
- if (target === this.getSize()) return;
1689
+ if (target === this.getSize()) {
1690
+ if (!this.isAnimating && !this.isDragging()) {
1691
+ this.element.style.willChange = "";
1692
+ }
1693
+ return;
1694
+ }
1528
1695
  this.currentTween?.cancel();
1529
1696
  this.currentSpring?.cancel();
1530
1697
  this.getRootEl()?.setAttribute("data-animating", "true");
@@ -1532,7 +1699,7 @@ var init_animation_runner = __esm({
1532
1699
  if (this.reducedMotion) {
1533
1700
  this.applySizeFn(target);
1534
1701
  this.getRootEl()?.removeAttribute("data-animating");
1535
- if (!this.isDragging()) this.element.style.willChange = "auto";
1702
+ if (!this.isDragging()) this.element.style.willChange = "";
1536
1703
  return;
1537
1704
  }
1538
1705
  if (this.animationKind === "spring") {
@@ -1560,7 +1727,7 @@ var init_animation_runner = __esm({
1560
1727
  }
1561
1728
  if (!this.isAnimating) {
1562
1729
  this.getRootEl()?.removeAttribute("data-animating");
1563
- if (!this.isDragging()) this.element.style.willChange = "auto";
1730
+ if (!this.isDragging()) this.element.style.willChange = "";
1564
1731
  }
1565
1732
  }
1566
1733
  destroy() {
@@ -1574,7 +1741,7 @@ var init_animation_runner = __esm({
1574
1741
  });
1575
1742
 
1576
1743
  // src/core/lifecycle/focusTrap.ts
1577
- var FOCUSABLE, isVisible, focusables, trapStack, installFocusTrap;
1744
+ var FOCUSABLE, isVisible, collectFocusables, focusables, containsComposed, trapStack, installFocusTrap;
1578
1745
  var init_focusTrap = __esm({
1579
1746
  "src/core/lifecycle/focusTrap.ts"() {
1580
1747
  FOCUSABLE = [
@@ -1596,9 +1763,35 @@ var init_focusTrap = __esm({
1596
1763
  }
1597
1764
  return el.offsetParent !== null;
1598
1765
  };
1599
- focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1766
+ collectFocusables = (root) => {
1767
+ const out = Array.from(root.querySelectorAll(FOCUSABLE));
1768
+ if (typeof HTMLSlotElement !== "undefined") {
1769
+ for (const slot of Array.from(root.querySelectorAll("slot"))) {
1770
+ for (const assigned of slot.assignedElements()) {
1771
+ if (assigned.matches(FOCUSABLE)) out.push(assigned);
1772
+ out.push(...collectFocusables(assigned));
1773
+ }
1774
+ }
1775
+ }
1776
+ return out;
1777
+ };
1778
+ focusables = (root) => collectFocusables(root).filter(
1600
1779
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1601
1780
  );
1781
+ containsComposed = (container, node) => {
1782
+ let n = node;
1783
+ while (n) {
1784
+ if (n === container) return true;
1785
+ const slot = n.assignedSlot;
1786
+ if (slot) {
1787
+ n = slot;
1788
+ continue;
1789
+ }
1790
+ const parent = n.parentNode;
1791
+ n = parent instanceof ShadowRoot ? parent.host : parent;
1792
+ }
1793
+ return false;
1794
+ };
1602
1795
  trapStack = [];
1603
1796
  installFocusTrap = (container, options = {}) => {
1604
1797
  if (typeof document === "undefined") return () => {
@@ -1608,7 +1801,13 @@ var init_focusTrap = __esm({
1608
1801
  const isActive = () => trapStack[trapStack.length - 1] === token;
1609
1802
  const previouslyFocused = document.activeElement;
1610
1803
  const focusInitial = () => {
1611
- const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
1804
+ const spec = options.initialFocus;
1805
+ if (spec === false) {
1806
+ if (!container.hasAttribute("tabindex")) container.tabIndex = -1;
1807
+ container.focus({ preventScroll: true });
1808
+ return;
1809
+ }
1810
+ const initial = typeof spec === "string" ? container.querySelector(spec) : spec ?? focusables(container)[0] ?? container;
1612
1811
  initial?.focus({ preventScroll: true });
1613
1812
  };
1614
1813
  focusInitial();
@@ -1638,7 +1837,7 @@ var init_focusTrap = __esm({
1638
1837
  const handleFocusIn = (e) => {
1639
1838
  if (!isActive()) return;
1640
1839
  const target = e.target;
1641
- if (target && !container.contains(target)) {
1840
+ if (target && !containsComposed(container, target)) {
1642
1841
  const list = focusables(container);
1643
1842
  list[0]?.focus({ preventScroll: true });
1644
1843
  }
@@ -1680,8 +1879,14 @@ var init_scrollLock = __esm({
1680
1879
  position: body.style.position,
1681
1880
  top: body.style.top,
1682
1881
  width: body.style.width,
1882
+ paddingRight: body.style.paddingRight,
1683
1883
  scrollY: window.scrollY
1684
1884
  };
1885
+ const scrollbarGap = window.innerWidth - document.documentElement.clientWidth;
1886
+ if (scrollbarGap > 0) {
1887
+ const current = parseFloat(getComputedStyle(body).paddingRight) || 0;
1888
+ body.style.paddingRight = `${current + scrollbarGap}px`;
1889
+ }
1685
1890
  body.style.overflow = "hidden";
1686
1891
  body.style.position = "fixed";
1687
1892
  body.style.top = `-${savedStyles.scrollY}px`;
@@ -1699,6 +1904,7 @@ var init_scrollLock = __esm({
1699
1904
  body.style.position = savedStyles.position;
1700
1905
  body.style.top = savedStyles.top;
1701
1906
  body.style.width = savedStyles.width;
1907
+ body.style.paddingRight = savedStyles.paddingRight;
1702
1908
  window.scrollTo(0, savedStyles.scrollY);
1703
1909
  savedStyles = null;
1704
1910
  }
@@ -1928,12 +2134,14 @@ var init_gestures = __esm({
1928
2134
  handle.addEventListener("pointermove", onPointerMove);
1929
2135
  handle.addEventListener("pointerup", finishGesture);
1930
2136
  handle.addEventListener("pointercancel", onPointerCancel);
2137
+ const prevTouchAction = handle.style.touchAction;
1931
2138
  handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1932
2139
  return () => {
1933
2140
  handle.removeEventListener("pointerdown", onPointerDown);
1934
2141
  handle.removeEventListener("pointermove", onPointerMove);
1935
2142
  handle.removeEventListener("pointerup", finishGesture);
1936
2143
  handle.removeEventListener("pointercancel", onPointerCancel);
2144
+ handle.style.touchAction = prevTouchAction;
1937
2145
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1938
2146
  handle.releasePointerCapture(activePointerId);
1939
2147
  }
@@ -2076,7 +2284,7 @@ var init_gesture_controller = __esm({
2076
2284
  this.isDragging_ = false;
2077
2285
  this.keyboardDismissed = false;
2078
2286
  this.deps.getRoot()?.removeAttribute("data-dragging");
2079
- this.deps.element.style.willChange = "auto";
2287
+ this.deps.element.style.willChange = "";
2080
2288
  }
2081
2289
  };
2082
2290
  }
@@ -2511,8 +2719,34 @@ var init_engine_options = __esm({
2511
2719
  }
2512
2720
  });
2513
2721
 
2722
+ // src/core/primitives/teardown-stack.ts
2723
+ var TeardownStack;
2724
+ var init_teardown_stack = __esm({
2725
+ "src/core/primitives/teardown-stack.ts"() {
2726
+ TeardownStack = class {
2727
+ constructor() {
2728
+ __publicField(this, "fns", []);
2729
+ }
2730
+ add(fn) {
2731
+ if (fn) this.fns.push(fn);
2732
+ }
2733
+ drain() {
2734
+ while (this.fns.length) {
2735
+ try {
2736
+ this.fns.pop()();
2737
+ } catch (err) {
2738
+ queueMicrotask(() => {
2739
+ throw err;
2740
+ });
2741
+ }
2742
+ }
2743
+ }
2744
+ };
2745
+ }
2746
+ });
2747
+
2514
2748
  // src/core/BottomSheetEngine.ts
2515
- var HAPTIC_DURATION_MS, TeardownStack, BottomSheetEngine;
2749
+ var HAPTIC_DURATION_MS, BottomSheetEngine;
2516
2750
  var init_BottomSheetEngine = __esm({
2517
2751
  "src/core/BottomSheetEngine.ts"() {
2518
2752
  init_sheetStack();
@@ -2528,6 +2762,8 @@ var init_BottomSheetEngine = __esm({
2528
2762
  init_content_swipe();
2529
2763
  init_visual_viewport();
2530
2764
  init_resize_observer();
2765
+ init_fit_observer();
2766
+ init_fit_measurement();
2531
2767
  init_slider_keyboard();
2532
2768
  init_scrim_controller();
2533
2769
  init_animation_runner();
@@ -2539,27 +2775,9 @@ var init_BottomSheetEngine = __esm({
2539
2775
  init_sheet_anchors();
2540
2776
  init_scrim_stages();
2541
2777
  init_engine_options();
2778
+ init_teardown_stack();
2542
2779
  init_opacity_dedup();
2543
2780
  HAPTIC_DURATION_MS = 8;
2544
- TeardownStack = class {
2545
- constructor() {
2546
- __publicField(this, "fns", []);
2547
- }
2548
- add(fn) {
2549
- if (fn) this.fns.push(fn);
2550
- }
2551
- drain() {
2552
- while (this.fns.length) {
2553
- try {
2554
- this.fns.pop()();
2555
- } catch (err) {
2556
- queueMicrotask(() => {
2557
- throw err;
2558
- });
2559
- }
2560
- }
2561
- }
2562
- };
2563
2781
  BottomSheetEngine = class {
2564
2782
  constructor(opts) {
2565
2783
  __publicField(this, "id", nextInstanceId("bs"));
@@ -2584,6 +2802,7 @@ var init_BottomSheetEngine = __esm({
2584
2802
  __publicField(this, "disableDragFlag");
2585
2803
  __publicField(this, "closeOnRouteChange");
2586
2804
  __publicField(this, "maxHeightCap");
2805
+ __publicField(this, "maxHeightRaw");
2587
2806
  __publicField(this, "snapPointsRaw");
2588
2807
  __publicField(this, "snaps");
2589
2808
  __publicField(this, "activeId");
@@ -2619,6 +2838,7 @@ var init_BottomSheetEngine = __esm({
2619
2838
  __publicField(this, "detachScrimStages", null);
2620
2839
  __publicField(this, "stackEffectEnabled", false);
2621
2840
  __publicField(this, "stackEffectPrimed", false);
2841
+ __publicField(this, "fitDeps");
2622
2842
  __publicField(this, "transformTemplate");
2623
2843
  const resolved = resolveEngineOptions(opts, {
2624
2844
  shouldApplyInertSiblings: () => {
@@ -2691,12 +2911,19 @@ var init_BottomSheetEngine = __esm({
2691
2911
  }
2692
2912
  this.activeId = resolved.initialId;
2693
2913
  auditVhUsage(this.snapPointsRaw);
2914
+ this.auditDuplicateSnapIds(this.snapPointsRaw);
2694
2915
  this.rootEl = this.element.closest(".bs-root");
2916
+ this.fitDeps = {
2917
+ element: this.element,
2918
+ scrollContainer: this.scrollContainer,
2919
+ mode: this.mode,
2920
+ getMaxHeightCap: () => this.maxHeightCap
2921
+ };
2695
2922
  this.snaps = new SnapResolver(
2696
2923
  this.snapPointsRaw,
2697
2924
  resolved.initialAllowed,
2698
2925
  this.mode,
2699
- () => this.measureFitSize(),
2926
+ () => measureFitSize(this.fitDeps),
2700
2927
  (maxAxisSize) => {
2701
2928
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2702
2929
  }
@@ -2730,7 +2957,18 @@ var init_BottomSheetEngine = __esm({
2730
2957
  io.observe(this.element);
2731
2958
  this.teardowns.add(() => io.disconnect());
2732
2959
  }
2733
- this.installFitObserver();
2960
+ this.teardowns.add(
2961
+ installFitObserver({
2962
+ handle: this.handle,
2963
+ scrollContainer: this.scrollContainer,
2964
+ hasFitSnap: () => this.snapPointsRaw.some(
2965
+ (p) => p.size === "fit" || p.size === "content"
2966
+ ),
2967
+ isDestroyed: () => this.destroyed,
2968
+ isDragging: () => this.gesture?.isDragging ?? false,
2969
+ recompute: () => this.recompute()
2970
+ })
2971
+ );
2734
2972
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2735
2973
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2736
2974
  this.applyAutoAriaLabelledBy();
@@ -2881,27 +3119,33 @@ var init_BottomSheetEngine = __esm({
2881
3119
  } else {
2882
3120
  await this.animation.animateTo(target.size, velocityPxPerMs);
2883
3121
  }
2884
- this.opening = false;
2885
3122
  externalSignal?.removeEventListener("abort", onExternalAbort);
2886
3123
  if (signal.aborted) return;
2887
- this.scrollCache.restore(id, previousSize, target.size);
3124
+ this.opening = false;
3125
+ this.completeSnap(id, target.size, previousSize, wasClosed, false);
3126
+ }
3127
+ completeSnap(id, targetSize, previousSize, wasClosed, withHaptic) {
3128
+ this.scrollCache.restore(id, previousSize, targetSize);
2888
3129
  this.updateAriaSlider();
2889
3130
  this.emit("snap", {
2890
3131
  id,
2891
3132
  size: this.size,
2892
3133
  progress: this.computeProgress(this.size)
2893
3134
  });
2894
- if (wasClosed && target.size > 0) {
2895
- this.emit("open", { id });
2896
- this.handleOpen();
2897
- notifyLinkedSheets(this.linkedSheets, this);
2898
- this.emit("opened", { id });
2899
- }
2900
- if (target.size === 0) {
2901
- this.emit("close", void 0);
2902
- this.handleClose();
2903
- this.emit("closed", void 0);
2904
- }
3135
+ if (withHaptic) this.haptic();
3136
+ if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
3137
+ if (targetSize === 0) this.emitCloseSequence();
3138
+ }
3139
+ emitOpenSequence(id) {
3140
+ this.emit("open", { id });
3141
+ this.handleOpen();
3142
+ notifyLinkedSheets(this.linkedSheets, this);
3143
+ this.emit("opened", { id });
3144
+ }
3145
+ emitCloseSequence() {
3146
+ this.emit("close", void 0);
3147
+ this.handleClose();
3148
+ this.emit("closed", void 0);
2905
3149
  }
2906
3150
  async dragTo(targetSize, velocityOrOpts = 0) {
2907
3151
  if (this.destroyed) return;
@@ -2922,19 +3166,31 @@ var init_BottomSheetEngine = __esm({
2922
3166
  }
2923
3167
  open(id) {
2924
3168
  if (this.destroyed) return Promise.resolve();
2925
- const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
3169
+ const target = id ?? this.snaps.getAllowedIds().find((a) => (this.snaps.findById(a)?.size ?? 0) > 0) ?? this.activeId;
2926
3170
  return this.snapTo(target);
2927
3171
  }
2928
3172
  close(reason = "programmatic") {
2929
3173
  if (this.destroyed) return Promise.resolve();
2930
3174
  if (this.disableCloseFlag) return Promise.resolve();
2931
3175
  if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2932
- const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
3176
+ 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];
2933
3177
  return this.snapTo(closedId ?? this.activeId);
2934
3178
  }
2935
3179
  canDismiss() {
2936
3180
  return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2937
3181
  }
3182
+ setPersistent(value) {
3183
+ if (this.destroyed) return;
3184
+ this.persistent = value;
3185
+ }
3186
+ setDisableClose(value) {
3187
+ if (this.destroyed) return;
3188
+ this.disableCloseFlag = value;
3189
+ }
3190
+ setDisableDrag(value) {
3191
+ if (this.destroyed) return;
3192
+ this.disableDragFlag = value;
3193
+ }
2938
3194
  isTop() {
2939
3195
  return this.isTopSheet;
2940
3196
  }
@@ -2971,18 +3227,23 @@ var init_BottomSheetEngine = __esm({
2971
3227
  axis === "height" ? "max-height" : "max-width",
2972
3228
  value
2973
3229
  );
2974
- if (typeof h === "number") {
2975
- this.maxHeightCap = h;
2976
- this.recompute();
2977
- } else if (typeof window !== "undefined") {
2978
- const probe2 = document.createElement("div");
2979
- probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2980
- document.body.appendChild(probe2);
2981
- const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2982
- probe2.remove();
2983
- this.maxHeightCap = measured > 0 ? measured : void 0;
2984
- this.recompute();
3230
+ this.maxHeightRaw = h;
3231
+ this.resolveMaxHeightCap();
3232
+ this.recompute();
3233
+ }
3234
+ resolveMaxHeightCap() {
3235
+ const raw = this.maxHeightRaw;
3236
+ if (raw === void 0) {
3237
+ this.maxHeightCap = void 0;
3238
+ return;
3239
+ }
3240
+ if (typeof raw === "number") {
3241
+ this.maxHeightCap = raw;
3242
+ return;
2985
3243
  }
3244
+ if (typeof window === "undefined") return;
3245
+ const measured = resolveSnap(raw, this.mode);
3246
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2986
3247
  }
2987
3248
  clampToMaxHeight() {
2988
3249
  if (this.maxHeightCap === void 0) return;
@@ -3113,6 +3374,7 @@ var init_BottomSheetEngine = __esm({
3113
3374
  this.newCycle();
3114
3375
  this.snapPointsRaw = points;
3115
3376
  auditVhUsage(points);
3377
+ this.auditDuplicateSnapIds(points);
3116
3378
  this.snaps.setRaw(points);
3117
3379
  if (allowed) this.snaps.setAllowedIds(allowed);
3118
3380
  this.scrollCache.clear();
@@ -3125,6 +3387,7 @@ var init_BottomSheetEngine = __esm({
3125
3387
  }
3126
3388
  recompute() {
3127
3389
  if (this.destroyed) return;
3390
+ this.resolveMaxHeightCap();
3128
3391
  this.snaps.recompute();
3129
3392
  this.clampToMaxHeight();
3130
3393
  this.scrim.invalidateOpacityCache();
@@ -3136,129 +3399,6 @@ var init_BottomSheetEngine = __esm({
3136
3399
  this.updateAriaSlider();
3137
3400
  }
3138
3401
  }
3139
- contentTargets() {
3140
- const scroller = this.scrollContainer;
3141
- if (!scroller) return [];
3142
- const hasSlot = typeof HTMLSlotElement !== "undefined";
3143
- const out = [];
3144
- for (const child of Array.from(scroller.children)) {
3145
- if (hasSlot && child instanceof HTMLSlotElement) {
3146
- for (const el of child.assignedElements()) out.push(el);
3147
- } else {
3148
- out.push(child);
3149
- }
3150
- }
3151
- return out;
3152
- }
3153
- measureSheetNatural(vertical) {
3154
- const el = this.element;
3155
- const content = this.scrollContainer;
3156
- if (!content) {
3157
- return vertical ? el.offsetHeight : el.offsetWidth;
3158
- }
3159
- const axis = vertical ? "height" : "width";
3160
- const sheetStyle = el.style;
3161
- const cs = content.style;
3162
- const prevSheet = sheetStyle[axis];
3163
- const prevFlex = cs.flex;
3164
- const prevContent = vertical ? cs.height : cs.width;
3165
- sheetStyle[axis] = "auto";
3166
- cs.flex = "none";
3167
- if (vertical) cs.height = "auto";
3168
- else cs.width = "auto";
3169
- const poked = vertical ? el.offsetHeight : el.offsetWidth;
3170
- const contentBox = vertical ? content.clientHeight : content.clientWidth;
3171
- const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
3172
- const natural = Math.max(poked, poked - contentBox + contentScroll);
3173
- sheetStyle[axis] = prevSheet;
3174
- cs.flex = prevFlex;
3175
- if (vertical) cs.height = prevContent;
3176
- else cs.width = prevContent;
3177
- return natural;
3178
- }
3179
- measureFitSize() {
3180
- const vertical = layoutAxis(this.mode) === "height";
3181
- const natural = this.measureSheetNatural(vertical);
3182
- const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
3183
- if (typeof window === "undefined") return capped;
3184
- const viewport = this.containingExtent(vertical);
3185
- return viewport > 0 ? Math.min(capped, viewport) : capped;
3186
- }
3187
- // The fit cap is the sheet's containing block, not always the window: a
3188
- // position:fixed sheet nested under a transform/filter/will-change ancestor
3189
- // is clipped to that ancestor, so capping by window.innerHeight oversizes the
3190
- // sheet and the content/footer get clipped. offsetParent resolves to that
3191
- // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
3192
- containingExtent(vertical) {
3193
- const op = this.element.offsetParent;
3194
- if (op && op !== document.body && op !== document.documentElement) {
3195
- return vertical ? op.clientHeight : op.clientWidth;
3196
- }
3197
- return vertical ? window.innerHeight : window.innerWidth;
3198
- }
3199
- installFitObserver() {
3200
- if (typeof ResizeObserver === "undefined") return;
3201
- if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
3202
- return;
3203
- let raf = 0;
3204
- const schedule = () => {
3205
- if (raf) return;
3206
- raf = requestAnimationFrame(() => {
3207
- raf = 0;
3208
- if (this.destroyed || this.gesture?.isDragging) return;
3209
- this.recompute();
3210
- });
3211
- };
3212
- const ro = new ResizeObserver(schedule);
3213
- ro.observe(this.handle);
3214
- const scroller = this.scrollContainer;
3215
- const observed = /* @__PURE__ */ new Set();
3216
- const slots = /* @__PURE__ */ new Set();
3217
- const hasSlot = typeof HTMLSlotElement !== "undefined";
3218
- const onSlotChange = () => resync();
3219
- const trackSlots = () => {
3220
- if (!scroller || !hasSlot) return;
3221
- for (const child of Array.from(scroller.children)) {
3222
- if (child instanceof HTMLSlotElement && !slots.has(child)) {
3223
- child.addEventListener("slotchange", onSlotChange);
3224
- slots.add(child);
3225
- }
3226
- }
3227
- };
3228
- const resync = () => {
3229
- trackSlots();
3230
- const targets = this.contentTargets();
3231
- for (const el of observed) {
3232
- if (!targets.includes(el)) {
3233
- ro.unobserve(el);
3234
- observed.delete(el);
3235
- }
3236
- }
3237
- for (const el of targets) {
3238
- if (!observed.has(el)) {
3239
- ro.observe(el);
3240
- observed.add(el);
3241
- }
3242
- }
3243
- schedule();
3244
- };
3245
- let mo;
3246
- if (scroller) {
3247
- resync();
3248
- if (typeof MutationObserver !== "undefined") {
3249
- mo = new MutationObserver(resync);
3250
- mo.observe(scroller, { childList: true });
3251
- }
3252
- }
3253
- this.teardowns.add(() => {
3254
- ro.disconnect();
3255
- mo?.disconnect();
3256
- for (const slot of slots) {
3257
- slot.removeEventListener("slotchange", onSlotChange);
3258
- }
3259
- if (raf) cancelAnimationFrame(raf);
3260
- });
3261
- }
3262
3402
  destroy() {
3263
3403
  this.destroyed = true;
3264
3404
  this.opening = false;
@@ -3276,9 +3416,10 @@ var init_BottomSheetEngine = __esm({
3276
3416
  this.backdrop.style.opacity = "";
3277
3417
  this.backdrop.style.pointerEvents = "";
3278
3418
  }
3279
- if (this.anchorHost) {
3280
- this.anchorHost.style.removeProperty("--bs-size");
3281
- this.anchorHost.style.removeProperty("--bs-progress");
3419
+ for (const host of [this.anchorHost, this.scrimParent, this.rootEl]) {
3420
+ if (!host) continue;
3421
+ host.style.removeProperty("--bs-size");
3422
+ host.style.removeProperty("--bs-progress");
3282
3423
  }
3283
3424
  if (this.screenComponent) {
3284
3425
  this.screenComponent.style.opacity = "";
@@ -3292,6 +3433,7 @@ var init_BottomSheetEngine = __esm({
3292
3433
  }
3293
3434
  newCycle() {
3294
3435
  this.allowOvershoot = false;
3436
+ this.opening = false;
3295
3437
  this.currentAbort.abort();
3296
3438
  this.currentAbort = new AbortController();
3297
3439
  return this.currentAbort.signal;
@@ -3347,8 +3489,14 @@ var init_BottomSheetEngine = __esm({
3347
3489
  this.element.setAttribute("aria-labelledby", titled.id);
3348
3490
  }
3349
3491
  recomputeSnaps() {
3492
+ this.resolveMaxHeightCap();
3493
+ this.snaps.setRaw(this.snapPointsRaw);
3494
+ this.clampToMaxHeight();
3495
+ this.scrim.invalidateOpacityCache();
3496
+ }
3497
+ auditDuplicateSnapIds(points) {
3350
3498
  const seen = /* @__PURE__ */ new Set();
3351
- for (const p of this.snapPointsRaw) {
3499
+ for (const p of points) {
3352
3500
  if (seen.has(p.id)) {
3353
3501
  console.warn(
3354
3502
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
@@ -3356,9 +3504,6 @@ var init_BottomSheetEngine = __esm({
3356
3504
  }
3357
3505
  seen.add(p.id);
3358
3506
  }
3359
- this.snaps.setRaw(this.snapPointsRaw);
3360
- this.clampToMaxHeight();
3361
- this.scrim.invalidateOpacityCache();
3362
3507
  }
3363
3508
  getAllowedRange() {
3364
3509
  return this.snaps.getAllowedRange();
@@ -3546,9 +3691,15 @@ var init_BottomSheetEngine = __esm({
3546
3691
  flickVelocity: this.flickVelocity,
3547
3692
  dragThreshold: this.dragThreshold
3548
3693
  });
3549
- if (!target) return;
3694
+ if (!target) {
3695
+ this.clearWillChangeIfIdle();
3696
+ return;
3697
+ }
3550
3698
  const previousId = this.activeId;
3551
- if (target.id === previousId && target.size === this.size) return;
3699
+ if (target.id === previousId && target.size === this.size) {
3700
+ this.clearWillChangeIfIdle();
3701
+ return;
3702
+ }
3552
3703
  if (this.emitBeforeSnap(target, previousId)) {
3553
3704
  const restore = this.snaps.findById(previousId);
3554
3705
  if (restore) {
@@ -3571,27 +3722,19 @@ var init_BottomSheetEngine = __esm({
3571
3722
  if (signal.aborted) return;
3572
3723
  this.allowOvershoot = false;
3573
3724
  this.opening = false;
3574
- this.scrollCache.restore(target.id, previousSize, target.size);
3575
- this.updateAriaSlider();
3576
- this.emit("snap", {
3577
- id: target.id,
3578
- size: this.size,
3579
- progress: this.computeProgress(this.size)
3580
- });
3581
- this.haptic();
3582
- if (previousSnapSize === 0 && target.size > 0) {
3583
- this.emit("open", { id: target.id });
3584
- this.handleOpen();
3585
- notifyLinkedSheets(this.linkedSheets, this);
3586
- this.emit("opened", { id: target.id });
3587
- }
3588
- if (target.size === 0) {
3589
- this.emit("close", void 0);
3590
- this.handleClose();
3591
- this.emit("closed", void 0);
3592
- }
3725
+ this.completeSnap(
3726
+ target.id,
3727
+ target.size,
3728
+ previousSize,
3729
+ previousSnapSize === 0,
3730
+ true
3731
+ );
3593
3732
  });
3594
3733
  }
3734
+ clearWillChangeIfIdle() {
3735
+ if (this.animation.isAnimating || this.gesture?.isDragging) return;
3736
+ this.element.style.willChange = "";
3737
+ }
3595
3738
  haptic() {
3596
3739
  if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3597
3740
  try {
@@ -3672,14 +3815,9 @@ var init_BottomSheetEngine = __esm({
3672
3815
  progress: this.computeProgress(this.size)
3673
3816
  });
3674
3817
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3675
- this.emit("open", { id: target.id });
3676
- this.handleOpen();
3677
- notifyLinkedSheets(this.linkedSheets, this);
3678
- this.emit("opened", { id: target.id });
3818
+ this.emitOpenSequence(target.id);
3679
3819
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3680
- this.emit("close", void 0);
3681
- this.handleClose();
3682
- this.emit("closed", void 0);
3820
+ this.emitCloseSequence();
3683
3821
  }
3684
3822
  }
3685
3823
  handleClose() {
@@ -3699,17 +3837,102 @@ var init_baseStyles = __esm({
3699
3837
  }
3700
3838
  });
3701
3839
 
3702
- // src/web-component/BottomSheetElement.ts
3703
- var BottomSheetElement_exports = {};
3704
- __export(BottomSheetElement_exports, {
3705
- BottomSheetElement: () => BottomSheetElement,
3706
- defineBottomSheet: () => defineBottomSheet
3840
+ // src/web-component/shadow-tree.ts
3841
+ var buildSlot, buildSlotRegion, buildShadowTree;
3842
+ var init_shadow_tree = __esm({
3843
+ "src/web-component/shadow-tree.ts"() {
3844
+ buildSlot = (name) => {
3845
+ const slot = document.createElement("slot");
3846
+ slot.name = name;
3847
+ return slot;
3848
+ };
3849
+ buildSlotRegion = (name, className) => {
3850
+ const region = document.createElement("div");
3851
+ region.className = className;
3852
+ region.setAttribute("part", name);
3853
+ const slot = buildSlot(name);
3854
+ region.appendChild(slot);
3855
+ const sync = () => {
3856
+ region.hidden = slot.assignedNodes({ flatten: true }).length === 0;
3857
+ };
3858
+ slot.addEventListener("slotchange", sync);
3859
+ sync();
3860
+ return region;
3861
+ };
3862
+ buildShadowTree = () => {
3863
+ const root = document.createElement("div");
3864
+ root.className = "bs-root";
3865
+ const backdrop = document.createElement("div");
3866
+ backdrop.className = "bs-backdrop";
3867
+ backdrop.setAttribute("part", "backdrop");
3868
+ const screen = document.createElement("div");
3869
+ screen.className = "bs-screen";
3870
+ screen.setAttribute("part", "screen");
3871
+ screen.appendChild(buildSlot("screen"));
3872
+ const sheet = document.createElement("section");
3873
+ sheet.className = "bs-sheet";
3874
+ sheet.setAttribute("part", "sheet");
3875
+ sheet.setAttribute("role", "dialog");
3876
+ sheet.setAttribute("aria-modal", "false");
3877
+ const leftButton = document.createElement("div");
3878
+ leftButton.className = "bs-button-slot";
3879
+ leftButton.setAttribute("data-side", "left");
3880
+ leftButton.setAttribute("part", "left-button");
3881
+ leftButton.appendChild(buildSlot("leftButton"));
3882
+ const rightButton = document.createElement("div");
3883
+ rightButton.className = "bs-button-slot";
3884
+ rightButton.setAttribute("data-side", "right");
3885
+ rightButton.setAttribute("part", "right-button");
3886
+ rightButton.appendChild(buildSlot("rightButton"));
3887
+ const handle = document.createElement("div");
3888
+ handle.className = "bs-handle";
3889
+ handle.setAttribute("part", "handle");
3890
+ handle.setAttribute("role", "slider");
3891
+ handle.setAttribute("tabindex", "0");
3892
+ handle.setAttribute("aria-label", "Resize sheet");
3893
+ const header = buildSlotRegion("header", "bs-header");
3894
+ const content = document.createElement("div");
3895
+ content.className = "bs-content";
3896
+ content.setAttribute("part", "content");
3897
+ content.setAttribute("tabindex", "0");
3898
+ content.setAttribute("role", "region");
3899
+ content.setAttribute("aria-label", "Sheet content");
3900
+ const defaultSlot = document.createElement("slot");
3901
+ content.appendChild(defaultSlot);
3902
+ const footer = buildSlotRegion("footer", "bs-footer");
3903
+ const announcer = document.createElement("span");
3904
+ announcer.className = "bs-sr-announce";
3905
+ announcer.setAttribute("role", "status");
3906
+ announcer.setAttribute("aria-live", "polite");
3907
+ announcer.setAttribute("aria-atomic", "true");
3908
+ announcer.style.cssText = "position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);";
3909
+ sheet.append(handle, header, content, footer);
3910
+ root.append(backdrop, screen, sheet, leftButton, rightButton, announcer);
3911
+ const fragment = document.createDocumentFragment();
3912
+ fragment.appendChild(root);
3913
+ return {
3914
+ fragment,
3915
+ refs: {
3916
+ sheet,
3917
+ handle,
3918
+ header,
3919
+ content,
3920
+ footer,
3921
+ backdrop,
3922
+ screen,
3923
+ leftButton,
3924
+ rightButton,
3925
+ announcer
3926
+ }
3927
+ };
3928
+ };
3929
+ }
3707
3930
  });
3708
- var ATTR_SNAP_POINTS, ATTR_ALLOWED, ATTR_INITIAL, ATTR_MODE, ATTR_BACKDROP, ATTR_STYLESHEET, ATTR_ANIMATION, ATTR_FOCUS_TRAP, ATTR_CLOSE_ON_ESCAPE, ATTR_LOCK_BODY_SCROLL, ATTR_SHEET_LABEL, ATTR_STACK_EFFECT, ATTR_PERSISTENT, ATTR_DISABLE_CLOSE, ATTR_DISABLE_DRAG, ATTR_CLOSE_ON_ROUTE_CHANGE, ATTR_RADIUS, ATTR_MAX_HEIGHT, ATTR_RETURN_FOCUS_TO, ATTR_BACKDROP_COLOR, ATTR_SCRIM_COLOR, ATTR_SNAP, LIVE_ATTRS, isValidSnapPoint, DEFAULT_SNAP_POINTS, parseSnapPoints, VALID_MODES, VALID_ANIMATIONS, parseMode, parseAnimation, parseList, parseDimension, ANCHOR_POSITIONS, ANCHOR_ANIMATIONS, parseAnchorOptions, buildSlot, buildSlotRegion, buildShadowTree, BaseHTMLElement, BottomSheetElement, defineBottomSheet;
3709
- var init_BottomSheetElement = __esm({
3710
- "src/web-component/BottomSheetElement.ts"() {
3711
- init_BottomSheetEngine();
3712
- init_baseStyles();
3931
+
3932
+ // src/web-component/attributes.ts
3933
+ var ATTR_SNAP_POINTS, ATTR_ALLOWED, ATTR_INITIAL, ATTR_MODE, ATTR_BACKDROP, ATTR_STYLESHEET, ATTR_ANIMATION, ATTR_FOCUS_TRAP, ATTR_CLOSE_ON_ESCAPE, ATTR_LOCK_BODY_SCROLL, ATTR_SHEET_LABEL, ATTR_STACK_EFFECT, ATTR_PERSISTENT, ATTR_DISABLE_CLOSE, ATTR_DISABLE_DRAG, ATTR_CLOSE_ON_ROUTE_CHANGE, ATTR_RADIUS, ATTR_MAX_HEIGHT, ATTR_RETURN_FOCUS_TO, ATTR_BACKDROP_COLOR, ATTR_SCRIM_COLOR, ATTR_SNAP, LIVE_ATTRS, OBSERVED_ATTRS, isValidSnapPoint, DEFAULT_SNAP_POINTS, parseSnapPoints, VALID_MODES, VALID_ANIMATIONS, parseMode, parseAnimation, parseList, parseDimension, ANCHOR_POSITIONS, ANCHOR_ANIMATIONS, parseAnchorOptions;
3934
+ var init_attributes = __esm({
3935
+ "src/web-component/attributes.ts"() {
3713
3936
  ATTR_SNAP_POINTS = "snap-points";
3714
3937
  ATTR_ALLOWED = "allowed";
3715
3938
  ATTR_INITIAL = "initial";
@@ -3737,8 +3960,34 @@ var init_BottomSheetElement = __esm({
3737
3960
  ATTR_MAX_HEIGHT,
3738
3961
  ATTR_BACKDROP_COLOR,
3739
3962
  ATTR_SCRIM_COLOR,
3740
- ATTR_SNAP
3963
+ ATTR_SNAP,
3964
+ ATTR_PERSISTENT,
3965
+ ATTR_DISABLE_CLOSE,
3966
+ ATTR_DISABLE_DRAG
3741
3967
  ]);
3968
+ OBSERVED_ATTRS = [
3969
+ ATTR_SNAP_POINTS,
3970
+ ATTR_ALLOWED,
3971
+ ATTR_INITIAL,
3972
+ ATTR_MODE,
3973
+ ATTR_BACKDROP,
3974
+ ATTR_ANIMATION,
3975
+ ATTR_FOCUS_TRAP,
3976
+ ATTR_CLOSE_ON_ESCAPE,
3977
+ ATTR_LOCK_BODY_SCROLL,
3978
+ ATTR_SHEET_LABEL,
3979
+ ATTR_STACK_EFFECT,
3980
+ ATTR_PERSISTENT,
3981
+ ATTR_DISABLE_CLOSE,
3982
+ ATTR_DISABLE_DRAG,
3983
+ ATTR_CLOSE_ON_ROUTE_CHANGE,
3984
+ ATTR_RADIUS,
3985
+ ATTR_MAX_HEIGHT,
3986
+ ATTR_RETURN_FOCUS_TO,
3987
+ ATTR_BACKDROP_COLOR,
3988
+ ATTR_SCRIM_COLOR,
3989
+ ATTR_SNAP
3990
+ ];
3742
3991
  isValidSnapPoint = (p) => !!p && typeof p === "object" && typeof p.id === "string" && (typeof p.size === "number" || typeof p.size === "string");
3743
3992
  DEFAULT_SNAP_POINTS = [{ id: "full", size: "full" }];
3744
3993
  parseSnapPoints = (raw) => {
@@ -3811,91 +4060,22 @@ var init_BottomSheetElement = __esm({
3811
4060
  animation: animation && ANCHOR_ANIMATIONS.includes(animation) ? animation : void 0
3812
4061
  };
3813
4062
  };
3814
- buildSlot = (name) => {
3815
- const slot = document.createElement("slot");
3816
- slot.name = name;
3817
- return slot;
3818
- };
3819
- buildSlotRegion = (name, className) => {
3820
- const region = document.createElement("div");
3821
- region.className = className;
3822
- region.setAttribute("part", name);
3823
- const slot = buildSlot(name);
3824
- region.appendChild(slot);
3825
- const sync = () => {
3826
- region.hidden = slot.assignedNodes({ flatten: true }).length === 0;
3827
- };
3828
- slot.addEventListener("slotchange", sync);
3829
- sync();
3830
- return region;
3831
- };
3832
- buildShadowTree = () => {
3833
- const root = document.createElement("div");
3834
- root.className = "bs-root";
3835
- const backdrop = document.createElement("div");
3836
- backdrop.className = "bs-backdrop";
3837
- backdrop.setAttribute("part", "backdrop");
3838
- const screen = document.createElement("div");
3839
- screen.className = "bs-screen";
3840
- screen.setAttribute("part", "screen");
3841
- screen.appendChild(buildSlot("screen"));
3842
- const sheet = document.createElement("section");
3843
- sheet.className = "bs-sheet";
3844
- sheet.setAttribute("part", "sheet");
3845
- sheet.setAttribute("role", "dialog");
3846
- sheet.setAttribute("aria-modal", "false");
3847
- const leftButton = document.createElement("div");
3848
- leftButton.className = "bs-button-slot";
3849
- leftButton.setAttribute("data-side", "left");
3850
- leftButton.setAttribute("part", "left-button");
3851
- leftButton.appendChild(buildSlot("leftButton"));
3852
- const rightButton = document.createElement("div");
3853
- rightButton.className = "bs-button-slot";
3854
- rightButton.setAttribute("data-side", "right");
3855
- rightButton.setAttribute("part", "right-button");
3856
- rightButton.appendChild(buildSlot("rightButton"));
3857
- const handle = document.createElement("div");
3858
- handle.className = "bs-handle";
3859
- handle.setAttribute("part", "handle");
3860
- handle.setAttribute("role", "slider");
3861
- handle.setAttribute("tabindex", "0");
3862
- handle.setAttribute("aria-label", "Resize sheet");
3863
- const header = buildSlotRegion("header", "bs-header");
3864
- const content = document.createElement("div");
3865
- content.className = "bs-content";
3866
- content.setAttribute("part", "content");
3867
- content.setAttribute("tabindex", "0");
3868
- content.setAttribute("role", "region");
3869
- content.setAttribute("aria-label", "Sheet content");
3870
- const defaultSlot = document.createElement("slot");
3871
- content.appendChild(defaultSlot);
3872
- const footer = buildSlotRegion("footer", "bs-footer");
3873
- const announcer = document.createElement("span");
3874
- announcer.className = "bs-sr-announce";
3875
- announcer.setAttribute("role", "status");
3876
- announcer.setAttribute("aria-live", "polite");
3877
- announcer.setAttribute("aria-atomic", "true");
3878
- announcer.style.cssText = "position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);";
3879
- sheet.append(handle, header, content, footer);
3880
- root.append(backdrop, screen, sheet, leftButton, rightButton, announcer);
3881
- const fragment = document.createDocumentFragment();
3882
- fragment.appendChild(root);
3883
- return {
3884
- fragment,
3885
- refs: {
3886
- sheet,
3887
- handle,
3888
- header,
3889
- content,
3890
- footer,
3891
- backdrop,
3892
- screen,
3893
- leftButton,
3894
- rightButton,
3895
- announcer
3896
- }
3897
- };
3898
- };
4063
+ }
4064
+ });
4065
+
4066
+ // src/web-component/BottomSheetElement.ts
4067
+ var BottomSheetElement_exports = {};
4068
+ __export(BottomSheetElement_exports, {
4069
+ BottomSheetElement: () => BottomSheetElement,
4070
+ defineBottomSheet: () => defineBottomSheet
4071
+ });
4072
+ var BaseHTMLElement, BottomSheetElement, defineBottomSheet;
4073
+ var init_BottomSheetElement = __esm({
4074
+ "src/web-component/BottomSheetElement.ts"() {
4075
+ init_BottomSheetEngine();
4076
+ init_baseStyles();
4077
+ init_shadow_tree();
4078
+ init_attributes();
3899
4079
  BaseHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
3900
4080
  };
3901
4081
  BottomSheetElement = class extends BaseHTMLElement {
@@ -3908,6 +4088,7 @@ var init_BottomSheetElement = __esm({
3908
4088
  __publicField(this, "backdropClickHandler", null);
3909
4089
  __publicField(this, "stylesheetLinked", false);
3910
4090
  __publicField(this, "declarativeAnchors", []);
4091
+ __publicField(this, "lastActiveId", null);
3911
4092
  this.root = this.attachShadow({ mode: "open" });
3912
4093
  const baseStyle = document.createElement("style");
3913
4094
  baseStyle.textContent = baseStyles;
@@ -3930,29 +4111,7 @@ var init_BottomSheetElement = __esm({
3930
4111
  this.root.appendChild(fragment);
3931
4112
  }
3932
4113
  static get observedAttributes() {
3933
- return [
3934
- ATTR_SNAP_POINTS,
3935
- ATTR_ALLOWED,
3936
- ATTR_INITIAL,
3937
- ATTR_MODE,
3938
- ATTR_BACKDROP,
3939
- ATTR_ANIMATION,
3940
- ATTR_FOCUS_TRAP,
3941
- ATTR_CLOSE_ON_ESCAPE,
3942
- ATTR_LOCK_BODY_SCROLL,
3943
- ATTR_SHEET_LABEL,
3944
- ATTR_STACK_EFFECT,
3945
- ATTR_PERSISTENT,
3946
- ATTR_DISABLE_CLOSE,
3947
- ATTR_DISABLE_DRAG,
3948
- ATTR_CLOSE_ON_ROUTE_CHANGE,
3949
- ATTR_RADIUS,
3950
- ATTR_MAX_HEIGHT,
3951
- ATTR_RETURN_FOCUS_TO,
3952
- ATTR_BACKDROP_COLOR,
3953
- ATTR_SCRIM_COLOR,
3954
- ATTR_SNAP
3955
- ];
4114
+ return OBSERVED_ATTRS.slice();
3956
4115
  }
3957
4116
  connectedCallback() {
3958
4117
  if (!this.stylesheetLinked) {
@@ -3996,6 +4155,15 @@ var init_BottomSheetElement = __esm({
3996
4155
  case ATTR_SCRIM_COLOR:
3997
4156
  this.engine.setScrimColor(value);
3998
4157
  break;
4158
+ case ATTR_PERSISTENT:
4159
+ this.engine.setPersistent(value === "true");
4160
+ break;
4161
+ case ATTR_DISABLE_CLOSE:
4162
+ this.engine.setDisableClose(value === "true");
4163
+ break;
4164
+ case ATTR_DISABLE_DRAG:
4165
+ this.engine.setDisableDrag(value === "true");
4166
+ break;
3999
4167
  case ATTR_SNAP:
4000
4168
  if (value && value !== this.engine.state.activeId) {
4001
4169
  void this.engine.snapTo(value);
@@ -4007,6 +4175,7 @@ var init_BottomSheetElement = __esm({
4007
4175
  return this.engine;
4008
4176
  }
4009
4177
  teardownEngine() {
4178
+ this.lastActiveId = this.engine?.state.activeId ?? this.lastActiveId;
4010
4179
  this.offHandlers.forEach((off) => off());
4011
4180
  this.offHandlers = [];
4012
4181
  if (this.backdropClickHandler) {
@@ -4044,6 +4213,9 @@ var init_BottomSheetElement = __esm({
4044
4213
  };
4045
4214
  backdrop.addEventListener("click", this.backdropClickHandler);
4046
4215
  }
4216
+ const snapPoints = parseSnapPoints(this.getAttribute(ATTR_SNAP_POINTS));
4217
+ const initialAttr = this.getAttribute(ATTR_INITIAL) ?? void 0;
4218
+ const initial = this.lastActiveId && snapPoints.some((p) => p.id === this.lastActiveId) ? this.lastActiveId : initialAttr;
4047
4219
  const opts = {
4048
4220
  element: sheet,
4049
4221
  handle,
@@ -4051,9 +4223,9 @@ var init_BottomSheetElement = __esm({
4051
4223
  backdrop: showBackdrop ? backdrop : void 0,
4052
4224
  scrim: this.refs.screen,
4053
4225
  mode,
4054
- snapPoints: parseSnapPoints(this.getAttribute(ATTR_SNAP_POINTS)),
4226
+ snapPoints,
4055
4227
  allowed: parseList(this.getAttribute(ATTR_ALLOWED)),
4056
- initial: this.getAttribute(ATTR_INITIAL) ?? void 0,
4228
+ initial,
4057
4229
  animation: parseAnimation(this.getAttribute(ATTR_ANIMATION)),
4058
4230
  focusTrap,
4059
4231
  closeOnEscape: this.getAttribute(ATTR_CLOSE_ON_ESCAPE) !== "false",
@@ -4083,6 +4255,15 @@ var init_BottomSheetElement = __esm({
4083
4255
  announcer.textContent = payload.id;
4084
4256
  this.dispatchEvent(new CustomEvent("snap", { detail: payload }));
4085
4257
  }),
4258
+ this.engine.on("before-snap", (payload) => {
4259
+ const accepted = this.dispatchEvent(
4260
+ new CustomEvent("before-snap", {
4261
+ detail: payload,
4262
+ cancelable: true
4263
+ })
4264
+ );
4265
+ if (!accepted) payload.cancel();
4266
+ }),
4086
4267
  this.engine.on(
4087
4268
  "open",
4088
4269
  (payload) => this.dispatchEvent(new CustomEvent("open", { detail: payload }))