@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
@@ -302,6 +302,11 @@ class SnapResolver {
302
302
  this.raw = raw;
303
303
  this.recompute();
304
304
  }
305
+ setMode(mode) {
306
+ if (mode === this.mode) return;
307
+ this.mode = mode;
308
+ this.recompute();
309
+ }
305
310
  setAllowedIds(ids) {
306
311
  this.allowedIds = ids;
307
312
  this.rangeCache = null;
@@ -904,6 +909,11 @@ class ScrimController {
904
909
  interactive
905
910
  });
906
911
  }
912
+ setSheetMode(mode) {
913
+ if (this.isDestroyed() || mode === this.mode) return;
914
+ this.mode = mode;
915
+ this.invalidateOpacityCache();
916
+ }
907
917
  setScrimMode(mode, applySize) {
908
918
  if (this.isDestroyed()) return;
909
919
  if (mode === this.scrimMode) return;
@@ -1834,30 +1844,47 @@ class SampleRing {
1834
1844
  this.size = 0;
1835
1845
  }
1836
1846
  }
1837
- const installGestures = (handle, mode, callbacks) => {
1847
+ const installGestures = (handle, mode, callbacks, options = {}) => {
1838
1848
  let activePointerId = null;
1839
1849
  let activePointerType = "touch";
1840
1850
  let startCoord = 0;
1851
+ let startCross = 0;
1841
1852
  let lastCoord = 0;
1853
+ let pendingStart = false;
1842
1854
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1843
1855
  const MAX_SAMPLES = 32;
1844
1856
  const samples = new SampleRing(MAX_SAMPLES);
1845
1857
  const axis = isAxisVertical(mode) ? "Y" : "X";
1846
1858
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1847
1859
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1860
+ const crossOf = (e) => axis === "Y" ? e.clientX : e.clientY;
1861
+ const capture = (pointerId) => {
1862
+ try {
1863
+ handle.setPointerCapture(pointerId);
1864
+ } catch {
1865
+ }
1866
+ };
1867
+ const releaseTracking = () => {
1868
+ activePointerId = null;
1869
+ pendingStart = false;
1870
+ samples.reset();
1871
+ };
1848
1872
  const onPointerDown = (e) => {
1849
1873
  if (activePointerId !== null) return;
1850
1874
  if (e.button !== void 0 && e.button !== 0) return;
1875
+ if (options.shouldStart && !options.shouldStart(e)) return;
1851
1876
  activePointerId = e.pointerId;
1852
1877
  activePointerType = e.pointerType || "touch";
1853
1878
  startCoord = coordOf(e);
1879
+ startCross = crossOf(e);
1854
1880
  lastCoord = startCoord;
1855
1881
  samples.reset();
1856
1882
  samples.push(e.timeStamp, startCoord);
1857
- try {
1858
- handle.setPointerCapture(e.pointerId);
1859
- } catch {
1883
+ if (options.deferStart) {
1884
+ pendingStart = true;
1885
+ return;
1860
1886
  }
1887
+ capture(e.pointerId);
1861
1888
  callbacks.onStart(startCoord, activePointerType);
1862
1889
  };
1863
1890
  const onPointerMove = (e) => {
@@ -1865,6 +1892,22 @@ const installGestures = (handle, mode, callbacks) => {
1865
1892
  const coord = coordOf(e);
1866
1893
  const rawDelta = coord - startCoord;
1867
1894
  const delta = rawDelta * sign;
1895
+ if (pendingStart) {
1896
+ const verdict = options.deferStart(e, delta, crossOf(e) - startCross);
1897
+ if (verdict === false) {
1898
+ releaseTracking();
1899
+ return;
1900
+ }
1901
+ if (verdict !== true) return;
1902
+ pendingStart = false;
1903
+ startCoord = coord;
1904
+ lastCoord = coord;
1905
+ samples.reset();
1906
+ samples.push(e.timeStamp, coord);
1907
+ capture(e.pointerId);
1908
+ callbacks.onStart(startCoord, activePointerType);
1909
+ return;
1910
+ }
1868
1911
  lastCoord = coord;
1869
1912
  samples.push(e.timeStamp, coord);
1870
1913
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1873,6 +1916,10 @@ const installGestures = (handle, mode, callbacks) => {
1873
1916
  };
1874
1917
  const finishGesture = (e) => {
1875
1918
  if (e.pointerId !== activePointerId) return;
1919
+ if (pendingStart) {
1920
+ releaseTracking();
1921
+ return;
1922
+ }
1876
1923
  const rawDelta = lastCoord - startCoord;
1877
1924
  const delta = rawDelta * sign;
1878
1925
  let velocity = 0;
@@ -1895,22 +1942,26 @@ const installGestures = (handle, mode, callbacks) => {
1895
1942
  };
1896
1943
  const onPointerCancel = (e) => {
1897
1944
  if (e.pointerId !== activePointerId) return;
1898
- activePointerId = null;
1899
- samples.reset();
1945
+ const wasPending = pendingStart;
1946
+ releaseTracking();
1947
+ if (wasPending) return;
1900
1948
  callbacks.onCancel?.();
1901
1949
  };
1902
1950
  handle.addEventListener("pointerdown", onPointerDown);
1903
1951
  handle.addEventListener("pointermove", onPointerMove);
1904
1952
  handle.addEventListener("pointerup", finishGesture);
1905
1953
  handle.addEventListener("pointercancel", onPointerCancel);
1954
+ const managesTouchAction = options.manageTouchAction !== false;
1906
1955
  const prevTouchAction = handle.style.touchAction;
1907
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1956
+ if (managesTouchAction) {
1957
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1958
+ }
1908
1959
  return () => {
1909
1960
  handle.removeEventListener("pointerdown", onPointerDown);
1910
1961
  handle.removeEventListener("pointermove", onPointerMove);
1911
1962
  handle.removeEventListener("pointerup", finishGesture);
1912
1963
  handle.removeEventListener("pointercancel", onPointerCancel);
1913
- handle.style.touchAction = prevTouchAction;
1964
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1914
1965
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1915
1966
  handle.releasePointerCapture(activePointerId);
1916
1967
  }
@@ -2028,7 +2079,7 @@ class GestureController {
2028
2079
  const restoreTo = this.dragStartSize;
2029
2080
  void this.deps.animateTo(restoreTo, 0);
2030
2081
  }
2031
- });
2082
+ }, this.deps.gestureOptions);
2032
2083
  return this.detach;
2033
2084
  }
2034
2085
  forceClearDragState() {
@@ -2039,6 +2090,68 @@ class GestureController {
2039
2090
  this.deps.element.style.willChange = "";
2040
2091
  }
2041
2092
  }
2093
+ const DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2094
+ const NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2095
+ const asElement = (target) => {
2096
+ const candidate = target;
2097
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2098
+ };
2099
+ function isDragAllowedFrom(target, mode) {
2100
+ const el = asElement(target);
2101
+ if (!el) return mode !== "zones";
2102
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2103
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2104
+ return true;
2105
+ }
2106
+ const CONTENT_DRAG_SLOP = 6;
2107
+ function decideContentGesture(input) {
2108
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2109
+ const cross = input.crossDelta ?? 0;
2110
+ const sharesScrollAxis = input.sharesScrollAxis ?? true;
2111
+ if (!sharesScrollAxis) {
2112
+ if (Math.abs(input.delta) < slop && Math.abs(cross) < slop) return "pending";
2113
+ if (Math.abs(input.delta) <= Math.abs(cross)) return "scroll";
2114
+ return "drag";
2115
+ }
2116
+ if (Math.abs(input.delta) < slop) return "pending";
2117
+ if (input.scrollTop > 0) return "scroll";
2118
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2119
+ return "drag";
2120
+ }
2121
+ const isLogicalMode = (mode) => mode === "start" || mode === "end";
2122
+ const readDirection = (el) => {
2123
+ if (!el) return "ltr";
2124
+ if (typeof getComputedStyle === "function") {
2125
+ try {
2126
+ if (getComputedStyle(el).direction === "rtl") return "rtl";
2127
+ } catch {
2128
+ }
2129
+ }
2130
+ try {
2131
+ const marked = el.closest?.("[dir]");
2132
+ if (marked) {
2133
+ return marked.getAttribute("dir")?.toLowerCase() === "rtl" ? "rtl" : "ltr";
2134
+ }
2135
+ } catch {
2136
+ }
2137
+ return "ltr";
2138
+ };
2139
+ const resolveMode = (mode, el) => {
2140
+ if (!isLogicalMode(mode)) return mode;
2141
+ const rtl = readDirection(el) === "rtl";
2142
+ if (mode === "start") return rtl ? "right" : "left";
2143
+ return rtl ? "left" : "right";
2144
+ };
2145
+ function installTouchScrollGuard(surface, isDragging) {
2146
+ const onTouchMove = (e) => {
2147
+ if (!isDragging()) return;
2148
+ if (e.cancelable) e.preventDefault();
2149
+ };
2150
+ surface.addEventListener("touchmove", onTouchMove, {
2151
+ passive: false
2152
+ });
2153
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2154
+ }
2042
2155
  class AriaSliderWriter {
2043
2156
  constructor(handle, mode) {
2044
2157
  __publicField(this, "handle");
@@ -2429,9 +2542,11 @@ function resolveEngineOptions(opts, extras) {
2429
2542
  }
2430
2543
  }
2431
2544
  return {
2432
- mode: opts.mode ?? "bottom",
2545
+ mode: resolveMode(opts.mode ?? "bottom", opts.element),
2433
2546
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2434
2547
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2548
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2549
+ dragFromContent: opts.dragFromContent ?? true,
2435
2550
  rubberBandEnabled: opts.rubberBand ?? true,
2436
2551
  closeOnBack: opts.closeOnBack ?? false,
2437
2552
  persistent: opts.persistent ?? false,
@@ -2513,6 +2628,8 @@ class BottomSheetCore {
2513
2628
  __publicField(this, "mode");
2514
2629
  __publicField(this, "flickVelocity");
2515
2630
  __publicField(this, "dragThreshold");
2631
+ __publicField(this, "dragFromMode");
2632
+ __publicField(this, "dragFromContentDefault");
2516
2633
  __publicField(this, "rubberBandEnabled");
2517
2634
  __publicField(this, "scrim");
2518
2635
  __publicField(this, "aria");
@@ -2525,13 +2642,18 @@ class BottomSheetCore {
2525
2642
  __publicField(this, "disableDragFlag");
2526
2643
  __publicField(this, "closeOnRouteChange");
2527
2644
  __publicField(this, "maxHeight");
2645
+ __publicField(this, "maxHeightDeps");
2528
2646
  __publicField(this, "snapPointsRaw");
2529
2647
  __publicField(this, "snaps");
2530
2648
  __publicField(this, "activeId");
2531
2649
  __publicField(this, "size", 0);
2532
2650
  __publicField(this, "gesture");
2651
+ __publicField(this, "contentGesture");
2652
+ __publicField(this, "detachSheetGesture", null);
2653
+ __publicField(this, "detachSliderKeyboard", null);
2533
2654
  __publicField(this, "rootEl", null);
2534
2655
  __publicField(this, "destroyed", false);
2656
+ __publicField(this, "restClosed", false);
2535
2657
  __publicField(this, "currentViewTransition", null);
2536
2658
  __publicField(this, "currentAbort", new AbortController());
2537
2659
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2581,10 +2703,13 @@ class BottomSheetCore {
2581
2703
  this.screenComponent = opts.scrim ?? opts.screenComponent;
2582
2704
  this.scrimParent = this.screenComponent?.parentElement ?? null;
2583
2705
  this.mode = resolved.mode;
2706
+ this.element.dataset.mode = this.mode;
2584
2707
  this.transformTemplate = buildTransformTemplate(this.mode);
2585
2708
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2586
2709
  this.flickVelocity = resolved.flickVelocity;
2587
2710
  this.dragThreshold = resolved.dragThreshold;
2711
+ this.dragFromMode = resolved.dragFrom;
2712
+ this.dragFromContentDefault = resolved.dragFromContent;
2588
2713
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2589
2714
  this.scrim = new ScrimController({
2590
2715
  mode: this.mode,
@@ -2607,7 +2732,7 @@ class BottomSheetCore {
2607
2732
  getRootEl: () => this.rootEl,
2608
2733
  applySize: (size) => this.applySize(size),
2609
2734
  getSize: () => this.size,
2610
- isDragging: () => this.gesture?.isDragging ?? false,
2735
+ isDragging: () => this.isDraggingAny(),
2611
2736
  applyAux: (size) => this.applySize(size, true),
2612
2737
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2613
2738
  }, resolved.animation);
@@ -2636,7 +2761,7 @@ class BottomSheetCore {
2636
2761
  auditVhUsage(this.snapPointsRaw);
2637
2762
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2638
2763
  this.rootEl = this.element.closest(".bs-root");
2639
- this.maxHeight = createMaxHeightController({
2764
+ this.maxHeightDeps = {
2640
2765
  element: this.element,
2641
2766
  mode: this.mode,
2642
2767
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
@@ -2644,7 +2769,8 @@ class BottomSheetCore {
2644
2769
  this.snaps.setMaxAxisSize(size);
2645
2770
  },
2646
2771
  recompute: () => this.recompute()
2647
- });
2772
+ };
2773
+ this.maxHeight = createMaxHeightController(this.maxHeightDeps);
2648
2774
  this.fitDeps = {
2649
2775
  element: this.element,
2650
2776
  scrollContainer: this.scrollContainer,
@@ -2657,6 +2783,7 @@ class BottomSheetCore {
2657
2783
  const initial = this.snaps.findById(this.activeId);
2658
2784
  if (initial) this.size = initial.size;
2659
2785
  this.applySize(this.size);
2786
+ this.setRestClosed(this.size === 0);
2660
2787
  this.updateAriaSlider();
2661
2788
  this.scrollCache = createScrollCache({
2662
2789
  scrollContainer: this.scrollContainer,
@@ -2684,7 +2811,7 @@ class BottomSheetCore {
2684
2811
  scrollContainer: this.scrollContainer,
2685
2812
  hasFitSnap: () => this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"),
2686
2813
  isDestroyed: () => this.destroyed,
2687
- isDragging: () => this.gesture?.isDragging ?? false,
2814
+ isDragging: () => this.isDraggingAny(),
2688
2815
  recompute: () => this.recompute()
2689
2816
  }));
2690
2817
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
@@ -2709,7 +2836,7 @@ class BottomSheetCore {
2709
2836
  autoCollapseAfter
2710
2837
  },
2711
2838
  isDestroyed: () => this.destroyed,
2712
- isDragging: () => this.gesture?.isDragging ?? false,
2839
+ isDragging: () => this.isDraggingAny(),
2713
2840
  isAnimating: () => this.animation.isAnimating,
2714
2841
  isTopSheet: () => this.isTopSheet,
2715
2842
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2739,6 +2866,7 @@ class BottomSheetCore {
2739
2866
  void this.snapTo(id);
2740
2867
  },
2741
2868
  close: (reason) => this.close(reason),
2869
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2742
2870
  on: (event, fn) => this.on(event, fn),
2743
2871
  addTeardown: (fn) => this.teardowns.add(fn)
2744
2872
  };
@@ -2763,7 +2891,7 @@ class BottomSheetCore {
2763
2891
  return {
2764
2892
  size: this.size,
2765
2893
  activeId: this.activeId,
2766
- isDragging: this.gesture?.isDragging ?? false,
2894
+ isDragging: this.isDraggingAny(),
2767
2895
  isAnimating: this.animation.isAnimating,
2768
2896
  progress: this.computeProgress(this.size)
2769
2897
  };
@@ -2885,9 +3013,16 @@ class BottomSheetCore {
2885
3013
  progress: this.computeProgress(this.size)
2886
3014
  });
2887
3015
  if (withHaptic) this.haptic();
3016
+ this.setRestClosed(targetSize === 0);
2888
3017
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2889
3018
  if (targetSize === 0) this.emitCloseSequence();
2890
3019
  }
3020
+ setRestClosed(closed) {
3021
+ if (closed === this.restClosed) return;
3022
+ this.restClosed = closed;
3023
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
3024
+ else this.element.removeAttribute("data-bs-rest");
3025
+ }
2891
3026
  emitOpenSequence(id) {
2892
3027
  this.emit("open", {
2893
3028
  id
@@ -2951,6 +3086,51 @@ class BottomSheetCore {
2951
3086
  if (this.destroyed) return;
2952
3087
  this.disableDragFlag = value;
2953
3088
  }
3089
+ setDragFromContent(value) {
3090
+ if (this.destroyed) return;
3091
+ this.dragFromContentDefault = value;
3092
+ }
3093
+ setDragFrom(mode) {
3094
+ if (this.destroyed || mode === this.dragFromMode) return;
3095
+ this.gesture?.forceClearDragState();
3096
+ this.detachSheetGesture?.();
3097
+ this.dragFromMode = mode;
3098
+ this.mountSheetGesture();
3099
+ }
3100
+ getDragFrom() {
3101
+ return this.dragFromMode;
3102
+ }
3103
+ setMode(mode) {
3104
+ if (this.destroyed) return;
3105
+ const next = resolveMode(mode, this.element);
3106
+ if (next === this.mode) return;
3107
+ const wasVertical = this.mode === "bottom" || this.mode === "top";
3108
+ const nowVertical = next === "bottom" || next === "top";
3109
+ this.gesture?.forceClearDragState();
3110
+ this.detachSheetGesture?.();
3111
+ this.animation.cancel();
3112
+ this.element.style.removeProperty(layoutAxis(this.mode));
3113
+ this.mode = next;
3114
+ this.element.dataset.mode = next;
3115
+ this.transformTemplate = buildTransformTemplate(next);
3116
+ this.maxHeightDeps.mode = next;
3117
+ this.fitDeps.mode = next;
3118
+ this.snaps.setMode(next);
3119
+ this.scrim.setSheetMode(next);
3120
+ if (wasVertical !== nowVertical) {
3121
+ this.aria = new AriaSliderWriter(this.handle, next);
3122
+ }
3123
+ this.detachSliderKeyboard?.();
3124
+ this.mountSliderKeyboard();
3125
+ this.mountSheetGesture();
3126
+ this.recompute();
3127
+ const active = this.snaps.findById(this.activeId);
3128
+ if (active) this.applySize(active.size);
3129
+ this.updateAriaSlider();
3130
+ }
3131
+ getMode() {
3132
+ return this.mode;
3133
+ }
2954
3134
  isTop() {
2955
3135
  return this.isTopSheet;
2956
3136
  }
@@ -3097,12 +3277,24 @@ class BottomSheetCore {
3097
3277
  auditVhUsage(points);
3098
3278
  this.auditDuplicateSnapIds(points);
3099
3279
  this.snaps.setRaw(points);
3100
- if (allowed) this.snaps.setAllowedIds(allowed);
3280
+ if (allowed) {
3281
+ this.snaps.setAllowedIds(allowed);
3282
+ } else {
3283
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3284
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3285
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3286
+ }
3101
3287
  this.scrollCache.clear();
3102
3288
  this.scrim.invalidateOpacityCache();
3103
3289
  const current = this.snaps.findById(this.activeId);
3104
3290
  if (current) {
3105
3291
  this.applySize(current.size);
3292
+ } else {
3293
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3294
+ if (fallback) {
3295
+ this.activeId = fallback.id;
3296
+ this.applySize(fallback.size);
3297
+ }
3106
3298
  }
3107
3299
  this.updateAriaSlider();
3108
3300
  }
@@ -3112,7 +3304,7 @@ class BottomSheetCore {
3112
3304
  this.snaps.recompute();
3113
3305
  this.maxHeight.clampTo();
3114
3306
  this.scrim.invalidateOpacityCache();
3115
- if (this.gesture?.isDragging) return;
3307
+ if (this.isDraggingAny()) return;
3116
3308
  this.healActiveSnap();
3117
3309
  }
3118
3310
  healActiveSnap() {
@@ -3134,6 +3326,8 @@ class BottomSheetCore {
3134
3326
  this.bus.clear();
3135
3327
  this.animation.cancel();
3136
3328
  this.gesture?.forceClearDragState();
3329
+ this.contentGesture?.forceClearDragState();
3330
+ this.setRestClosed(false);
3137
3331
  this.teardowns.drain();
3138
3332
  this.scrim.destroy();
3139
3333
  this.animation.destroy();
@@ -3244,11 +3438,12 @@ class BottomSheetCore {
3244
3438
  const scale = Math.max(1 - depth * 0.04, 0.86);
3245
3439
  this.element.style.scale = scale === 1 ? "" : String(scale);
3246
3440
  }
3247
- attach() {
3248
- this.gesture = new GestureController({
3249
- handle: this.handle,
3441
+ buildGestureDeps(surface, gestureOptions) {
3442
+ return {
3443
+ handle: surface,
3250
3444
  element: this.element,
3251
3445
  mode: this.mode,
3446
+ gestureOptions,
3252
3447
  getRoot: () => this.rootEl,
3253
3448
  getDragContext: () => {
3254
3449
  const buf = this.dragContextBuf;
@@ -3267,8 +3462,83 @@ class BottomSheetCore {
3267
3462
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3268
3463
  emit: (event, payload) => this.emit(event, payload),
3269
3464
  listenerCount: (event) => this.bus.listenerCount(event)
3465
+ };
3466
+ }
3467
+ isContentDragAllowed() {
3468
+ if (this.disableDragFlag) return false;
3469
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3470
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3471
+ }
3472
+ decideContentDrag(surface, e, delta, crossDelta) {
3473
+ if (this.destroyed) return false;
3474
+ if (e.pointerType === "mouse") return false;
3475
+ if (this.gesture?.isDragging) return false;
3476
+ if (!this.isContentDragAllowed()) return false;
3477
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3478
+ const decision = decideContentGesture({
3479
+ delta,
3480
+ crossDelta,
3481
+ sharesScrollAxis: this.mode === "bottom" || this.mode === "top",
3482
+ scrollTop: surface.scrollTop,
3483
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3484
+ });
3485
+ if (decision === "pending") return null;
3486
+ return decision === "drag";
3487
+ }
3488
+ attachDragSurface(surface, kind) {
3489
+ if (this.destroyed || kind !== "content") return;
3490
+ if (this.contentGesture) return;
3491
+ const controller = new GestureController(this.buildGestureDeps(surface, {
3492
+ manageTouchAction: false,
3493
+ deferStart: (e, delta, crossDelta) => this.decideContentDrag(surface, e, delta, crossDelta)
3494
+ }));
3495
+ this.contentGesture = controller;
3496
+ const detachGestures = controller.install();
3497
+ const detachGuard = installTouchScrollGuard(surface, () => controller.isDragging);
3498
+ return () => {
3499
+ detachGuard();
3500
+ detachGestures();
3501
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3502
+ };
3503
+ }
3504
+ sheetDragSurface() {
3505
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3506
+ }
3507
+ canStartSheetDrag(e) {
3508
+ if (this.contentGesture?.isDragging) return false;
3509
+ const target = e.target;
3510
+ const scoped = typeof target?.closest === "function";
3511
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3512
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3513
+ return true;
3514
+ }
3515
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3516
+ if (this.dragFromMode === "handle") return true;
3517
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3518
+ return false;
3519
+ }
3520
+ return true;
3521
+ }
3522
+ mountSheetGesture() {
3523
+ const surface = this.sheetDragSurface();
3524
+ const controller = new GestureController(this.buildGestureDeps(surface, {
3525
+ shouldStart: (e) => this.canStartSheetDrag(e),
3526
+ manageTouchAction: this.dragFromMode === "handle"
3527
+ }));
3528
+ this.gesture = controller;
3529
+ const detachGestures = controller.install();
3530
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3531
+ this.detachSheetGesture = () => {
3532
+ detachGuard?.();
3533
+ detachGestures();
3534
+ };
3535
+ }
3536
+ attach() {
3537
+ this.mountSheetGesture();
3538
+ this.teardowns.add(() => {
3539
+ this.detachSheetGesture?.();
3540
+ this.detachSheetGesture = null;
3270
3541
  });
3271
- this.teardowns.add(this.gesture.install());
3272
3542
  try {
3273
3543
  this.attachFeatures();
3274
3544
  } catch (err) {
@@ -3305,7 +3575,7 @@ class BottomSheetCore {
3305
3575
  element: this.element,
3306
3576
  getMode: () => this.mode,
3307
3577
  isDestroyed: () => this.destroyed,
3308
- isDragging: () => this.gesture?.isDragging ?? false,
3578
+ isDragging: () => this.isDraggingAny(),
3309
3579
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3310
3580
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3311
3581
  getSize: () => this.size,
@@ -3338,7 +3608,11 @@ class BottomSheetCore {
3338
3608
  this.teardowns.add(() => document.removeEventListener("keydown", onKey));
3339
3609
  }
3340
3610
  }
3341
- this.teardowns.add(installSliderKeyboard({
3611
+ this.mountSliderKeyboard();
3612
+ this.scrim.attach();
3613
+ }
3614
+ mountSliderKeyboard() {
3615
+ const detach = installSliderKeyboard({
3342
3616
  handle: this.handle,
3343
3617
  mode: this.mode,
3344
3618
  isDestroyed: () => this.destroyed,
@@ -3347,8 +3621,12 @@ class BottomSheetCore {
3347
3621
  snapTo: (id) => {
3348
3622
  void this.snapTo(id);
3349
3623
  }
3350
- }));
3351
- this.scrim.attach();
3624
+ });
3625
+ this.detachSliderKeyboard = detach;
3626
+ this.teardowns.add(() => {
3627
+ this.detachSliderKeyboard?.();
3628
+ this.detachSliderKeyboard = null;
3629
+ });
3352
3630
  }
3353
3631
  settleAfterDrag(delta, velocity, kind = "touch") {
3354
3632
  const signal = this.newCycle();
@@ -3395,8 +3673,11 @@ class BottomSheetCore {
3395
3673
  this.completeSnap(target.id, target.size, previousSize, previousSnapSize === 0, true);
3396
3674
  });
3397
3675
  }
3676
+ isDraggingAny() {
3677
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3678
+ }
3398
3679
  clearWillChangeIfIdle() {
3399
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3680
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3400
3681
  this.element.style.willChange = "";
3401
3682
  }
3402
3683
  haptic() {
@@ -3409,8 +3690,9 @@ class BottomSheetCore {
3409
3690
  }
3410
3691
  applySize(size, skipTransform = false) {
3411
3692
  const cap = this.snaps.getMaxAxisSize();
3412
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3693
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3413
3694
  this.size = clamped;
3695
+ if (clamped > 0) this.setRestClosed(false);
3414
3696
  const offset = cap - clamped;
3415
3697
  const style = this.element.style;
3416
3698
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3782,55 +4064,9 @@ function installAutoCollapse(deps) {
3782
4064
  unsubscribe();
3783
4065
  };
3784
4066
  }
3785
- const CONTENT_PULL_THRESHOLD = 60;
3786
- function installContentSwipe(deps) {
3787
- const { container } = deps;
3788
- let startY = 0;
3789
- let startScrollTop = 0;
3790
- let moved = false;
3791
- const onTouchStart = (e) => {
3792
- const t = e.touches[0];
3793
- if (!t) return;
3794
- startY = t.clientY;
3795
- startScrollTop = container.scrollTop;
3796
- moved = false;
3797
- };
3798
- const onTouchMove = (e) => {
3799
- const t = e.touches[0];
3800
- if (!t) return;
3801
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3802
- };
3803
- const onTouchEnd = (e) => {
3804
- if (!moved) return;
3805
- if (deps.isDragging() || deps.isAnimating()) return;
3806
- if (container.scrollTop !== startScrollTop) return;
3807
- const t = e.changedTouches[0];
3808
- if (!t) return;
3809
- const deltaY = t.clientY - startY;
3810
- const atTop = container.scrollTop <= 0;
3811
- const allowed = deps.getAllowedIds();
3812
- const idx = allowed.indexOf(deps.getActiveId());
3813
- if (idx === -1) return;
3814
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3815
- deps.snapTo(allowed[idx - 1]);
3816
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3817
- deps.snapTo(allowed[idx + 1]);
3818
- }
3819
- };
3820
- container.addEventListener("touchstart", onTouchStart, {
3821
- passive: true
3822
- });
3823
- container.addEventListener("touchmove", onTouchMove, {
3824
- passive: true
3825
- });
3826
- container.addEventListener("touchend", onTouchEnd, {
3827
- passive: true
3828
- });
3829
- return () => {
3830
- container.removeEventListener("touchstart", onTouchStart);
3831
- container.removeEventListener("touchmove", onTouchMove);
3832
- container.removeEventListener("touchend", onTouchEnd);
3833
- };
4067
+ function installContentDrag(deps) {
4068
+ const detach = deps.attachDragSurface(deps.container, "content");
4069
+ return () => detach?.();
3834
4070
  }
3835
4071
  const SAFETY_PAD = 8;
3836
4072
  function installVisualViewport(deps) {
@@ -3932,13 +4168,9 @@ function contentSwipeFeature() {
3932
4168
  stage: "attach",
3933
4169
  install: (ctx) => {
3934
4170
  if (!ctx.scrollContainer) return;
3935
- return installContentSwipe({
4171
+ return installContentDrag({
3936
4172
  container: ctx.scrollContainer,
3937
- isDragging: ctx.isDragging,
3938
- isAnimating: ctx.isAnimating,
3939
- getAllowedIds: ctx.allowedIdsBySize,
3940
- getActiveId: ctx.getActiveId,
3941
- snapTo: ctx.snapTo
4173
+ attachDragSurface: ctx.attachDragSurface
3942
4174
  });
3943
4175
  }
3944
4176
  };
@@ -4069,6 +4301,8 @@ const BottomSheet = qwik.component$((props) => {
4069
4301
  persistent: props.persistent,
4070
4302
  disableClose: props.disableClose,
4071
4303
  disableDrag: props.disableDrag,
4304
+ dragFrom: props.dragFrom,
4305
+ dragFromContent: props.dragFromContent,
4072
4306
  closeOnRouteChange: props.closeOnRouteChange,
4073
4307
  stackEffect: props.stackEffect,
4074
4308
  returnFocusTo: props.returnFocusTo,
@@ -4176,11 +4410,17 @@ const BottomSheet = qwik.component$((props) => {
4176
4410
  const persistent = track(() => props.persistent);
4177
4411
  const disableClose = track(() => props.disableClose);
4178
4412
  const disableDrag = track(() => props.disableDrag);
4413
+ const dragFromContent = track(() => props.dragFromContent);
4414
+ const dragFrom = track(() => props.dragFrom);
4179
4415
  const engine = engineStore.engine;
4180
4416
  if (!engine) return;
4181
4417
  if (persistent !== void 0) engine.setPersistent(persistent);
4182
4418
  if (disableClose !== void 0) engine.setDisableClose(disableClose);
4183
4419
  if (disableDrag !== void 0) engine.setDisableDrag(disableDrag);
4420
+ if (dragFromContent !== void 0) {
4421
+ engine.setDragFromContent(dragFromContent);
4422
+ }
4423
+ if (dragFrom !== void 0) engine.setDragFrom(dragFrom);
4184
4424
  });
4185
4425
  const showBackdrop = props.backdrop !== false;
4186
4426
  const mode = props.mode ?? "bottom";