@surdeddd/bottom-sheet 0.13.0 → 0.20.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 (66) hide show
  1. package/README.md +46 -0
  2. package/dist/{BottomSheetCore-D16lsa6g.d.ts → BottomSheetCore-Cgn8b_6A.d.ts} +36 -2
  3. package/dist/{BottomSheetCore-CsIjyq7C.d.cts → BottomSheetCore-VPJZcKcg.d.cts} +36 -2
  4. package/dist/{BottomSheetEngine-BYjAVrmd.d.cts → BottomSheetEngine-B8C2CwF9.d.cts} +1 -1
  5. package/dist/{BottomSheetEngine-YuCNugZn.d.ts → BottomSheetEngine-DwYR_ygJ.d.ts} +1 -1
  6. package/dist/core.cjs +328 -33
  7. package/dist/core.d.cts +2 -2
  8. package/dist/core.d.ts +2 -2
  9. package/dist/core.js +328 -33
  10. package/dist/debug.d.cts +1 -1
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/element.cjs +404 -108
  13. package/dist/element.d.cts +2 -2
  14. package/dist/element.d.ts +2 -2
  15. package/dist/element.iife.global.js +2 -2
  16. package/dist/element.js +404 -108
  17. package/dist/features.cjs +6 -50
  18. package/dist/features.d.cts +4 -4
  19. package/dist/features.d.ts +4 -4
  20. package/dist/features.js +6 -50
  21. package/dist/index-CnfbOKx0.d.cts +9 -0
  22. package/dist/index-DcMkhXAy.d.ts +9 -0
  23. package/dist/index.cjs +335 -829
  24. package/dist/index.d.cts +44 -47
  25. package/dist/index.d.ts +44 -47
  26. package/dist/index.js +336 -816
  27. package/dist/integrations/formik.d.cts +1 -1
  28. package/dist/integrations/formik.d.ts +1 -1
  29. package/dist/integrations/react-hook-form.d.cts +1 -1
  30. package/dist/integrations/react-hook-form.d.ts +1 -1
  31. package/dist/overlay.cjs +5 -5
  32. package/dist/overlay.js +5 -5
  33. package/dist/preact.d.cts +2 -2
  34. package/dist/preact.d.ts +2 -2
  35. package/dist/qwik-lib/index.qwik.cjs +323 -83
  36. package/dist/qwik-lib/index.qwik.mjs +323 -83
  37. package/dist/qwik.cjs +342 -83
  38. package/dist/qwik.d.cts +4 -2
  39. package/dist/qwik.d.ts +4 -2
  40. package/dist/qwik.js +342 -83
  41. package/dist/react.cjs +347 -85
  42. package/dist/react.d.cts +3 -4
  43. package/dist/react.d.ts +3 -4
  44. package/dist/react.js +347 -85
  45. package/dist/solid.cjs +348 -83
  46. package/dist/solid.d.cts +37 -1
  47. package/dist/solid.d.ts +37 -1
  48. package/dist/solid.js +348 -83
  49. package/dist/solid.source.jsx +356 -83
  50. package/dist/styles.css +24 -4
  51. package/dist/svelte-core.cjs +334 -83
  52. package/dist/svelte-core.js +334 -83
  53. package/dist/svelte-src/BottomSheet.svelte +14 -0
  54. package/dist/svelte.cjs +344 -83
  55. package/dist/svelte.d.cts +2 -0
  56. package/dist/svelte.d.ts +2 -0
  57. package/dist/svelte.js +344 -83
  58. package/dist/vue.cjs +350 -83
  59. package/dist/vue.js +350 -83
  60. package/dist/webgl.cjs +673 -0
  61. package/dist/webgl.d.cts +17 -0
  62. package/dist/webgl.d.ts +17 -0
  63. package/dist/webgl.js +671 -0
  64. package/package.json +64 -40
  65. package/dist/index-DdYLOIIT.d.ts +0 -15
  66. package/dist/index-dfrzw-lI.d.cts +0 -15
@@ -9,7 +9,7 @@ import {
9
9
  Show
10
10
  } from "solid-js";
11
11
 
12
- // src/core/lifecycle/sheetStack.ts
12
+ // src/core/lifecycle/sheet-stack.ts
13
13
  var BASE_Z = 100;
14
14
  var STEP = 10;
15
15
  var SheetStack = class {
@@ -132,7 +132,7 @@ function createEventBus() {
132
132
  };
133
133
  }
134
134
 
135
- // src/core/primitives/cssLength.ts
135
+ // src/core/primitives/css-length.ts
136
136
  var probe = null;
137
137
  var PROBE_ATTR = "data-bs-probe";
