@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/index.cjs CHANGED
@@ -270,18 +270,25 @@ var SnapResolver = class {
270
270
  __publicField(this, "resolved", []);
271
271
  __publicField(this, "maxAxisSize", 0);
272
272
  __publicField(this, "rangeCache", null);
273
+ __publicField(this, "notified", false);
273
274
  this.raw = raw;
274
275
  this.allowedIds = allowed ?? raw.map((p) => p.id);
275
276
  this.recompute();
276
277
  }
277
278
  recompute() {
278
- this.resolved = resolveSnapList(this.raw, this.mode, this.measureFit);
279
- this.maxAxisSize = this.resolved.reduce(
279
+ const measure = this.measureFit;
280
+ let measured;
281
+ const measureOnce = measure ? () => measured ?? (measured = measure()) : void 0;
282
+ this.resolved = resolveSnapList(this.raw, this.mode, measureOnce);
283
+ const nextMax = this.resolved.reduce(
280
284
  (m, s) => s.size > m ? s.size : m,
281
285
  0
282
286
  );
283
287
  this.rangeCache = null;
284
- this.onMaxAxisSizeChange?.(this.maxAxisSize);
288
+ const changed = nextMax !== this.maxAxisSize || !this.notified;
289
+ this.maxAxisSize = nextMax;
290
+ this.notified = true;
291
+ if (changed) this.onMaxAxisSizeChange?.(this.maxAxisSize);
285
292
  }
286
293
  setRaw(raw) {
287
294
  this.raw = raw;
@@ -434,7 +441,10 @@ var ensureHandler = () => {
434
441
  target.consumed = true;
435
442
  const idx = closers.indexOf(target);
436
443
  if (idx !== -1) closers.splice(idx, 1);
437
- target.close();
444
+ const settled = target;
445
+ void Promise.resolve(settled.close()).then(() => {
446
+ if (settled.isOpen()) settled.restore();
447
+ });
438
448
  });
439
449
  };
440
450
  var programmaticBack = () => {
@@ -470,16 +480,7 @@ function installRoute(deps) {
470
480
  pushed = false;
471
481
  }
472
482
  };
