@surdeddd/bottom-sheet 0.13.0 → 0.14.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 +33 -0
  2. package/dist/{BottomSheetCore-CsIjyq7C.d.cts → BottomSheetCore-Cp7dWy91.d.cts} +26 -1
  3. package/dist/{BottomSheetCore-D16lsa6g.d.ts → BottomSheetCore-DJYwWOqt.d.ts} +26 -1
  4. package/dist/{BottomSheetEngine-BYjAVrmd.d.cts → BottomSheetEngine-Lm7HIVHv.d.cts} +1 -1
  5. package/dist/{BottomSheetEngine-YuCNugZn.d.ts → BottomSheetEngine-O5PbeKfe.d.ts} +1 -1
  6. package/dist/core.cjs +231 -27
  7. package/dist/core.d.cts +2 -2
  8. package/dist/core.d.ts +2 -2
  9. package/dist/core.js +231 -27
  10. package/dist/debug.d.cts +1 -1
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/element.cjs +300 -102
  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 +300 -102
  17. package/dist/features.cjs +6 -50
  18. package/dist/features.d.cts +2 -2
  19. package/dist/features.d.ts +2 -2
  20. package/dist/features.js +6 -50
  21. package/dist/index-Bb5TRmyg.d.cts +9 -0
  22. package/dist/index-Cl9ebCP2.d.ts +9 -0
  23. package/dist/index.cjs +238 -823
  24. package/dist/index.d.cts +42 -45
  25. package/dist/index.d.ts +42 -45
  26. package/dist/index.js +239 -810
  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 +228 -77
  36. package/dist/qwik-lib/index.qwik.mjs +228 -77
  37. package/dist/qwik.cjs +245 -77
  38. package/dist/qwik.d.cts +4 -2
  39. package/dist/qwik.d.ts +4 -2
  40. package/dist/qwik.js +245 -77
  41. package/dist/react.cjs +250 -79
  42. package/dist/react.d.cts +3 -4
  43. package/dist/react.d.ts +3 -4
  44. package/dist/react.js +250 -79
  45. package/dist/solid.cjs +251 -77
  46. package/dist/solid.d.cts +27 -0
  47. package/dist/solid.d.ts +27 -0
  48. package/dist/solid.js +251 -77
  49. package/dist/solid.source.jsx +259 -77
  50. package/dist/styles.css +24 -4
  51. package/dist/svelte-core.cjs +237 -77
  52. package/dist/svelte-core.js +237 -77
  53. package/dist/svelte-src/BottomSheet.svelte +14 -0
  54. package/dist/svelte.cjs +247 -77
  55. package/dist/svelte.d.cts +2 -0
  56. package/dist/svelte.d.ts +2 -0
  57. package/dist/svelte.js +247 -77
  58. package/dist/vue.cjs +253 -77
  59. package/dist/vue.js +253 -77
  60. package/dist/webgl.cjs +583 -0
  61. package/dist/webgl.d.cts +17 -0
  62. package/dist/webgl.d.ts +17 -0
  63. package/dist/webgl.js +581 -0
  64. package/package.json +36 -12
  65. package/dist/index-DdYLOIIT.d.ts +0 -15
  66. package/dist/index-dfrzw-lI.d.cts +0 -15
@@ -1832,30 +1832,44 @@ class SampleRing {
1832
1832
  this.size = 0;
1833
1833
  }
1834
1834
  }