138
138
  var ensureProbe = (axis) => {
@@ -186,7 +186,7 @@ var resolveSnap = (point, mode, measureFit) => {
186
186
  return Math.max(0, el.getBoundingClientRect().width);
187
187
  };
188
188
 
189
- // src/core/primitives/devWarn.ts
189
+ // src/core/primitives/dev-warn.ts
190
190
  function devWarn(...args) {
191
191
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
192
192
  console.warn(...args);
@@ -321,6 +321,11 @@ var SnapResolver = class {
321
321
  this.raw = raw;
322
322
  this.recompute();
323
323
  }
324
+ setMode(mode) {
325
+ if (mode === this.mode) return;
326
+ this.mode = mode;
327
+ this.recompute();
328
+ }
324
329
  setAllowedIds(ids) {
325
330
  this.allowedIds = ids;
326
331
  this.rangeCache = null;
@@ -902,6 +907,11 @@ var ScrimController = class {
902
907
  if (this.isDestroyed()) return;
903
908
  this.applyScrimStyles({ interactive });
904
909
  }
910
+ setSheetMode(mode) {
911
+ if (this.isDestroyed() || mode === this.mode) return;
912
+ this.mode = mode;
913
+ this.invalidateOpacityCache();
914
+ }
905
915
  setScrimMode(mode, applySize) {
906
916
  if (this.isDestroyed()) return;
907
917
  if (mode === this.scrimMode) return;
@@ -1520,7 +1530,7 @@ var AnimationRunner = class {
1520
1530
  }
1521
1531
  };
1522
1532
 
1523
- // src/core/lifecycle/focusTrap.ts
1533
+ // src/core/lifecycle/focus-trap.ts
1524
1534
  var FOCUSABLE = [
1525
1535
  "a[href]",
1526
1536
  "area[href]",
@@ -1638,7 +1648,7 @@ var installFocusTrap = (container, options = {}) => {
1638
1648
  };
1639
1649
  };
1640
1650
 
1641
- // src/core/lifecycle/scrollLock.ts
1651
+ // src/core/lifecycle/scroll-lock.ts
1642
1652
  var lockCount = 0;
1643
1653
  var savedStyles = null;
1644
1654
  var lockBodyScroll = () => {
@@ -1820,30 +1830,47 @@ var SampleRing = class {
1820
1830
  this.size = 0;
1821
1831
  }
1822
1832
  };
1823
- var installGestures = (handle, mode, callbacks) => {
1833
+ var installGestures = (handle, mode, callbacks, options = {}) => {
1824
1834
  let activePointerId = null;
1825
1835
  let activePointerType = "touch";
1826
1836
  let startCoord = 0;
1837
+ let startCross = 0;
1827
1838
  let lastCoord = 0;
1839
+ let pendingStart = false;
1828
1840
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1829
1841
  const MAX_SAMPLES = 32;
1830
1842
  const samples = new SampleRing(MAX_SAMPLES);
1831
1843
  const axis = isAxisVertical(mode) ? "Y" : "X";
1832
1844
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1833
1845
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1846
+ const crossOf = (e) => axis === "Y" ? e.clientX : e.clientY;
1847
+ const capture = (pointerId) => {
1848
+ try {
1849
+ handle.setPointerCapture(pointerId);
1850
+ } catch {
1851
+ }
1852
+ };
1853
+ const releaseTracking = () => {
1854
+ activePointerId = null;
1855
+ pendingStart = false;
1856
+ samples.reset();
1857
+ };
1834
1858
  const onPointerDown = (e) => {
1835
1859
  if (activePointerId !== null) return;
1836
1860
  if (e.button !== void 0 && e.button !== 0) return;
1861
+ if (options.shouldStart && !options.shouldStart(e)) return;
1837
1862
  activePointerId = e.pointerId;
1838
1863
  activePointerType = e.pointerType || "touch";
1839
1864
  startCoord = coordOf(e);
1865
+ startCross = crossOf(e);
1840
1866
  lastCoord = startCoord;
1841
1867
  samples.reset();
1842
1868
  samples.push(e.timeStamp, startCoord);
1843
- try {
1844
- handle.setPointerCapture(e.pointerId);
1845
- } catch {
1869
+ if (options.deferStart) {
1870
+ pendingStart = true;
1871
+ return;
1846
1872
  }
1873
+ capture(e.pointerId);
1847
1874
  callbacks.onStart(startCoord, activePointerType);
1848
1875
  };
1849
1876
  const onPointerMove = (e) => {
@@ -1851,6 +1878,22 @@ var installGestures = (handle, mode, callbacks) => {
1851
1878
  const coord = coordOf(e);
1852
1879
  const rawDelta = coord - startCoord;
1853
1880
  const delta = rawDelta * sign;
1881
+ if (pendingStart) {
1882
+ const verdict = options.deferStart(e, delta, crossOf(e) - startCross);
1883
+ if (verdict === false) {
1884
+ releaseTracking();
1885
+ return;
1886
+ }
1887
+ if (verdict !== true) return;
1888
+ pendingStart = false;
1889
+ startCoord = coord;
1890
+ lastCoord = coord;
1891
+ samples.reset();
1892
+ samples.push(e.timeStamp, coord);
1893
+ capture(e.pointerId);
1894
+ callbacks.onStart(startCoord, activePointerType);
1895
+ return;
1896
+ }
1854
1897
  lastCoord = coord;
1855
1898
  samples.push(e.timeStamp, coord);
1856
1899
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1859,6 +1902,10 @@ var installGestures = (handle, mode, callbacks) => {
1859
1902
  };
1860
1903
  const finishGesture = (e) => {
1861
1904
  if (e.pointerId !== activePointerId) return;
1905
+ if (pendingStart) {
1906
+ releaseTracking();
1907
+ return;
1908
+ }
1862
1909
  const rawDelta = lastCoord - startCoord;
1863
1910
  const delta = rawDelta * sign;
1864
1911
  let velocity = 0;
@@ -1881,22 +1928,26 @@ var installGestures = (handle, mode, callbacks) => {
1881
1928
  };
1882
1929
  const onPointerCancel = (e) => {
1883
1930
  if (e.pointerId !== activePointerId) return;
1884
- activePointerId = null;
1885
- samples.reset();
1931
+ const wasPending = pendingStart;
1932
+ releaseTracking();
1933
+ if (wasPending) return;
1886
1934
  callbacks.onCancel?.();
1887
1935
  };
1888
1936
  handle.addEventListener("pointerdown", onPointerDown);
1889
1937
  handle.addEventListener("pointermove", onPointerMove);
1890
1938
  handle.addEventListener("pointerup", finishGesture);
1891
1939
  handle.addEventListener("pointercancel", onPointerCancel);
1940
+ const managesTouchAction = options.manageTouchAction !== false;
1892
1941
  const prevTouchAction = handle.style.touchAction;
1893
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1942
+ if (managesTouchAction) {
1943
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1944
+ }
1894
1945
  return () => {
1895
1946
  handle.removeEventListener("pointerdown", onPointerDown);
1896
1947
  handle.removeEventListener("pointermove", onPointerMove);
1897
1948
  handle.removeEventListener("pointerup", finishGesture);
1898
1949
  handle.removeEventListener("pointercancel", onPointerCancel);
1899
- handle.style.touchAction = prevTouchAction;
1950
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1900
1951
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1901
1952
  handle.releasePointerCapture(activePointerId);
1902
1953
  }
@@ -2015,7 +2066,7 @@ var GestureController = class {
2015
2066
  const restoreTo = this.dragStartSize;
2016
2067
  void this.deps.animateTo(restoreTo, 0);
2017
2068
  }
2018
- });
2069
+ }, this.deps.gestureOptions);
2019
2070
  return this.detach;
2020
2071
  }
2021
2072
  forceClearDragState() {
@@ -2027,6 +2078,74 @@ var GestureController = class {
2027
2078
  }
2028
2079
  };
2029
2080
 
2081
+ // src/core/primitives/drag-zones.ts
2082
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2083
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2084
+ var asElement = (target) => {
2085
+ const candidate = target;
2086
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2087
+ };
2088
+ function isDragAllowedFrom(target, mode) {
2089
+ const el = asElement(target);
2090
+ if (!el) return mode !== "zones";
2091
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2092
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2093
+ return true;
2094
+ }
2095
+
2096
+ // src/core/primitives/content-gesture.ts
2097
+ var CONTENT_DRAG_SLOP = 6;
2098
+ function decideContentGesture(input) {
2099
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2100
+ const cross = input.crossDelta ?? 0;
2101
+ const sharesScrollAxis = input.sharesScrollAxis ?? true;
2102
+ if (!sharesScrollAxis) {
2103
+ if (Math.abs(input.delta) < slop && Math.abs(cross) < slop) return "pending";
2104
+ if (Math.abs(input.delta) <= Math.abs(cross)) return "scroll";
2105
+ return "drag";
2106
+ }
2107
+ if (Math.abs(input.delta) < slop) return "pending";
2108
+ if (input.scrollTop > 0) return "scroll";
2109
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2110
+ return "drag";
2111
+ }
2112
+
2113
+ // src/core/primitives/logical-mode.ts
2114
+ var isLogicalMode = (mode) => mode === "start" || mode === "end";
2115
+ var readDirection = (el) => {
2116
+ if (!el) return "ltr";
2117
+ if (typeof getComputedStyle === "function") {
2118
+ try {
2119
+ if (getComputedStyle(el).direction === "rtl") return "rtl";
2120
+ } catch {
2121
+ }
2122
+ }
2123
+ try {
2124
+ const marked = el.closest?.("[dir]");
2125
+ if (marked) {
2126
+ return marked.getAttribute("dir")?.toLowerCase() === "rtl" ? "rtl" : "ltr";
2127
+ }
2128
+ } catch {
2129
+ }
2130
+ return "ltr";
2131
+ };
2132
+ var resolveMode = (mode, el) => {
2133
+ if (!isLogicalMode(mode)) return mode;
2134
+ const rtl = readDirection(el) === "rtl";
2135
+ if (mode === "start") return rtl ? "right" : "left";
2136
+ return rtl ? "left" : "right";
2137
+ };
2138
+
2139
+ // src/core/primitives/touch-scroll-guard.ts
2140
+ function installTouchScrollGuard(surface, isDragging) {
2141
+ const onTouchMove = (e) => {
2142
+ if (!isDragging()) return;
2143
+ if (e.cancelable) e.preventDefault();
2144
+ };
2145
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2146
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2147
+ }
2148
+
2030
2149
  // src/core/primitives/aria-slider-writer.ts
2031
2150
  var AriaSliderWriter = class {
2032
2151
  constructor(handle, mode) {
@@ -2380,9 +2499,11 @@ function resolveEngineOptions(opts, extras) {
2380
2499
  }
2381
2500
  }
2382
2501
  return {
2383
- mode: opts.mode ?? "bottom",
2502
+ mode: resolveMode(opts.mode ?? "bottom", opts.element),
2384
2503
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2385
2504
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2505
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2506
+ dragFromContent: opts.dragFromContent ?? true,
2386
2507
  rubberBandEnabled: opts.rubberBand ?? true,
2387
2508
  closeOnBack: opts.closeOnBack ?? false,
2388
2509
  persistent: opts.persistent ?? false,
@@ -2465,6 +2586,8 @@ var BottomSheetCore = class {
2465
2586
  mode;
2466
2587
  flickVelocity;
2467
2588
  dragThreshold;
2589
+ dragFromMode;
2590
+ dragFromContentDefault;
2468
2591
  rubberBandEnabled;
2469
2592
  scrim;
2470
2593
  aria;
@@ -2477,13 +2600,18 @@ var BottomSheetCore = class {
2477
2600
  disableDragFlag;
2478
2601
  closeOnRouteChange;
2479
2602
  maxHeight;
2603
+ maxHeightDeps;
2480
2604
  snapPointsRaw;
2481
2605
  snaps;
2482
2606
  activeId;
2483
2607
  size = 0;
2484
2608
  gesture;
2609
+ contentGesture;
2610
+ detachSheetGesture = null;
2611
+ detachSliderKeyboard = null;
2485
2612
  rootEl = null;
2486
2613
  destroyed = false;
2614
+ restClosed = false;
2487
2615
  currentViewTransition = null;
2488
2616
  currentAbort = new AbortController();
2489
2617
  sizeWriteSentinel = new WriteSentinel();
@@ -2533,10 +2661,13 @@ var BottomSheetCore = class {
2533
2661
  this.screenComponent = opts.scrim ?? opts.screenComponent;
2534
2662
  this.scrimParent = this.screenComponent?.parentElement ?? null;
2535
2663
  this.mode = resolved.mode;
2664
+ this.element.dataset.mode = this.mode;
2536
2665
  this.transformTemplate = buildTransformTemplate(this.mode);
2537
2666
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2538
2667
  this.flickVelocity = resolved.flickVelocity;
2539
2668
  this.dragThreshold = resolved.dragThreshold;
2669
+ this.dragFromMode = resolved.dragFrom;
2670
+ this.dragFromContentDefault = resolved.dragFromContent;
2540
2671
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2541
2672
  this.scrim = new ScrimController(
2542
2673
  {
@@ -2563,7 +2694,7 @@ var BottomSheetCore = class {
2563
2694
  getRootEl: () => this.rootEl,
2564
2695
  applySize: (size) => this.applySize(size),
2565
2696
  getSize: () => this.size,
2566
- isDragging: () => this.gesture?.isDragging ?? false,
2697
+ isDragging: () => this.isDraggingAny(),
2567
2698
  applyAux: (size) => this.applySize(size, true),
2568
2699
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2569
2700
  },
@@ -2597,7 +2728,7 @@ var BottomSheetCore = class {
2597
2728
  auditVhUsage(this.snapPointsRaw);
2598
2729
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2599
2730
  this.rootEl = this.element.closest(".bs-root");
2600
- this.maxHeight = createMaxHeightController({
2731
+ this.maxHeightDeps = {
2601
2732
  element: this.element,
2602
2733
  mode: this.mode,
2603
2734
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
@@ -2605,7 +2736,8 @@ var BottomSheetCore = class {
2605
2736
  this.snaps.setMaxAxisSize(size);
2606
2737
  },
2607
2738
  recompute: () => this.recompute()
2608
- });
2739
+ };
2740
+ this.maxHeight = createMaxHeightController(this.maxHeightDeps);
2609
2741
  this.fitDeps = {
2610
2742
  element: this.element,
2611
2743
  scrollContainer: this.scrollContainer,
@@ -2624,6 +2756,7 @@ var BottomSheetCore = class {
2624
2756
  const initial = this.snaps.findById(this.activeId);
2625
2757
  if (initial) this.size = initial.size;
2626
2758
  this.applySize(this.size);
2759
+ this.setRestClosed(this.size === 0);
2627
2760
  this.updateAriaSlider();
2628
2761
  this.scrollCache = createScrollCache({
2629
2762
  scrollContainer: this.scrollContainer,
@@ -2654,7 +2787,7 @@ var BottomSheetCore = class {
2654
2787
  (p) => p.size === "fit" || p.size === "content"
2655
2788
  ),
2656
2789
  isDestroyed: () => this.destroyed,
2657
- isDragging: () => this.gesture?.isDragging ?? false,
2790
+ isDragging: () => this.isDraggingAny(),
2658
2791
  recompute: () => this.recompute()
2659
2792
  })
2660
2793
  );
@@ -2680,7 +2813,7 @@ var BottomSheetCore = class {
2680
2813
  autoCollapseAfter
2681
2814
  },
2682
2815
  isDestroyed: () => this.destroyed,
2683
- isDragging: () => this.gesture?.isDragging ?? false,
2816
+ isDragging: () => this.isDraggingAny(),
2684
2817
  isAnimating: () => this.animation.isAnimating,
2685
2818
  isTopSheet: () => this.isTopSheet,
2686
2819
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2710,6 +2843,7 @@ var BottomSheetCore = class {
2710
2843
  void this.snapTo(id);
2711
2844
  },
2712
2845
  close: (reason) => this.close(reason),
2846
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2713
2847
  on: (event, fn) => this.on(event, fn),
2714
2848
  addTeardown: (fn) => this.teardowns.add(fn)
2715
2849
  };
@@ -2738,7 +2872,7 @@ var BottomSheetCore = class {
2738
2872
  return {
2739
2873
  size: this.size,
2740
2874
  activeId: this.activeId,
2741
- isDragging: this.gesture?.isDragging ?? false,
2875
+ isDragging: this.isDraggingAny(),
2742
2876
  isAnimating: this.animation.isAnimating,
2743
2877
  progress: this.computeProgress(this.size)
2744
2878
  };
@@ -2858,9 +2992,16 @@ var BottomSheetCore = class {
2858
2992
  progress: this.computeProgress(this.size)
2859
2993
  });
2860
2994
  if (withHaptic) this.haptic();
2995
+ this.setRestClosed(targetSize === 0);
2861
2996
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2862
2997
  if (targetSize === 0) this.emitCloseSequence();
2863
2998
  }
2999
+ setRestClosed(closed) {
3000
+ if (closed === this.restClosed) return;
3001
+ this.restClosed = closed;
3002
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
3003
+ else this.element.removeAttribute("data-bs-rest");
3004
+ }
2864
3005
  emitOpenSequence(id) {
2865
3006
  this.emit("open", { id });
2866
3007
  this.handleOpen();
@@ -2916,6 +3057,51 @@ var BottomSheetCore = class {
2916
3057
  if (this.destroyed) return;
2917
3058
  this.disableDragFlag = value;
2918
3059
  }
3060
+ setDragFromContent(value) {
3061
+ if (this.destroyed) return;
3062
+ this.dragFromContentDefault = value;
3063
+ }
3064
+ setDragFrom(mode) {
3065
+ if (this.destroyed || mode === this.dragFromMode) return;
3066
+ this.gesture?.forceClearDragState();
3067
+ this.detachSheetGesture?.();
3068
+ this.dragFromMode = mode;
3069
+ this.mountSheetGesture();
3070
+ }
3071
+ getDragFrom() {
3072
+ return this.dragFromMode;
3073
+ }
3074
+ setMode(mode) {
3075
+ if (this.destroyed) return;
3076
+ const next = resolveMode(mode, this.element);
3077
+ if (next === this.mode) return;
3078
+ const wasVertical = this.mode === "bottom" || this.mode === "top";
3079
+ const nowVertical = next === "bottom" || next === "top";
3080
+ this.gesture?.forceClearDragState();
3081
+ this.detachSheetGesture?.();
3082
+ this.animation.cancel();
3083
+ this.element.style.removeProperty(layoutAxis(this.mode));
3084
+ this.mode = next;
3085
+ this.element.dataset.mode = next;
3086
+ this.transformTemplate = buildTransformTemplate(next);
3087
+ this.maxHeightDeps.mode = next;
3088
+ this.fitDeps.mode = next;
3089
+ this.snaps.setMode(next);
3090
+ this.scrim.setSheetMode(next);
3091
+ if (wasVertical !== nowVertical) {
3092
+ this.aria = new AriaSliderWriter(this.handle, next);
3093
+ }
3094
+ this.detachSliderKeyboard?.();
3095
+ this.mountSliderKeyboard();
3096
+ this.mountSheetGesture();
3097
+ this.recompute();
3098
+ const active = this.snaps.findById(this.activeId);
3099
+ if (active) this.applySize(active.size);
3100
+ this.updateAriaSlider();
3101
+ }
3102
+ getMode() {
3103
+ return this.mode;
3104
+ }
2919
3105
  isTop() {
2920
3106
  return this.isTopSheet;
2921
3107
  }
@@ -3070,12 +3256,24 @@ var BottomSheetCore = class {
3070
3256
  auditVhUsage(points);
3071
3257
  this.auditDuplicateSnapIds(points);
3072
3258
  this.snaps.setRaw(points);
3073
- if (allowed) this.snaps.setAllowedIds(allowed);
3259
+ if (allowed) {
3260
+ this.snaps.setAllowedIds(allowed);
3261
+ } else {
3262
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3263
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3264
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3265
+ }
3074
3266
  this.scrollCache.clear();
3075
3267
  this.scrim.invalidateOpacityCache();
3076
3268
  const current = this.snaps.findById(this.activeId);
3077
3269
  if (current) {
3078
3270
  this.applySize(current.size);
3271
+ } else {
3272
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3273
+ if (fallback) {
3274
+ this.activeId = fallback.id;
3275
+ this.applySize(fallback.size);
3276
+ }
3079
3277
  }
3080
3278
  this.updateAriaSlider();
3081
3279
  }
@@ -3085,7 +3283,7 @@ var BottomSheetCore = class {
3085
3283
  this.snaps.recompute();
3086
3284
  this.maxHeight.clampTo();
3087
3285
  this.scrim.invalidateOpacityCache();
3088
- if (this.gesture?.isDragging) return;
3286
+ if (this.isDraggingAny()) return;
3089
3287
  this.healActiveSnap();
3090
3288
  }
3091
3289
  healActiveSnap() {
@@ -3107,6 +3305,8 @@ var BottomSheetCore = class {
3107
3305
  this.bus.clear();
3108
3306
  this.animation.cancel();
3109
3307
  this.gesture?.forceClearDragState();
3308
+ this.contentGesture?.forceClearDragState();
3309
+ this.setRestClosed(false);
3110
3310
  this.teardowns.drain();
3111
3311
  this.scrim.destroy();
3112
3312
  this.animation.destroy();
@@ -3219,11 +3419,12 @@ var BottomSheetCore = class {
3219
3419
  const scale = Math.max(1 - depth * 0.04, 0.86);
3220
3420
  this.element.style.scale = scale === 1 ? "" : String(scale);
3221
3421
  }
3222
- attach() {
3223
- this.gesture = new GestureController({
3224
- handle: this.handle,
3422
+ buildGestureDeps(surface, gestureOptions) {
3423
+ return {
3424
+ handle: surface,
3225
3425
  element: this.element,
3226
3426
  mode: this.mode,
3427
+ gestureOptions,
3227
3428
  getRoot: () => this.rootEl,
3228
3429
  getDragContext: () => {
3229
3430
  const buf = this.dragContextBuf;
@@ -3242,8 +3443,90 @@ var BottomSheetCore = class {
3242
3443
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3243
3444
  emit: (event, payload) => this.emit(event, payload),
3244
3445
  listenerCount: (event) => this.bus.listenerCount(event)
3446
+ };
3447
+ }
3448
+ isContentDragAllowed() {
3449
+ if (this.disableDragFlag) return false;
3450
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3451
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3452
+ }
3453
+ decideContentDrag(surface, e, delta, crossDelta) {
3454
+ if (this.destroyed) return false;
3455
+ if (e.pointerType === "mouse") return false;
3456
+ if (this.gesture?.isDragging) return false;
3457
+ if (!this.isContentDragAllowed()) return false;
3458
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3459
+ const decision = decideContentGesture({
3460
+ delta,
3461
+ crossDelta,
3462
+ sharesScrollAxis: this.mode === "bottom" || this.mode === "top",
3463
+ scrollTop: surface.scrollTop,
3464
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3465
+ });
3466
+ if (decision === "pending") return null;
3467
+ return decision === "drag";
3468
+ }
3469
+ attachDragSurface(surface, kind) {
3470
+ if (this.destroyed || kind !== "content") return;
3471
+ if (this.contentGesture) return;
3472
+ const controller = new GestureController(
3473
+ this.buildGestureDeps(surface, {
3474
+ manageTouchAction: false,
3475
+ deferStart: (e, delta, crossDelta) => this.decideContentDrag(surface, e, delta, crossDelta)
3476
+ })
3477
+ );
3478
+ this.contentGesture = controller;
3479
+ const detachGestures = controller.install();
3480
+ const detachGuard = installTouchScrollGuard(
3481
+ surface,
3482
+ () => controller.isDragging
3483
+ );
3484
+ return () => {
3485
+ detachGuard();
3486
+ detachGestures();
3487
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3488
+ };
3489
+ }
3490
+ sheetDragSurface() {
3491
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3492
+ }
3493
+ canStartSheetDrag(e) {
3494
+ if (this.contentGesture?.isDragging) return false;
3495
+ const target = e.target;
3496
+ const scoped = typeof target?.closest === "function";
3497
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3498
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3499
+ return true;
3500
+ }
3501
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3502
+ if (this.dragFromMode === "handle") return true;
3503
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3504
+ return false;
3505
+ }
3506
+ return true;
3507
+ }
3508
+ mountSheetGesture() {
3509
+ const surface = this.sheetDragSurface();
3510
+ const controller = new GestureController(
3511
+ this.buildGestureDeps(surface, {
3512
+ shouldStart: (e) => this.canStartSheetDrag(e),
3513
+ manageTouchAction: this.dragFromMode === "handle"
3514
+ })
3515
+ );
3516
+ this.gesture = controller;
3517
+ const detachGestures = controller.install();
3518
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3519
+ this.detachSheetGesture = () => {
3520
+ detachGuard?.();
3521
+ detachGestures();
3522
+ };
3523
+ }
3524
+ attach() {
3525
+ this.mountSheetGesture();
3526
+ this.teardowns.add(() => {
3527
+ this.detachSheetGesture?.();
3528
+ this.detachSheetGesture = null;
3245
3529
  });
3246
- this.teardowns.add(this.gesture.install());
3247
3530
  try {
3248
3531
  this.attachFeatures();
3249
3532
  } catch (err) {
@@ -3280,7 +3563,7 @@ var BottomSheetCore = class {
3280
3563
  element: this.element,
3281
3564
  getMode: () => this.mode,
3282
3565
  isDestroyed: () => this.destroyed,
3283
- isDragging: () => this.gesture?.isDragging ?? false,
3566
+ isDragging: () => this.isDraggingAny(),
3284
3567
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3285
3568
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3286
3569
  getSize: () => this.size,
@@ -3315,7 +3598,11 @@ var BottomSheetCore = class {
3315
3598
  );
3316
3599
  }
3317
3600
  }
3318
- this.teardowns.add(installSliderKeyboard({
3601
+ this.mountSliderKeyboard();
3602
+ this.scrim.attach();
3603
+ }
3604
+ mountSliderKeyboard() {
3605
+ const detach = installSliderKeyboard({
3319
3606
  handle: this.handle,
3320
3607
  mode: this.mode,
3321
3608
  isDestroyed: () => this.destroyed,
@@ -3324,8 +3611,12 @@ var BottomSheetCore = class {
3324
3611
  snapTo: (id) => {
3325
3612
  void this.snapTo(id);
3326
3613
  }
3327
- }));
3328
- this.scrim.attach();
3614
+ });
3615
+ this.detachSliderKeyboard = detach;
3616
+ this.teardowns.add(() => {
3617
+ this.detachSliderKeyboard?.();
3618
+ this.detachSliderKeyboard = null;
3619
+ });
3329
3620
  }
3330
3621
  settleAfterDrag(delta, velocity, kind = "touch") {
3331
3622
  const signal = this.newCycle();
@@ -3378,8 +3669,11 @@ var BottomSheetCore = class {
3378
3669
  );
3379
3670
  });
3380
3671
  }
3672
+ isDraggingAny() {
3673
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3674
+ }
3381
3675
  clearWillChangeIfIdle() {
3382
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3676
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3383
3677
  this.element.style.willChange = "";
3384
3678
  }
3385
3679
  haptic() {
@@ -3392,8 +3686,9 @@ var BottomSheetCore = class {
3392
3686
  }
3393
3687
  applySize(size, skipTransform = false) {
3394
3688
  const cap = this.snaps.getMaxAxisSize();
3395
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3689
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3396
3690
  this.size = clamped;
3691
+ if (clamped > 0) this.setRestClosed(false);
3397
3692
  const offset = cap - clamped;
3398
3693
  const style = this.element.style;
3399
3694
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3773,50 +4068,10 @@ function installAutoCollapse(deps) {
3773
4068
  };
3774
4069
  }
3775
4070
 
3776
- // src/core/features/content-swipe.ts
3777
- var CONTENT_PULL_THRESHOLD = 60;
3778
- function installContentSwipe(deps) {
3779
- const { container } = deps;
3780
- let startY = 0;
3781
- let startScrollTop = 0;
3782
- let moved = false;
3783
- const onTouchStart = (e) => {
3784
- const t = e.touches[0];
3785
- if (!t) return;
3786
- startY = t.clientY;
3787
- startScrollTop = container.scrollTop;
3788
- moved = false;
3789
- };
3790
- const onTouchMove = (e) => {
3791
- const t = e.touches[0];
3792
- if (!t) return;
3793
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3794
- };
3795
- const onTouchEnd = (e) => {
3796
- if (!moved) return;
3797
- if (deps.isDragging() || deps.isAnimating()) return;
3798
- if (container.scrollTop !== startScrollTop) return;
3799
- const t = e.changedTouches[0];
3800
- if (!t) return;
3801
- const deltaY = t.clientY - startY;
3802
- const atTop = container.scrollTop <= 0;
3803
- const allowed = deps.getAllowedIds();
3804
- const idx = allowed.indexOf(deps.getActiveId());
3805
- if (idx === -1) return;
3806
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3807
- deps.snapTo(allowed[idx - 1]);
3808
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3809
- deps.snapTo(allowed[idx + 1]);
3810
- }
3811
- };
3812
- container.addEventListener("touchstart", onTouchStart, { passive: true });
3813
- container.addEventListener("touchmove", onTouchMove, { passive: true });
3814
- container.addEventListener("touchend", onTouchEnd, { passive: true });
3815
- return () => {
3816
- container.removeEventListener("touchstart", onTouchStart);
3817
- container.removeEventListener("touchmove", onTouchMove);
3818
- container.removeEventListener("touchend", onTouchEnd);
3819
- };
4071
+ // src/core/features/content-drag.ts
4072
+ function installContentDrag(deps) {
4073
+ const detach = deps.attachDragSurface(deps.container, "content");
4074
+ return () => detach?.();
3820
4075
  }
3821
4076
 
3822
4077
  // src/core/features/visual-viewport.ts
@@ -3924,13 +4179,9 @@ function contentSwipeFeature() {
3924
4179
  stage: "attach",
3925
4180
  install: (ctx) => {
3926
4181
  if (!ctx.scrollContainer) return;
3927
- return installContentSwipe({
4182
+ return installContentDrag({
3928
4183
  container: ctx.scrollContainer,
3929
- isDragging: ctx.isDragging,
3930
- isAnimating: ctx.isAnimating,
3931
- getAllowedIds: ctx.allowedIdsBySize,
3932
- getActiveId: ctx.getActiveId,
3933
- snapTo: ctx.snapTo
4184
+ attachDragSurface: ctx.attachDragSurface
3934
4185
  });
3935
4186
  }
3936
4187
  };
@@ -4056,6 +4307,8 @@ var BottomSheet = (props) => {
4056
4307
  persistent: props.persistent,
4057
4308
  disableClose: props.disableClose,
4058
4309
  disableDrag: props.disableDrag,
4310
+ dragFrom: props.dragFrom,
4311
+ dragFromContent: props.dragFromContent,
4059
4312
  closeOnRouteChange: props.closeOnRouteChange,
4060
4313
  stackEffect: props.stackEffect,
4061
4314
  returnFocusTo: props.returnFocusTo
@@ -4168,6 +4421,26 @@ var BottomSheet = (props) => {
4168
4421
  { defer: true }
4169
4422
  )
4170
4423
  );
4424
+ createEffect(
4425
+ on(
4426
+ () => props.dragFromContent,
4427
+ (value, prev) => {
4428
+ if (!engine || prev === void 0 || value === void 0) return;
4429
+ engine.setDragFromContent(value);
4430
+ },
4431
+ { defer: true }
4432
+ )
4433
+ );
4434
+ createEffect(
4435
+ on(
4436
+ () => props.dragFrom,
4437
+ (value, prev) => {
4438
+ if (!engine || prev === void 0 || value === void 0) return;
4439
+ engine.setDragFrom(value);
4440
+ },
4441
+ { defer: true }
4442
+ )
4443
+ );
4171
4444
  const showBackdrop = createMemo(() => props.backdrop !== false);
4172
4445
  const mode = createMemo(() => props.mode ?? "bottom");
4173
4446
  const ariaLabel = createMemo(() => props.ariaLabel ?? "Bottom sheet");