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