1835
- const installGestures = (handle, mode, callbacks) => {
1835
+ const installGestures = (handle, mode, callbacks, options = {}) => {
1836
1836
  let activePointerId = null;
1837
1837
  let activePointerType = "touch";
1838
1838
  let startCoord = 0;
1839
1839
  let lastCoord = 0;
1840
+ let pendingStart = false;
1840
1841
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1841
1842
  const MAX_SAMPLES = 32;
1842
1843
  const samples = new SampleRing(MAX_SAMPLES);
1843
1844
  const axis = isAxisVertical(mode) ? "Y" : "X";
1844
1845
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1845
1846
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
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
+ };
1846
1858
  const onPointerDown = (e) => {
1847
1859
  if (activePointerId !== null) return;
1848
1860
  if (e.button !== void 0 && e.button !== 0) return;
1861
+ if (options.shouldStart && !options.shouldStart(e)) return;
1849
1862
  activePointerId = e.pointerId;
1850
1863
  activePointerType = e.pointerType || "touch";
1851
1864
  startCoord = coordOf(e);
1852
1865
  lastCoord = startCoord;
1853
1866
  samples.reset();
1854
1867
  samples.push(e.timeStamp, startCoord);
1855
- try {
1856
- handle.setPointerCapture(e.pointerId);
1857
- } catch {
1868
+ if (options.deferStart) {
1869
+ pendingStart = true;
1870
+ return;
1858
1871
  }
1872
+ capture(e.pointerId);
1859
1873
  callbacks.onStart(startCoord, activePointerType);
1860
1874
  };
1861
1875
  const onPointerMove = (e) => {
@@ -1863,6 +1877,22 @@ const installGestures = (handle, mode, callbacks) => {
1863
1877
  const coord = coordOf(e);
1864
1878
  const rawDelta = coord - startCoord;
1865
1879
  const delta = rawDelta * sign;
1880
+ if (pendingStart) {
1881
+ const verdict = options.deferStart(e, delta);
1882
+ if (verdict === false) {
1883
+ releaseTracking();
1884
+ return;
1885
+ }
1886
+ if (verdict !== true) return;
1887
+ pendingStart = false;
1888
+ startCoord = coord;
1889
+ lastCoord = coord;
1890
+ samples.reset();
1891
+ samples.push(e.timeStamp, coord);
1892
+ capture(e.pointerId);
1893
+ callbacks.onStart(startCoord, activePointerType);
1894
+ return;
1895
+ }
1866
1896
  lastCoord = coord;
1867
1897
  samples.push(e.timeStamp, coord);
1868
1898
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1871,6 +1901,10 @@ const installGestures = (handle, mode, callbacks) => {
1871
1901
  };
1872
1902
  const finishGesture = (e) => {
1873
1903
  if (e.pointerId !== activePointerId) return;
1904
+ if (pendingStart) {
1905
+ releaseTracking();
1906
+ return;
1907
+ }
1874
1908
  const rawDelta = lastCoord - startCoord;
1875
1909
  const delta = rawDelta * sign;
1876
1910
  let velocity = 0;
@@ -1893,22 +1927,26 @@ const installGestures = (handle, mode, callbacks) => {
1893
1927
  };
1894
1928
  const onPointerCancel = (e) => {
1895
1929
  if (e.pointerId !== activePointerId) return;
1896
- activePointerId = null;
1897
- samples.reset();
1930
+ const wasPending = pendingStart;
1931
+ releaseTracking();
1932
+ if (wasPending) return;
1898
1933
  callbacks.onCancel?.();
1899
1934
  };
1900
1935
  handle.addEventListener("pointerdown", onPointerDown);
1901
1936
  handle.addEventListener("pointermove", onPointerMove);
1902
1937
  handle.addEventListener("pointerup", finishGesture);
1903
1938
  handle.addEventListener("pointercancel", onPointerCancel);
1939
+ const managesTouchAction = options.manageTouchAction !== false;
1904
1940
  const prevTouchAction = handle.style.touchAction;
1905
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1941
+ if (managesTouchAction) {
1942
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1943
+ }
1906
1944
  return () => {
1907
1945
  handle.removeEventListener("pointerdown", onPointerDown);
1908
1946
  handle.removeEventListener("pointermove", onPointerMove);
1909
1947
  handle.removeEventListener("pointerup", finishGesture);
1910
1948
  handle.removeEventListener("pointercancel", onPointerCancel);
1911
- handle.style.touchAction = prevTouchAction;
1949
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1912
1950
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1913
1951
  handle.releasePointerCapture(activePointerId);
1914
1952
  }
@@ -2026,7 +2064,7 @@ class GestureController {
2026
2064
  const restoreTo = this.dragStartSize;
2027
2065
  void this.deps.animateTo(restoreTo, 0);
2028
2066
  }
2029
- });
2067
+ }, this.deps.gestureOptions);
2030
2068
  return this.detach;
2031
2069
  }