473
- const onOpen = () => {
474
- if (closer) return;
475
- closer = {
476
- isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
477
- isTop: () => deps.isTopSheet(),
478
- close: () => void deps.close(),
479
- consumed: false
480
- };
481
- ensureHandler();
482
- closers.push(closer);
483
+ const pushEntry = () => {
483
484
  try {
484
485
  if (deps.routedTo !== void 0) {
485
486
  history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
@@ -491,6 +492,25 @@ function installRoute(deps) {
491
492
  pushed = false;
492
493
  }
493
494
  };
495
+ const onOpen = () => {
496
+ if (closer) return;
497
+ const self = {
498
+ isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
499
+ isTop: () => deps.isTopSheet(),
500
+ close: () => deps.close(),
501
+ restore: () => {
502
+ if (deps.isDestroyed() || closer !== self) return;
503
+ self.consumed = false;
504
+ if (!closers.includes(self)) closers.push(self);
505
+ pushEntry();
506
+ },
507
+ consumed: false
508
+ };
509
+ closer = self;
510
+ ensureHandler();
511
+ closers.push(self);
512
+ pushEntry();
513
+ };
494
514
  const unsubscribeOpen = deps.on("open", onOpen);
495
515
  const unsubscribeClose = deps.on("close", teardownEntry);
496
516
  if (deps.getSize() > 0) onOpen();
@@ -537,11 +557,13 @@ var CONTENT_PULL_THRESHOLD = 60;
537
557
  function installContentSwipe(deps) {
538
558
  const { container } = deps;
539
559
  let startY = 0;
560
+ let startScrollTop = 0;
540
561
  let moved = false;
541
562
  const onTouchStart = (e) => {
542
563
  const t = e.touches[0];
543
564
  if (!t) return;
544
565
  startY = t.clientY;
566
+ startScrollTop = container.scrollTop;
545
567
  moved = false;
546
568
  };
547
569
  const onTouchMove = (e) => {
@@ -552,6 +574,7 @@ function installContentSwipe(deps) {
552
574
  const onTouchEnd = (e) => {
553
575
  if (!moved) return;
554
576
  if (deps.isDragging() || deps.isAnimating()) return;
577
+ if (container.scrollTop !== startScrollTop) return;
555
578
  const t = e.changedTouches[0];
556
579
  if (!t) return;
557
580
  const deltaY = t.clientY - startY;
@@ -663,6 +686,131 @@ function installResizeObserver(deps) {
663
686
  };
664
687
  }
665
688
 
689
+ // src/core/features/fit-observer.ts
690
+ var contentTargets = (scroller) => {
691
+ if (!scroller) return [];
692
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
693
+ const out = [];
694
+ for (const child of Array.from(scroller.children)) {
695
+ if (hasSlot && child instanceof HTMLSlotElement) {
696
+ for (const el of child.assignedElements()) out.push(el);
697
+ } else {
698
+ out.push(child);
699
+ }
700
+ }
701
+ return out;
702
+ };
703
+ function installFitObserver(deps) {
704
+ if (typeof ResizeObserver === "undefined") return () => {
705
+ };
706
+ if (!deps.hasFitSnap()) return () => {
707
+ };
708
+ let raf = 0;
709
+ const schedule = () => {
710
+ if (raf) return;
711
+ raf = requestAnimationFrame(() => {
712
+ raf = 0;
713
+ if (deps.isDestroyed() || deps.isDragging()) return;
714
+ deps.recompute();
715
+ });
716
+ };
717
+ const ro = new ResizeObserver(schedule);
718
+ ro.observe(deps.handle);
719
+ const scroller = deps.scrollContainer;
720
+ const observed = /* @__PURE__ */ new Set();
721
+ const slots = /* @__PURE__ */ new Set();
722
+ const hasSlot = typeof HTMLSlotElement !== "undefined";
723
+ const onSlotChange = () => resync();
724
+ const trackSlots = () => {
725
+ if (!scroller || !hasSlot) return;
726
+ for (const child of Array.from(scroller.children)) {
727
+ if (child instanceof HTMLSlotElement && !slots.has(child)) {
728
+ child.addEventListener("slotchange", onSlotChange);
729
+ slots.add(child);
730
+ }
731
+ }
732
+ };
733
+ const resync = () => {
734
+ trackSlots();
735
+ const targets = contentTargets(scroller);
736
+ for (const el of observed) {
737
+ if (!targets.includes(el)) {
738
+ ro.unobserve(el);
739
+ observed.delete(el);
740
+ }
741
+ }
742
+ for (const el of targets) {
743
+ if (!observed.has(el)) {
744
+ ro.observe(el);
745
+ observed.add(el);
746
+ }
747
+ }
748
+ schedule();
749
+ };
750
+ let mo;
751
+ if (scroller) {
752
+ resync();
753
+ if (typeof MutationObserver !== "undefined") {
754
+ mo = new MutationObserver(resync);
755
+ mo.observe(scroller, { childList: true });
756
+ }
757
+ }
758
+ return () => {
759
+ ro.disconnect();
760
+ mo?.disconnect();
761
+ for (const slot of slots) {
762
+ slot.removeEventListener("slotchange", onSlotChange);
763
+ }
764
+ if (raf) cancelAnimationFrame(raf);
765
+ };
766
+ }
767
+
768
+ // src/core/features/fit-measurement.ts
769
+ var measureSheetNatural = (element, content, vertical) => {
770
+ if (!content) {
771
+ return vertical ? element.offsetHeight : element.offsetWidth;
772
+ }
773
+ const axis = vertical ? "height" : "width";
774
+ const sheetStyle = element.style;
775
+ const cs = content.style;
776
+ const prevSheet = sheetStyle[axis];
777
+ const prevFlex = cs.flex;
778
+ const prevContent = vertical ? cs.height : cs.width;
779
+ sheetStyle[axis] = "auto";
780
+ cs.flex = "none";
781
+ if (vertical) cs.height = "auto";
782
+ else cs.width = "auto";
783
+ const poked = vertical ? element.offsetHeight : element.offsetWidth;
784
+ const contentBox = vertical ? content.clientHeight : content.clientWidth;
785
+ const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
786
+ const natural = Math.max(poked, poked - contentBox + contentScroll);
787
+ sheetStyle[axis] = prevSheet;
788
+ cs.flex = prevFlex;
789
+ if (vertical) cs.height = prevContent;
790
+ else cs.width = prevContent;
791
+ return natural;
792
+ };
793
+ var containingExtent = (element, vertical) => {
794
+ const op = element.offsetParent;
795
+ if (op && op !== document.body && op !== document.documentElement) {
796
+ return vertical ? op.clientHeight : op.clientWidth;
797
+ }
798
+ return vertical ? window.innerHeight : window.innerWidth;
799
+ };
800
+ var measureFitSize = (deps) => {
801
+ const vertical = layoutAxis(deps.mode) === "height";
802
+ const natural = measureSheetNatural(
803
+ deps.element,
804
+ deps.scrollContainer,
805
+ vertical
806
+ );
807
+ const cap = deps.getMaxHeightCap();
808
+ const capped = cap !== void 0 ? Math.min(natural, cap) : natural;
809
+ if (typeof window === "undefined") return capped;
810
+ const viewport = containingExtent(deps.element, vertical);
811
+ return viewport > 0 ? Math.min(capped, viewport) : capped;
812
+ };
813
+
666
814
  // src/core/features/slider-keyboard.ts
667
815
  function installSliderKeyboard(deps) {
668
816
  const isVerticalAxis = deps.mode === "bottom" || deps.mode === "top";
@@ -1149,6 +1297,9 @@ var ScrimController = class {
1149
1297
  const s = this.screenComponent.style;
1150
1298
  s.opacity = "";
1151
1299
  s.display = "";
1300
+ s.position = "";
1301
+ s.inset = "";
1302
+ s.pointerEvents = "";
1152
1303
  }
1153
1304
  if (this.backdrop) {
1154
1305
  this.backdrop.style.opacity = "";
@@ -1397,7 +1548,12 @@ var AnimationRunner = class {
1397
1548
  this.currentSpring?.cancel();
1398
1549
  }
1399
1550
  async animateTo(target, velocityPxPerMs) {
1400
- if (target === this.getSize()) return;
1551
+ if (target === this.getSize()) {
1552
+ if (!this.isAnimating && !this.isDragging()) {
1553
+ this.element.style.willChange = "";
1554
+ }
1555
+ return;
1556
+ }
1401
1557
  this.currentTween?.cancel();
1402
1558
  this.currentSpring?.cancel();
1403
1559
  this.getRootEl()?.setAttribute("data-animating", "true");
@@ -1405,7 +1561,7 @@ var AnimationRunner = class {
1405
1561
  if (this.reducedMotion) {
1406
1562
  this.applySizeFn(target);
1407
1563
  this.getRootEl()?.removeAttribute("data-animating");
1408
- if (!this.isDragging()) this.element.style.willChange = "auto";
1564
+ if (!this.isDragging()) this.element.style.willChange = "";
1409
1565
  return;
1410
1566
  }
1411
1567
  if (this.animationKind === "spring") {
@@ -1433,7 +1589,7 @@ var AnimationRunner = class {
1433
1589
  }
1434
1590
  if (!this.isAnimating) {
1435
1591
  this.getRootEl()?.removeAttribute("data-animating");
1436
- if (!this.isDragging()) this.element.style.willChange = "auto";
1592
+ if (!this.isDragging()) this.element.style.willChange = "";
1437
1593
  }
1438
1594
  }
1439
1595
  destroy() {
@@ -1464,9 +1620,35 @@ var isVisible = (el) => {
1464
1620
  }
1465
1621
  return el.offsetParent !== null;
1466
1622
  };
1467
- var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
1623
+ var collectFocusables = (root) => {
1624
+ const out = Array.from(root.querySelectorAll(FOCUSABLE));
1625
+ if (typeof HTMLSlotElement !== "undefined") {
1626
+ for (const slot of Array.from(root.querySelectorAll("slot"))) {
1627
+ for (const assigned of slot.assignedElements()) {
1628
+ if (assigned.matches(FOCUSABLE)) out.push(assigned);
1629
+ out.push(...collectFocusables(assigned));
1630
+ }
1631
+ }
1632
+ }
1633
+ return out;
1634
+ };
1635
+ var focusables = (root) => collectFocusables(root).filter(
1468
1636
  (el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
1469
1637
  );
1638
+ var containsComposed = (container, node) => {
1639
+ let n = node;
1640
+ while (n) {
1641
+ if (n === container) return true;
1642
+ const slot = n.assignedSlot;
1643
+ if (slot) {
1644
+ n = slot;
1645
+ continue;
1646
+ }
1647
+ const parent = n.parentNode;
1648
+ n = parent instanceof ShadowRoot ? parent.host : parent;
1649
+ }
1650
+ return false;
1651
+ };
1470
1652
  var trapStack = [];
1471
1653
  var installFocusTrap = (container, options = {}) => {
1472
1654
  if (typeof document === "undefined") return () => {
@@ -1476,7 +1658,13 @@ var installFocusTrap = (container, options = {}) => {
1476
1658
  const isActive = () => trapStack[trapStack.length - 1] === token;
1477
1659
  const previouslyFocused = document.activeElement;
1478
1660
  const focusInitial = () => {
1479
- const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
1661
+ const spec = options.initialFocus;
1662
+ if (spec === false) {
1663
+ if (!container.hasAttribute("tabindex")) container.tabIndex = -1;
1664
+ container.focus({ preventScroll: true });
1665
+ return;
1666
+ }
1667
+ const initial = typeof spec === "string" ? container.querySelector(spec) : spec ?? focusables(container)[0] ?? container;
1480
1668
  initial?.focus({ preventScroll: true });
1481
1669
  };
1482
1670
  focusInitial();
@@ -1506,7 +1694,7 @@ var installFocusTrap = (container, options = {}) => {
1506
1694
  const handleFocusIn = (e) => {
1507
1695
  if (!isActive()) return;
1508
1696
  const target = e.target;
1509
- if (target && !container.contains(target)) {
1697
+ if (target && !containsComposed(container, target)) {
1510
1698
  const list = focusables(container);
1511
1699
  list[0]?.focus({ preventScroll: true });
1512
1700
  }
@@ -1543,8 +1731,14 @@ var lockBodyScroll = () => {
1543
1731
  position: body.style.position,
1544
1732
  top: body.style.top,
1545
1733
  width: body.style.width,
1734
+ paddingRight: body.style.paddingRight,
1546
1735
  scrollY: window.scrollY
1547
1736
  };
1737
+ const scrollbarGap = window.innerWidth - document.documentElement.clientWidth;
1738
+ if (scrollbarGap > 0) {
1739
+ const current = parseFloat(getComputedStyle(body).paddingRight) || 0;
1740
+ body.style.paddingRight = `${current + scrollbarGap}px`;
1741
+ }
1548
1742
  body.style.overflow = "hidden";
1549
1743
  body.style.position = "fixed";
1550
1744
  body.style.top = `-${savedStyles.scrollY}px`;
@@ -1562,6 +1756,7 @@ var lockBodyScroll = () => {
1562
1756
  body.style.position = savedStyles.position;
1563
1757
  body.style.top = savedStyles.top;
1564
1758
  body.style.width = savedStyles.width;
1759
+ body.style.paddingRight = savedStyles.paddingRight;
1565
1760
  window.scrollTo(0, savedStyles.scrollY);
1566
1761
  savedStyles = null;
1567
1762
  }
@@ -1774,12 +1969,14 @@ var installGestures = (handle, mode, callbacks) => {
1774
1969
  handle.addEventListener("pointermove", onPointerMove);
1775
1970
  handle.addEventListener("pointerup", finishGesture);
1776
1971
  handle.addEventListener("pointercancel", onPointerCancel);
1972
+ const prevTouchAction = handle.style.touchAction;
1777
1973
  handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1778
1974
  return () => {
1779
1975
  handle.removeEventListener("pointerdown", onPointerDown);
1780
1976
  handle.removeEventListener("pointermove", onPointerMove);
1781
1977
  handle.removeEventListener("pointerup", finishGesture);
1782
1978
  handle.removeEventListener("pointercancel", onPointerCancel);
1979
+ handle.style.touchAction = prevTouchAction;
1783
1980
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1784
1981
  handle.releasePointerCapture(activePointerId);
1785
1982
  }
@@ -1907,7 +2104,7 @@ var GestureController = class {
1907
2104
  this.isDragging_ = false;
1908
2105
  this.keyboardDismissed = false;
1909
2106
  this.deps.getRoot()?.removeAttribute("data-dragging");
1910
- this.deps.element.style.willChange = "auto";
2107
+ this.deps.element.style.willChange = "";
1911
2108
  }
1912
2109
  };
1913
2110
 
@@ -2306,8 +2503,7 @@ function resolveEngineOptions(opts, extras) {
2306
2503
  };
2307
2504
  }
2308
2505
 
2309
- // src/core/BottomSheetEngine.ts
2310
- var HAPTIC_DURATION_MS = 8;
2506
+ // src/core/primitives/teardown-stack.ts
2311
2507
  var TeardownStack = class {
2312
2508
  constructor() {
2313
2509
  __publicField(this, "fns", []);
@@ -2327,6 +2523,9 @@ var TeardownStack = class {
2327
2523
  }
2328
2524
  }
2329
2525
  };
2526
+
2527
+ // src/core/BottomSheetEngine.ts
2528
+ var HAPTIC_DURATION_MS = 8;
2330
2529
  var BottomSheetEngine = class {
2331
2530
  constructor(opts) {
2332
2531
  __publicField(this, "id", nextInstanceId("bs"));
@@ -2351,6 +2550,7 @@ var BottomSheetEngine = class {
2351
2550
  __publicField(this, "disableDragFlag");
2352
2551
  __publicField(this, "closeOnRouteChange");
2353
2552
  __publicField(this, "maxHeightCap");
2553
+ __publicField(this, "maxHeightRaw");
2354
2554
  __publicField(this, "snapPointsRaw");
2355
2555
  __publicField(this, "snaps");
2356
2556
  __publicField(this, "activeId");
@@ -2386,6 +2586,7 @@ var BottomSheetEngine = class {
2386
2586
  __publicField(this, "detachScrimStages", null);
2387
2587
  __publicField(this, "stackEffectEnabled", false);
2388
2588
  __publicField(this, "stackEffectPrimed", false);
2589
+ __publicField(this, "fitDeps");
2389
2590
  __publicField(this, "transformTemplate");
2390
2591
  const resolved = resolveEngineOptions(opts, {
2391
2592
  shouldApplyInertSiblings: () => {
@@ -2458,12 +2659,19 @@ var BottomSheetEngine = class {
2458
2659
  }
2459
2660
  this.activeId = resolved.initialId;
2460
2661
  auditVhUsage(this.snapPointsRaw);
2662
+ this.auditDuplicateSnapIds(this.snapPointsRaw);
2461
2663
  this.rootEl = this.element.closest(".bs-root");
2664
+ this.fitDeps = {
2665
+ element: this.element,
2666
+ scrollContainer: this.scrollContainer,
2667
+ mode: this.mode,
2668
+ getMaxHeightCap: () => this.maxHeightCap
2669
+ };
2462
2670
  this.snaps = new SnapResolver(
2463
2671
  this.snapPointsRaw,
2464
2672
  resolved.initialAllowed,
2465
2673
  this.mode,
2466
- () => this.measureFitSize(),
2674
+ () => measureFitSize(this.fitDeps),
2467
2675
  (maxAxisSize) => {
2468
2676
  this.element.style[layoutAxis(this.mode)] = `${maxAxisSize}px`;
2469
2677
  }
@@ -2497,7 +2705,18 @@ var BottomSheetEngine = class {
2497
2705
  io.observe(this.element);
2498
2706
  this.teardowns.add(() => io.disconnect());
2499
2707
  }
2500
- this.installFitObserver();
2708
+ this.teardowns.add(
2709
+ installFitObserver({
2710
+ handle: this.handle,
2711
+ scrollContainer: this.scrollContainer,
2712
+ hasFitSnap: () => this.snapPointsRaw.some(
2713
+ (p) => p.size === "fit" || p.size === "content"
2714
+ ),
2715
+ isDestroyed: () => this.destroyed,
2716
+ isDragging: () => this.gesture?.isDragging ?? false,
2717
+ recompute: () => this.recompute()
2718
+ })
2719
+ );
2501
2720
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
2502
2721
  if (resolved.maxHeight !== void 0) this.setMaxHeight(resolved.maxHeight);
2503
2722
  this.applyAutoAriaLabelledBy();
@@ -2648,27 +2867,33 @@ var BottomSheetEngine = class {
2648
2867
  } else {
2649
2868
  await this.animation.animateTo(target.size, velocityPxPerMs);
2650
2869
  }
2651
- this.opening = false;
2652
2870
  externalSignal?.removeEventListener("abort", onExternalAbort);
2653
2871
  if (signal.aborted) return;
2654
- this.scrollCache.restore(id, previousSize, target.size);
2872
+ this.opening = false;
2873
+ this.completeSnap(id, target.size, previousSize, wasClosed, false);
2874
+ }
2875
+ completeSnap(id, targetSize, previousSize, wasClosed, withHaptic) {
2876
+ this.scrollCache.restore(id, previousSize, targetSize);
2655
2877
  this.updateAriaSlider();
2656
2878
  this.emit("snap", {
2657
2879
  id,
2658
2880
  size: this.size,
2659
2881
  progress: this.computeProgress(this.size)
2660
2882
  });
2661
- if (wasClosed && target.size > 0) {
2662
- this.emit("open", { id });
2663
- this.handleOpen();
2664
- notifyLinkedSheets(this.linkedSheets, this);
2665
- this.emit("opened", { id });
2666
- }
2667
- if (target.size === 0) {
2668
- this.emit("close", void 0);
2669
- this.handleClose();
2670
- this.emit("closed", void 0);
2671
- }
2883
+ if (withHaptic) this.haptic();
2884
+ if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2885
+ if (targetSize === 0) this.emitCloseSequence();
2886
+ }
2887
+ emitOpenSequence(id) {
2888
+ this.emit("open", { id });
2889
+ this.handleOpen();
2890
+ notifyLinkedSheets(this.linkedSheets, this);
2891
+ this.emit("opened", { id });
2892
+ }
2893
+ emitCloseSequence() {
2894
+ this.emit("close", void 0);
2895
+ this.handleClose();
2896
+ this.emit("closed", void 0);
2672
2897
  }
2673
2898
  async dragTo(targetSize, velocityOrOpts = 0) {
2674
2899
  if (this.destroyed) return;
@@ -2689,19 +2914,31 @@ var BottomSheetEngine = class {
2689
2914
  }
2690
2915
  open(id) {
2691
2916
  if (this.destroyed) return Promise.resolve();
2692
- const target = id ?? this.snaps.getAllowedIds().find((a) => a !== "closed") ?? this.activeId;
2917
+ const target = id ?? this.snaps.getAllowedIds().find((a) => (this.snaps.findById(a)?.size ?? 0) > 0) ?? this.activeId;
2693
2918
  return this.snapTo(target);
2694
2919
  }
2695
2920
  close(reason = "programmatic") {
2696
2921
  if (this.destroyed) return Promise.resolve();
2697
2922
  if (this.disableCloseFlag) return Promise.resolve();
2698
2923
  if (this.size > 0 && this.emitBeforeClose(reason)) return Promise.resolve();
2699
- const closedId = this.snapPointsRaw.find((p) => p.id === "closed")?.id ?? this.snaps.getAllowedIds()[0];
2924
+ 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];
2700
2925
  return this.snapTo(closedId ?? this.activeId);
2701
2926
  }
2702
2927
  canDismiss() {
2703
2928
  return !this.persistent && !this.disableCloseFlag && !this.destroyed;
2704
2929
  }
2930
+ setPersistent(value) {
2931
+ if (this.destroyed) return;
2932
+ this.persistent = value;
2933
+ }
2934
+ setDisableClose(value) {
2935
+ if (this.destroyed) return;
2936
+ this.disableCloseFlag = value;
2937
+ }
2938
+ setDisableDrag(value) {
2939
+ if (this.destroyed) return;
2940
+ this.disableDragFlag = value;
2941
+ }
2705
2942
  isTop() {
2706
2943
  return this.isTopSheet;
2707
2944
  }
@@ -2738,18 +2975,23 @@ var BottomSheetEngine = class {
2738
2975
  axis === "height" ? "max-height" : "max-width",
2739
2976
  value
2740
2977
  );
2741
- if (typeof h === "number") {
2742
- this.maxHeightCap = h;
2743
- this.recompute();
2744
- } else if (typeof window !== "undefined") {
2745
- const probe2 = document.createElement("div");
2746
- probe2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;${axis === "height" ? "height" : "width"}:${value};`;
2747
- document.body.appendChild(probe2);
2748
- const measured = axis === "height" ? probe2.offsetHeight : probe2.offsetWidth;
2749
- probe2.remove();
2750
- this.maxHeightCap = measured > 0 ? measured : void 0;
2751
- this.recompute();
2978
+ this.maxHeightRaw = h;
2979
+ this.resolveMaxHeightCap();
2980
+ this.recompute();
2981
+ }
2982
+ resolveMaxHeightCap() {
2983
+ const raw = this.maxHeightRaw;
2984
+ if (raw === void 0) {
2985
+ this.maxHeightCap = void 0;
2986
+ return;
2752
2987
  }
2988
+ if (typeof raw === "number") {
2989
+ this.maxHeightCap = raw;
2990
+ return;
2991
+ }
2992
+ if (typeof window === "undefined") return;
2993
+ const measured = resolveSnap(raw, this.mode);
2994
+ this.maxHeightCap = measured > 0 ? measured : void 0;
2753
2995
  }
2754
2996
  clampToMaxHeight() {
2755
2997
  if (this.maxHeightCap === void 0) return;
@@ -2880,6 +3122,7 @@ var BottomSheetEngine = class {
2880
3122
  this.newCycle();
2881
3123
  this.snapPointsRaw = points;
2882
3124
  auditVhUsage(points);
3125
+ this.auditDuplicateSnapIds(points);
2883
3126
  this.snaps.setRaw(points);
2884
3127
  if (allowed) this.snaps.setAllowedIds(allowed);
2885
3128
  this.scrollCache.clear();
@@ -2892,6 +3135,7 @@ var BottomSheetEngine = class {
2892
3135
  }
2893
3136
  recompute() {
2894
3137
  if (this.destroyed) return;
3138
+ this.resolveMaxHeightCap();
2895
3139
  this.snaps.recompute();
2896
3140
  this.clampToMaxHeight();
2897
3141
  this.scrim.invalidateOpacityCache();
@@ -2903,129 +3147,6 @@ var BottomSheetEngine = class {
2903
3147
  this.updateAriaSlider();
2904
3148
  }
2905
3149
  }
2906
- contentTargets() {
2907
- const scroller = this.scrollContainer;
2908
- if (!scroller) return [];
2909
- const hasSlot = typeof HTMLSlotElement !== "undefined";
2910
- const out = [];
2911
- for (const child of Array.from(scroller.children)) {
2912
- if (hasSlot && child instanceof HTMLSlotElement) {
2913
- for (const el of child.assignedElements()) out.push(el);
2914
- } else {
2915
- out.push(child);
2916
- }
2917
- }
2918
- return out;
2919
- }
2920
- measureSheetNatural(vertical) {
2921
- const el = this.element;
2922
- const content = this.scrollContainer;
2923
- if (!content) {
2924
- return vertical ? el.offsetHeight : el.offsetWidth;
2925
- }
2926
- const axis = vertical ? "height" : "width";
2927
- const sheetStyle = el.style;
2928
- const cs = content.style;
2929
- const prevSheet = sheetStyle[axis];
2930
- const prevFlex = cs.flex;
2931
- const prevContent = vertical ? cs.height : cs.width;
2932
- sheetStyle[axis] = "auto";
2933
- cs.flex = "none";
2934
- if (vertical) cs.height = "auto";
2935
- else cs.width = "auto";
2936
- const poked = vertical ? el.offsetHeight : el.offsetWidth;
2937
- const contentBox = vertical ? content.clientHeight : content.clientWidth;
2938
- const contentScroll = vertical ? content.scrollHeight : content.scrollWidth;
2939
- const natural = Math.max(poked, poked - contentBox + contentScroll);
2940
- sheetStyle[axis] = prevSheet;
2941
- cs.flex = prevFlex;
2942
- if (vertical) cs.height = prevContent;
2943
- else cs.width = prevContent;
2944
- return natural;
2945
- }
2946
- measureFitSize() {
2947
- const vertical = layoutAxis(this.mode) === "height";
2948
- const natural = this.measureSheetNatural(vertical);
2949
- const capped = this.maxHeightCap !== void 0 ? Math.min(natural, this.maxHeightCap) : natural;
2950
- if (typeof window === "undefined") return capped;
2951
- const viewport = this.containingExtent(vertical);
2952
- return viewport > 0 ? Math.min(capped, viewport) : capped;
2953
- }
2954
- // The fit cap is the sheet's containing block, not always the window: a
2955
- // position:fixed sheet nested under a transform/filter/will-change ancestor
2956
- // is clipped to that ancestor, so capping by window.innerHeight oversizes the
2957
- // sheet and the content/footer get clipped. offsetParent resolves to that
2958
- // ancestor when present, and is null (→ viewport) for a plain fixed sheet.
2959
- containingExtent(vertical) {
2960
- const op = this.element.offsetParent;
2961
- if (op && op !== document.body && op !== document.documentElement) {
2962
- return vertical ? op.clientHeight : op.clientWidth;
2963
- }
2964
- return vertical ? window.innerHeight : window.innerWidth;
2965
- }
2966
- installFitObserver() {
2967
- if (typeof ResizeObserver === "undefined") return;
2968
- if (!this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"))
2969
- return;
2970
- let raf = 0;
2971
- const schedule = () => {
2972
- if (raf) return;
2973
- raf = requestAnimationFrame(() => {
2974
- raf = 0;
2975
- if (this.destroyed || this.gesture?.isDragging) return;
2976
- this.recompute();
2977
- });
2978
- };
2979
- const ro = new ResizeObserver(schedule);
2980
- ro.observe(this.handle);
2981
- const scroller = this.scrollContainer;
2982
- const observed = /* @__PURE__ */ new Set();
2983
- const slots = /* @__PURE__ */ new Set();
2984
- const hasSlot = typeof HTMLSlotElement !== "undefined";
2985
- const onSlotChange = () => resync();
2986
- const trackSlots = () => {
2987
- if (!scroller || !hasSlot) return;
2988
- for (const child of Array.from(scroller.children)) {
2989
- if (child instanceof HTMLSlotElement && !slots.has(child)) {
2990
- child.addEventListener("slotchange", onSlotChange);
2991
- slots.add(child);
2992
- }
2993
- }
2994
- };
2995
- const resync = () => {
2996
- trackSlots();
2997
- const targets = this.contentTargets();
2998
- for (const el of observed) {
2999
- if (!targets.includes(el)) {
3000
- ro.unobserve(el);
3001
- observed.delete(el);
3002
- }
3003
- }
3004
- for (const el of targets) {
3005
- if (!observed.has(el)) {
3006
- ro.observe(el);
3007
- observed.add(el);
3008
- }
3009
- }
3010
- schedule();
3011
- };
3012
- let mo;
3013
- if (scroller) {
3014
- resync();
3015
- if (typeof MutationObserver !== "undefined") {
3016
- mo = new MutationObserver(resync);
3017
- mo.observe(scroller, { childList: true });
3018
- }
3019
- }
3020
- this.teardowns.add(() => {
3021
- ro.disconnect();
3022
- mo?.disconnect();
3023
- for (const slot of slots) {
3024
- slot.removeEventListener("slotchange", onSlotChange);
3025
- }
3026
- if (raf) cancelAnimationFrame(raf);
3027
- });
3028
- }
3029
3150
  destroy() {
3030
3151
  this.destroyed = true;
3031
3152
  this.opening = false;
@@ -3043,9 +3164,10 @@ var BottomSheetEngine = class {
3043
3164
  this.backdrop.style.opacity = "";
3044
3165
  this.backdrop.style.pointerEvents = "";
3045
3166
  }
3046
- if (this.anchorHost) {
3047
- this.anchorHost.style.removeProperty("--bs-size");
3048
- this.anchorHost.style.removeProperty("--bs-progress");
3167
+ for (const host of [this.anchorHost, this.scrimParent, this.rootEl]) {
3168
+ if (!host) continue;
3169
+ host.style.removeProperty("--bs-size");
3170
+ host.style.removeProperty("--bs-progress");
3049
3171
  }
3050
3172
  if (this.screenComponent) {
3051
3173
  this.screenComponent.style.opacity = "";
@@ -3059,6 +3181,7 @@ var BottomSheetEngine = class {
3059
3181
  }
3060
3182
  newCycle() {
3061
3183
  this.allowOvershoot = false;
3184
+ this.opening = false;
3062
3185
  this.currentAbort.abort();
3063
3186
  this.currentAbort = new AbortController();
3064
3187
  return this.currentAbort.signal;
@@ -3114,8 +3237,14 @@ var BottomSheetEngine = class {
3114
3237
  this.element.setAttribute("aria-labelledby", titled.id);
3115
3238
  }
3116
3239
  recomputeSnaps() {
3240
+ this.resolveMaxHeightCap();
3241
+ this.snaps.setRaw(this.snapPointsRaw);
3242
+ this.clampToMaxHeight();
3243
+ this.scrim.invalidateOpacityCache();
3244
+ }
3245
+ auditDuplicateSnapIds(points) {
3117
3246
  const seen = /* @__PURE__ */ new Set();
3118
- for (const p of this.snapPointsRaw) {
3247
+ for (const p of points) {
3119
3248
  if (seen.has(p.id)) {
3120
3249
  console.warn(
3121
3250
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
@@ -3123,9 +3252,6 @@ var BottomSheetEngine = class {
3123
3252
  }
3124
3253
  seen.add(p.id);
3125
3254
  }
3126
- this.snaps.setRaw(this.snapPointsRaw);
3127
- this.clampToMaxHeight();
3128
- this.scrim.invalidateOpacityCache();
3129
3255
  }
3130
3256
  getAllowedRange() {
3131
3257
  return this.snaps.getAllowedRange();
@@ -3313,9 +3439,15 @@ var BottomSheetEngine = class {
3313
3439
  flickVelocity: this.flickVelocity,
3314
3440
  dragThreshold: this.dragThreshold
3315
3441
  });
3316
- if (!target) return;
3442
+ if (!target) {
3443
+ this.clearWillChangeIfIdle();
3444
+ return;
3445
+ }
3317
3446
  const previousId = this.activeId;
3318
- if (target.id === previousId && target.size === this.size) return;
3447
+ if (target.id === previousId && target.size === this.size) {
3448
+ this.clearWillChangeIfIdle();
3449
+ return;
3450
+ }
3319
3451
  if (this.emitBeforeSnap(target, previousId)) {
3320
3452
  const restore = this.snaps.findById(previousId);
3321
3453
  if (restore) {
@@ -3338,27 +3470,19 @@ var BottomSheetEngine = class {
3338
3470
  if (signal.aborted) return;
3339
3471
  this.allowOvershoot = false;
3340
3472
  this.opening = false;
3341
- this.scrollCache.restore(target.id, previousSize, target.size);
3342
- this.updateAriaSlider();
3343
- this.emit("snap", {
3344
- id: target.id,
3345
- size: this.size,
3346
- progress: this.computeProgress(this.size)
3347
- });
3348
- this.haptic();
3349
- if (previousSnapSize === 0 && target.size > 0) {
3350
- this.emit("open", { id: target.id });
3351
- this.handleOpen();
3352
- notifyLinkedSheets(this.linkedSheets, this);
3353
- this.emit("opened", { id: target.id });
3354
- }
3355
- if (target.size === 0) {
3356
- this.emit("close", void 0);
3357
- this.handleClose();
3358
- this.emit("closed", void 0);
3359
- }
3473
+ this.completeSnap(
3474
+ target.id,
3475
+ target.size,
3476
+ previousSize,
3477
+ previousSnapSize === 0,
3478
+ true
3479
+ );
3360
3480
  });
3361
3481
  }
3482
+ clearWillChangeIfIdle() {
3483
+ if (this.animation.isAnimating || this.gesture?.isDragging) return;
3484
+ this.element.style.willChange = "";
3485
+ }
3362
3486
  haptic() {
3363
3487
  if (typeof navigator !== "undefined" && "vibrate" in navigator) {
3364
3488
  try {
@@ -3439,14 +3563,9 @@ var BottomSheetEngine = class {
3439
3563
  progress: this.computeProgress(this.size)
3440
3564
  });
3441
3565
  if (target.size > 0 && !this.lifecycle.isInstalled) {
3442
- this.emit("open", { id: target.id });
3443
- this.handleOpen();
3444
- notifyLinkedSheets(this.linkedSheets, this);
3445
- this.emit("opened", { id: target.id });
3566
+ this.emitOpenSequence(target.id);
3446
3567
  } else if (target.size === 0 && this.lifecycle.isInstalled) {
3447
- this.emit("close", void 0);
3448
- this.handleClose();
3449
- this.emit("closed", void 0);
3568
+ this.emitCloseSequence();
3450
3569
  }
3451
3570
  }
3452
3571
  handleClose() {
@@ -3482,6 +3601,95 @@ var createSheetManager = (initial = {}) => {
3482
3601
  };
3483
3602
  };
3484
3603
 
3604
+ // src/core/primitives/overlay-transforms.ts
3605
+ var closedTransform = (edge) => CLOSED_TRANSFORM[edge];
3606
+ var closedTransformFor = (edge, animation, peekPx) => {
3607
+ switch (animation) {
3608
+ case "fade":
3609
+ return "translate3d(0, 0, 0)";
3610
+ case "scale":
3611
+ return "translate3d(0, 0, 0) scale(0.94)";
3612
+ case "slide":
3613
+ default:
3614
+ if (peekPx !== void 0 && edge === "bottom") {
3615
+ return `translate3d(0, calc(100% - ${peekPx}px), 0)`;
3616
+ }
3617
+ return closedTransform(edge);
3618
+ }
3619
+ };
3620
+ var openTransformFor = (animation) => animation === "scale" ? "translate3d(0, 0, 0) scale(1)" : "translate3d(0, 0, 0)";
3621
+
3622
+ // src/core/features/overlay-swipe.ts
3623
+ function installOverlaySwipe(deps) {
3624
+ const el = deps.element;
3625
+ const edge = deps.edge;
3626
+ const isVertical = edge === "bottom" || edge === "top";
3627
+ const sign = edge === "bottom" || edge === "right" ? 1 : -1;
3628
+ const SLOP = 8;
3629
+ let startX = 0;
3630
+ let startY = 0;
3631
+ let startT = 0;
3632
+ let tracking = false;
3633
+ let dragging = false;
3634
+ let pointerId = null;
3635
+ const previousTouchAction = el.style.touchAction;
3636
+ el.style.touchAction = isVertical ? "pan-x" : "pan-y";
3637
+ const onDown = (e) => {
3638
+ if (e.button !== void 0 && e.button !== 0) return;
3639
+ pointerId = e.pointerId;
3640
+ startX = e.clientX;
3641
+ startY = e.clientY;
3642
+ startT = performance.now();
3643
+ tracking = true;
3644
+ dragging = false;
3645
+ };
3646
+ const onMove = (e) => {
3647
+ if (!tracking || e.pointerId !== pointerId) return;
3648
+ const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3649
+ if (!dragging) {
3650
+ if (Math.abs(delta) < SLOP) return;
3651
+ dragging = true;
3652
+ el.style.transition = "none";
3653
+ try {
3654
+ el.setPointerCapture(e.pointerId);
3655
+ } catch {
3656
+ }
3657
+ }
3658
+ const offset = Math.max(0, sign * delta);
3659
+ el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
3660
+ };
3661
+ const finish = (e) => {
3662
+ if (!tracking || e.pointerId !== pointerId) return;
3663
+ tracking = false;
3664
+ pointerId = null;
3665
+ if (!dragging) return;
3666
+ dragging = false;
3667
+ const dt = performance.now() - startT || 1;
3668
+ const delta = isVertical ? e.clientY - startY : e.clientX - startX;
3669
+ const offset = Math.max(0, sign * delta);
3670
+ const velocity = sign * delta / dt;
3671
+ const size = isVertical ? el.offsetHeight : el.offsetWidth;
3672
+ const past = offset > size * deps.getThreshold() || velocity > deps.getVelocityThreshold();
3673
+ el.style.transition = deps.getExitTransition();
3674
+ if (past) {
3675
+ deps.close();
3676
+ } else {
3677
+ el.style.transform = deps.getOpenTransform();
3678
+ }
3679
+ };
3680
+ el.addEventListener("pointerdown", onDown);
3681
+ el.addEventListener("pointermove", onMove);
3682
+ el.addEventListener("pointerup", finish);
3683
+ el.addEventListener("pointercancel", finish);
3684
+ return () => {
3685
+ el.style.touchAction = previousTouchAction;
3686
+ el.removeEventListener("pointerdown", onDown);
3687
+ el.removeEventListener("pointermove", onMove);
3688
+ el.removeEventListener("pointerup", finish);
3689
+ el.removeEventListener("pointercancel", finish);
3690
+ };
3691
+ }
3692
+
3485
3693
  // src/core/overlay.ts
3486
3694
  var OVERLAY_PRESETS = Object.freeze({
3487
3695
  sheet: Object.freeze({
@@ -3513,22 +3721,6 @@ var OVERLAY_PRESETS = Object.freeze({
3513
3721
  swipeToClose: false
3514
3722
  })
3515
3723
  });
3516
- var closedTransform = (edge) => CLOSED_TRANSFORM[edge];
3517
- var closedTransformFor = (edge, animation, peekPx) => {
3518
- switch (animation) {
3519
- case "fade":
3520
- return "translate3d(0, 0, 0)";
3521
- case "scale":
3522
- return "translate3d(0, 0, 0) scale(0.94)";
3523
- case "slide":
3524
- default:
3525
- if (peekPx !== void 0 && edge === "bottom") {
3526
- return `translate3d(0, calc(100% - ${peekPx}px), 0)`;
3527
- }
3528
- return closedTransform(edge);
3529
- }
3530
- };
3531
- var openTransformFor = (animation) => animation === "scale" ? "translate3d(0, 0, 0) scale(1)" : "translate3d(0, 0, 0)";
3532
3724
  var OverlayEngine = class {
3533
3725
  constructor(opts) {
3534
3726
  __publicField(this, "id", nextInstanceId("ovl"));
@@ -3661,6 +3853,7 @@ var OverlayEngine = class {
3661
3853
  if (this.backdrop) {
3662
3854
  this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
3663
3855
  }
3856
+ this.setWillChange(true);
3664
3857
  const cycle = ++this.cycleNonce;
3665
3858
  return new Promise((resolve) => {
3666
3859
  requestAnimationFrame(() => {
@@ -3683,6 +3876,7 @@ var OverlayEngine = class {
3683
3876
  this.releaseInteractiveListeners();
3684
3877
  this.isOpen_ = false;
3685
3878
  this.isAnimating_ = false;
3879
+ this.setWillChange(false);
3686
3880
  this.element.style.transform = closedTransformFor(
3687
3881
  this.edge,
3688
3882
  this.enterAnimation,
@@ -3713,6 +3907,7 @@ var OverlayEngine = class {
3713
3907
  ).then(() => {
3714
3908
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
3715
3909
  this.isAnimating_ = false;
3910
+ this.setWillChange(false);
3716
3911
  this.element.setAttribute("data-state", "open");
3717
3912
  this.emit("open", void 0);
3718
3913
  this.onOpenCb?.();
@@ -3736,6 +3931,7 @@ var OverlayEngine = class {
3736
3931
  if (this.backdrop) {
3737
3932
  this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
3738
3933
  }
3934
+ this.setWillChange(true);
3739
3935
  const cycle = ++this.cycleNonce;
3740
3936
  return new Promise((resolve) => {
3741
3937
  requestAnimationFrame(() => {
@@ -3761,6 +3957,7 @@ var OverlayEngine = class {
3761
3957
  ).then(() => {
3762
3958
  if (this.destroyed || cycle !== this.cycleNonce) return resolve();
3763
3959
  this.isAnimating_ = false;
3960
+ this.setWillChange(false);
3764
3961
  if (this.peekPx === void 0) {
3765
3962
  this.element.setAttribute("hidden", "");
3766
3963
  }
@@ -3783,6 +3980,7 @@ var OverlayEngine = class {
3783
3980
  this.destroyed = true;
3784
3981
  this.cycleNonce++;
3785
3982
  this.bus.clear();
3983
+ this.setWillChange(false);
3786
3984
  this.releaseInteractiveListeners();
3787
3985
  this.releaseStackEntry?.();
3788
3986
  this.releaseStackEntry = null;
@@ -4008,7 +4206,6 @@ var OverlayEngine = class {
4008
4206
  this.originalNextSibling = null;
4009
4207
  }
4010
4208
  applyClosedStyles() {
4011
- this.element.style.willChange = "transform, opacity";
4012
4209
  this.element.style.transform = closedTransformFor(
4013
4210
  this.edge,
4014
4211
  this.enterAnimation,
@@ -4022,7 +4219,12 @@ var OverlayEngine = class {
4022
4219
  if (this.backdrop) {
4023
4220
  this.backdrop.style.opacity = "0";
4024
4221
  this.backdrop.style.pointerEvents = "none";
4025
- this.backdrop.style.willChange = "opacity";
4222
+ }
4223
+ }
4224
+ setWillChange(on) {
4225
+ this.element.style.willChange = on ? "transform, opacity" : "";
4226
+ if (this.backdrop) {
4227
+ this.backdrop.style.willChange = on ? "opacity" : "";
4026
4228
  }
4027
4229
  }
4028
4230
  awaitTransition(cycle, property = "transform") {
@@ -4108,73 +4310,15 @@ var OverlayEngine = class {
4108
4310
  this.lifecycle.release();
4109
4311
  }
4110
4312
  installSwipeToClose() {
4111
- const el = this.element;
4112
- const edge = this.edge;
4113
- const isVertical = edge === "bottom" || edge === "top";
4114
- const sign = edge === "bottom" || edge === "right" ? 1 : -1;
4115
- const SLOP = 8;
4116
- let startX = 0;
4117
- let startY = 0;
4118
- let startT = 0;
4119
- let tracking = false;
4120
- let dragging = false;
4121
- let pointerId = null;
4122
- const previousTouchAction = el.style.touchAction;
4123
- el.style.touchAction = isVertical ? "pan-x" : "pan-y";
4124
- const onDown = (e) => {
4125
- if (e.button !== void 0 && e.button !== 0) return;
4126
- pointerId = e.pointerId;
4127
- startX = e.clientX;
4128
- startY = e.clientY;
4129
- startT = performance.now();
4130
- tracking = true;
4131
- dragging = false;
4132
- };
4133
- const onMove = (e) => {
4134
- if (!tracking || e.pointerId !== pointerId) return;
4135
- const delta = isVertical ? e.clientY - startY : e.clientX - startX;
4136
- if (!dragging) {
4137
- if (Math.abs(delta) < SLOP) return;
4138
- dragging = true;
4139
- el.style.transition = "none";
4140
- try {
4141
- el.setPointerCapture(e.pointerId);
4142
- } catch {
4143
- }
4144
- }
4145
- const offset = Math.max(0, sign * delta);
4146
- el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
4147
- };
4148
- const finish = (e) => {
4149
- if (!tracking || e.pointerId !== pointerId) return;
4150
- tracking = false;
4151
- pointerId = null;
4152
- if (!dragging) return;
4153
- dragging = false;
4154
- const dt = performance.now() - startT || 1;
4155
- const delta = isVertical ? e.clientY - startY : e.clientX - startX;
4156
- const offset = Math.max(0, sign * delta);
4157
- const velocity = sign * delta / dt;
4158
- const size = isVertical ? el.offsetHeight : el.offsetWidth;
4159
- const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
4160
- el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
4161
- if (past) {
4162
- void this.close("swipe");
4163
- } else {
4164
- el.style.transform = openTransformFor(this.enterAnimation);
4165
- }
4166
- };
4167
- el.addEventListener("pointerdown", onDown);
4168
- el.addEventListener("pointermove", onMove);
4169
- el.addEventListener("pointerup", finish);
4170
- el.addEventListener("pointercancel", finish);
4171
- return () => {
4172
- el.style.touchAction = previousTouchAction;
4173
- el.removeEventListener("pointerdown", onDown);
4174
- el.removeEventListener("pointermove", onMove);
4175
- el.removeEventListener("pointerup", finish);
4176
- el.removeEventListener("pointercancel", finish);
4177
- };
4313
+ return installOverlaySwipe({
4314
+ element: this.element,
4315
+ edge: this.edge,
4316
+ getThreshold: () => this.swipeThreshold,
4317
+ getVelocityThreshold: () => this.swipeVelocityThreshold,
4318
+ getExitTransition: () => `transform ${this.effectiveDuration()}ms ${this.exitEasing}`,
4319
+ getOpenTransform: () => openTransformFor(this.enterAnimation),
4320
+ close: () => void this.close("swipe")
4321
+ });
4178
4322
  }
4179
4323
  registerInStack() {
4180
4324
  this.releaseStackEntry = sheetStack.push({