2032
2070
  forceClearDragState() {
@@ -2037,6 +2075,37 @@ class GestureController {
2037
2075
  this.deps.element.style.willChange = "";
2038
2076
  }
2039
2077
  }
2078
+ const DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2079
+ const NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2080
+ const asElement = (target) => {
2081
+ const candidate = target;
2082
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2083
+ };
2084
+ function isDragAllowedFrom(target, mode) {
2085
+ const el = asElement(target);
2086
+ if (!el) return mode !== "zones";
2087
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2088
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2089
+ return true;
2090
+ }
2091
+ const CONTENT_DRAG_SLOP = 6;
2092
+ function decideContentGesture(input) {
2093
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2094
+ if (Math.abs(input.delta) < slop) return "pending";
2095
+ if (input.scrollTop > 0) return "scroll";
2096
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2097
+ return "drag";
2098
+ }
2099
+ function installTouchScrollGuard(surface, isDragging) {
2100
+ const onTouchMove = (e) => {
2101
+ if (!isDragging()) return;
2102
+ if (e.cancelable) e.preventDefault();
2103
+ };
2104
+ surface.addEventListener("touchmove", onTouchMove, {
2105
+ passive: false
2106
+ });
2107
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2108
+ }
2040
2109
  class AriaSliderWriter {
2041
2110
  constructor(handle, mode) {
2042
2111
  __publicField(this, "handle");
@@ -2430,6 +2499,8 @@ function resolveEngineOptions(opts, extras) {
2430
2499
  mode: opts.mode ?? "bottom",
2431
2500
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2432
2501
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2502
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2503
+ dragFromContent: opts.dragFromContent ?? true,
2433
2504
  rubberBandEnabled: opts.rubberBand ?? true,
2434
2505
  closeOnBack: opts.closeOnBack ?? false,
2435
2506
  persistent: opts.persistent ?? false,
@@ -2511,6 +2582,8 @@ class BottomSheetCore {
2511
2582
  __publicField(this, "mode");
2512
2583
  __publicField(this, "flickVelocity");
2513
2584
  __publicField(this, "dragThreshold");
2585
+ __publicField(this, "dragFromMode");
2586
+ __publicField(this, "dragFromContentDefault");
2514
2587
  __publicField(this, "rubberBandEnabled");
2515
2588
  __publicField(this, "scrim");
2516
2589
  __publicField(this, "aria");
@@ -2528,8 +2601,11 @@ class BottomSheetCore {
2528
2601
  __publicField(this, "activeId");
2529
2602
  __publicField(this, "size", 0);
2530
2603
  __publicField(this, "gesture");
2604
+ __publicField(this, "contentGesture");
2605
+ __publicField(this, "detachSheetGesture", null);
2531
2606
  __publicField(this, "rootEl", null);
2532
2607
  __publicField(this, "destroyed", false);
2608
+ __publicField(this, "restClosed", false);
2533
2609
  __publicField(this, "currentViewTransition", null);
2534
2610
  __publicField(this, "currentAbort", new AbortController());
2535
2611
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2583,6 +2659,8 @@ class BottomSheetCore {
2583
2659
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2584
2660
  this.flickVelocity = resolved.flickVelocity;
2585
2661
  this.dragThreshold = resolved.dragThreshold;
2662
+ this.dragFromMode = resolved.dragFrom;
2663
+ this.dragFromContentDefault = resolved.dragFromContent;
2586
2664
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2587
2665
  this.scrim = new ScrimController({
2588
2666
  mode: this.mode,
@@ -2605,7 +2683,7 @@ class BottomSheetCore {
2605
2683
  getRootEl: () => this.rootEl,
2606
2684
  applySize: (size) => this.applySize(size),
2607
2685
  getSize: () => this.size,
2608
- isDragging: () => this.gesture?.isDragging ?? false,
2686
+ isDragging: () => this.isDraggingAny(),
2609
2687
  applyAux: (size) => this.applySize(size, true),
2610
2688
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2611
2689
  }, resolved.animation);
@@ -2655,6 +2733,7 @@ class BottomSheetCore {
2655
2733
  const initial = this.snaps.findById(this.activeId);
2656
2734
  if (initial) this.size = initial.size;
2657
2735
  this.applySize(this.size);
2736
+ this.setRestClosed(this.size === 0);
2658
2737
  this.updateAriaSlider();
2659
2738
  this.scrollCache = createScrollCache({
2660
2739
  scrollContainer: this.scrollContainer,
@@ -2682,7 +2761,7 @@ class BottomSheetCore {
2682
2761
  scrollContainer: this.scrollContainer,
2683
2762
  hasFitSnap: () => this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"),
2684
2763
  isDestroyed: () => this.destroyed,
2685
- isDragging: () => this.gesture?.isDragging ?? false,
2764
+ isDragging: () => this.isDraggingAny(),
2686
2765
  recompute: () => this.recompute()
2687
2766
  }));
2688
2767
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
@@ -2707,7 +2786,7 @@ class BottomSheetCore {
2707
2786
  autoCollapseAfter
2708
2787
  },
2709
2788
  isDestroyed: () => this.destroyed,
2710
- isDragging: () => this.gesture?.isDragging ?? false,
2789
+ isDragging: () => this.isDraggingAny(),
2711
2790
  isAnimating: () => this.animation.isAnimating,
2712
2791
  isTopSheet: () => this.isTopSheet,
2713
2792
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2737,6 +2816,7 @@ class BottomSheetCore {
2737
2816
  void this.snapTo(id);
2738
2817
  },
2739
2818
  close: (reason) => this.close(reason),
2819
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2740
2820
  on: (event, fn) => this.on(event, fn),
2741
2821
  addTeardown: (fn) => this.teardowns.add(fn)
2742
2822
  };
@@ -2761,7 +2841,7 @@ class BottomSheetCore {
2761
2841
  return {
2762
2842
  size: this.size,
2763
2843
  activeId: this.activeId,
2764
- isDragging: this.gesture?.isDragging ?? false,
2844
+ isDragging: this.isDraggingAny(),
2765
2845
  isAnimating: this.animation.isAnimating,
2766
2846
  progress: this.computeProgress(this.size)
2767
2847
  };
@@ -2883,9 +2963,16 @@ class BottomSheetCore {
2883
2963
  progress: this.computeProgress(this.size)
2884
2964
  });
2885
2965
  if (withHaptic) this.haptic();
2966
+ this.setRestClosed(targetSize === 0);
2886
2967
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2887
2968
  if (targetSize === 0) this.emitCloseSequence();
2888
2969
  }
2970
+ setRestClosed(closed) {
2971
+ if (closed === this.restClosed) return;
2972
+ this.restClosed = closed;
2973
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2974
+ else this.element.removeAttribute("data-bs-rest");
2975
+ }
2889
2976
  emitOpenSequence(id) {
2890
2977
  this.emit("open", {
2891
2978
  id
@@ -2949,6 +3036,20 @@ class BottomSheetCore {
2949
3036
  if (this.destroyed) return;
2950
3037
  this.disableDragFlag = value;
2951
3038
  }
3039
+ setDragFromContent(value) {
3040
+ if (this.destroyed) return;
3041
+ this.dragFromContentDefault = value;
3042
+ }
3043
+ setDragFrom(mode) {
3044
+ if (this.destroyed || mode === this.dragFromMode) return;
3045
+ this.gesture?.forceClearDragState();
3046
+ this.detachSheetGesture?.();
3047
+ this.dragFromMode = mode;
3048
+ this.mountSheetGesture();
3049
+ }
3050
+ getDragFrom() {
3051
+ return this.dragFromMode;
3052
+ }
2952
3053
  isTop() {
2953
3054
  return this.isTopSheet;
2954
3055
  }
@@ -3095,12 +3196,24 @@ class BottomSheetCore {
3095
3196
  auditVhUsage(points);
3096
3197
  this.auditDuplicateSnapIds(points);
3097
3198
  this.snaps.setRaw(points);
3098
- if (allowed) this.snaps.setAllowedIds(allowed);
3199
+ if (allowed) {
3200
+ this.snaps.setAllowedIds(allowed);
3201
+ } else {
3202
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3203
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3204
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3205
+ }
3099
3206
  this.scrollCache.clear();
3100
3207
  this.scrim.invalidateOpacityCache();
3101
3208
  const current = this.snaps.findById(this.activeId);
3102
3209
  if (current) {
3103
3210
  this.applySize(current.size);
3211
+ } else {
3212
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3213
+ if (fallback) {
3214
+ this.activeId = fallback.id;
3215
+ this.applySize(fallback.size);
3216
+ }
3104
3217
  }
3105
3218
  this.updateAriaSlider();
3106
3219
  }
@@ -3110,7 +3223,7 @@ class BottomSheetCore {
3110
3223
  this.snaps.recompute();
3111
3224
  this.maxHeight.clampTo();
3112
3225
  this.scrim.invalidateOpacityCache();
3113
- if (this.gesture?.isDragging) return;
3226
+ if (this.isDraggingAny()) return;
3114
3227
  this.healActiveSnap();
3115
3228
  }
3116
3229
  healActiveSnap() {
@@ -3132,6 +3245,8 @@ class BottomSheetCore {
3132
3245
  this.bus.clear();
3133
3246
  this.animation.cancel();
3134
3247
  this.gesture?.forceClearDragState();
3248
+ this.contentGesture?.forceClearDragState();
3249
+ this.setRestClosed(false);
3135
3250
  this.teardowns.drain();
3136
3251
  this.scrim.destroy();
3137
3252
  this.animation.destroy();
@@ -3242,11 +3357,12 @@ class BottomSheetCore {
3242
3357
  const scale = Math.max(1 - depth * 0.04, 0.86);
3243
3358
  this.element.style.scale = scale === 1 ? "" : String(scale);
3244
3359
  }
3245
- attach() {
3246
- this.gesture = new GestureController({
3247
- handle: this.handle,
3360
+ buildGestureDeps(surface, gestureOptions) {
3361
+ return {
3362
+ handle: surface,
3248
3363
  element: this.element,
3249
3364
  mode: this.mode,
3365
+ gestureOptions,
3250
3366
  getRoot: () => this.rootEl,
3251
3367
  getDragContext: () => {
3252
3368
  const buf = this.dragContextBuf;
@@ -3265,8 +3381,81 @@ class BottomSheetCore {
3265
3381
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3266
3382
  emit: (event, payload) => this.emit(event, payload),
3267
3383
  listenerCount: (event) => this.bus.listenerCount(event)
3384
+ };
3385
+ }
3386
+ isContentDragAllowed() {
3387
+ if (this.disableDragFlag) return false;
3388
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3389
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3390
+ }
3391
+ decideContentDrag(surface, e, delta) {
3392
+ if (this.destroyed) return false;
3393
+ if (e.pointerType === "mouse") return false;
3394
+ if (this.gesture?.isDragging) return false;
3395
+ if (!this.isContentDragAllowed()) return false;
3396
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3397
+ const decision = decideContentGesture({
3398
+ delta,
3399
+ scrollTop: surface.scrollTop,
3400
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3401
+ });
3402
+ if (decision === "pending") return null;
3403
+ return decision === "drag";
3404
+ }
3405
+ attachDragSurface(surface, kind) {
3406
+ if (this.destroyed || kind !== "content") return;
3407
+ if (this.contentGesture) return;
3408
+ const controller = new GestureController(this.buildGestureDeps(surface, {
3409
+ manageTouchAction: false,
3410
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3411
+ }));
3412
+ this.contentGesture = controller;
3413
+ const detachGestures = controller.install();
3414
+ const detachGuard = installTouchScrollGuard(surface, () => controller.isDragging);
3415
+ return () => {
3416
+ detachGuard();
3417
+ detachGestures();
3418
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3419
+ };
3420
+ }
3421
+ sheetDragSurface() {
3422
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3423
+ }
3424
+ canStartSheetDrag(e) {
3425
+ if (this.contentGesture?.isDragging) return false;
3426
+ const target = e.target;
3427
+ const scoped = typeof target?.closest === "function";
3428
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3429
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3430
+ return true;
3431
+ }
3432
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3433
+ if (this.dragFromMode === "handle") return true;
3434
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3435
+ return false;
3436
+ }
3437
+ return true;
3438
+ }
3439
+ mountSheetGesture() {
3440
+ const surface = this.sheetDragSurface();
3441
+ const controller = new GestureController(this.buildGestureDeps(surface, {
3442
+ shouldStart: (e) => this.canStartSheetDrag(e),
3443
+ manageTouchAction: this.dragFromMode === "handle"
3444
+ }));
3445
+ this.gesture = controller;
3446
+ const detachGestures = controller.install();
3447
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3448
+ this.detachSheetGesture = () => {
3449
+ detachGuard?.();
3450
+ detachGestures();
3451
+ };
3452
+ }
3453
+ attach() {
3454
+ this.mountSheetGesture();
3455
+ this.teardowns.add(() => {
3456
+ this.detachSheetGesture?.();
3457
+ this.detachSheetGesture = null;
3268
3458
  });
3269
- this.teardowns.add(this.gesture.install());
3270
3459
  try {
3271
3460
  this.attachFeatures();
3272
3461
  } catch (err) {
@@ -3303,7 +3492,7 @@ class BottomSheetCore {
3303
3492
  element: this.element,
3304
3493
  getMode: () => this.mode,
3305
3494
  isDestroyed: () => this.destroyed,
3306
- isDragging: () => this.gesture?.isDragging ?? false,
3495
+ isDragging: () => this.isDraggingAny(),
3307
3496
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3308
3497
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3309
3498
  getSize: () => this.size,
@@ -3393,8 +3582,11 @@ class BottomSheetCore {
3393
3582
  this.completeSnap(target.id, target.size, previousSize, previousSnapSize === 0, true);
3394
3583
  });
3395
3584
  }
3585
+ isDraggingAny() {
3586
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3587
+ }
3396
3588
  clearWillChangeIfIdle() {
3397
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3589
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3398
3590
  this.element.style.willChange = "";
3399
3591
  }
3400
3592
  haptic() {
@@ -3407,8 +3599,9 @@ class BottomSheetCore {
3407
3599
  }
3408
3600
  applySize(size, skipTransform = false) {
3409
3601
  const cap = this.snaps.getMaxAxisSize();
3410
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3602
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3411
3603
  this.size = clamped;
3604
+ if (clamped > 0) this.setRestClosed(false);
3412
3605
  const offset = cap - clamped;
3413
3606
  const style = this.element.style;
3414
3607
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3780,55 +3973,9 @@ function installAutoCollapse(deps) {
3780
3973
  unsubscribe();
3781
3974
  };
3782
3975
  }
3783
- const CONTENT_PULL_THRESHOLD = 60;
3784
- function installContentSwipe(deps) {
3785
- const { container } = deps;
3786
- let startY = 0;
3787
- let startScrollTop = 0;
3788
- let moved = false;
3789
- const onTouchStart = (e) => {
3790
- const t = e.touches[0];
3791
- if (!t) return;
3792
- startY = t.clientY;
3793
- startScrollTop = container.scrollTop;
3794
- moved = false;
3795
- };
3796
- const onTouchMove = (e) => {
3797
- const t = e.touches[0];
3798
- if (!t) return;
3799
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3800
- };
3801
- const onTouchEnd = (e) => {
3802
- if (!moved) return;
3803
- if (deps.isDragging() || deps.isAnimating()) return;
3804
- if (container.scrollTop !== startScrollTop) return;
3805
- const t = e.changedTouches[0];
3806
- if (!t) return;
3807
- const deltaY = t.clientY - startY;
3808
- const atTop = container.scrollTop <= 0;
3809
- const allowed = deps.getAllowedIds();
3810
- const idx = allowed.indexOf(deps.getActiveId());
3811
- if (idx === -1) return;
3812
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3813
- deps.snapTo(allowed[idx - 1]);
3814
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3815
- deps.snapTo(allowed[idx + 1]);
3816
- }
3817
- };
3818
- container.addEventListener("touchstart", onTouchStart, {
3819
- passive: true
3820
- });
3821
- container.addEventListener("touchmove", onTouchMove, {
3822
- passive: true
3823
- });
3824
- container.addEventListener("touchend", onTouchEnd, {
3825
- passive: true
3826
- });
3827
- return () => {
3828
- container.removeEventListener("touchstart", onTouchStart);
3829
- container.removeEventListener("touchmove", onTouchMove);
3830
- container.removeEventListener("touchend", onTouchEnd);
3831
- };
3976
+ function installContentDrag(deps) {
3977
+ const detach = deps.attachDragSurface(deps.container, "content");
3978
+ return () => detach?.();
3832
3979
  }
3833
3980
  const SAFETY_PAD = 8;
3834
3981
  function installVisualViewport(deps) {
@@ -3930,13 +4077,9 @@ function contentSwipeFeature() {
3930
4077
  stage: "attach",
3931
4078
  install: (ctx) => {
3932
4079
  if (!ctx.scrollContainer) return;
3933
- return installContentSwipe({
4080
+ return installContentDrag({
3934
4081
  container: ctx.scrollContainer,
3935
- isDragging: ctx.isDragging,
3936
- isAnimating: ctx.isAnimating,
3937
- getAllowedIds: ctx.allowedIdsBySize,
3938
- getActiveId: ctx.getActiveId,
3939
- snapTo: ctx.snapTo
4082
+ attachDragSurface: ctx.attachDragSurface
3940
4083
  });
3941
4084
  }
3942
4085
  };
@@ -4067,6 +4210,8 @@ const BottomSheet = component$((props) => {
4067
4210
  persistent: props.persistent,
4068
4211
  disableClose: props.disableClose,
4069
4212
  disableDrag: props.disableDrag,
4213
+ dragFrom: props.dragFrom,
4214
+ dragFromContent: props.dragFromContent,
4070
4215
  closeOnRouteChange: props.closeOnRouteChange,
4071
4216
  stackEffect: props.stackEffect,
4072
4217
  returnFocusTo: props.returnFocusTo,
@@ -4174,11 +4319,17 @@ const BottomSheet = component$((props) => {
4174
4319
  const persistent = track(() => props.persistent);
4175
4320
  const disableClose = track(() => props.disableClose);
4176
4321
  const disableDrag = track(() => props.disableDrag);
4322
+ const dragFromContent = track(() => props.dragFromContent);
4323
+ const dragFrom = track(() => props.dragFrom);
4177
4324
  const engine = engineStore.engine;
4178
4325
  if (!engine) return;
4179
4326
  if (persistent !== void 0) engine.setPersistent(persistent);
4180
4327
  if (disableClose !== void 0) engine.setDisableClose(disableClose);
4181
4328
  if (disableDrag !== void 0) engine.setDisableDrag(disableDrag);
4329
+ if (dragFromContent !== void 0) {
4330
+ engine.setDragFromContent(dragFromContent);
4331
+ }
4332
+ if (dragFrom !== void 0) engine.setDragFrom(dragFrom);
4182
4333
  });
4183
4334
  const showBackdrop = props.backdrop !== false;
4184
4335
  const mode = props.mode ?? "bottom";