@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
@@ -1834,30 +1834,44 @@ class SampleRing {
1834
1834
  this.size = 0;
1835
1835
  }
1836
1836
  }
1837
- const installGestures = (handle, mode, callbacks) => {
1837
+ const installGestures = (handle, mode, callbacks, options = {}) => {
1838
1838
  let activePointerId = null;
1839
1839
  let activePointerType = "touch";
1840
1840
  let startCoord = 0;
1841
1841
  let lastCoord = 0;
1842
+ let pendingStart = false;
1842
1843
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1843
1844
  const MAX_SAMPLES = 32;
1844
1845
  const samples = new SampleRing(MAX_SAMPLES);
1845
1846
  const axis = isAxisVertical(mode) ? "Y" : "X";
1846
1847
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1847
1848
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1849
+ const capture = (pointerId) => {
1850
+ try {
1851
+ handle.setPointerCapture(pointerId);
1852
+ } catch {
1853
+ }
1854
+ };
1855
+ const releaseTracking = () => {
1856
+ activePointerId = null;
1857
+ pendingStart = false;
1858
+ samples.reset();
1859
+ };
1848
1860
  const onPointerDown = (e) => {
1849
1861
  if (activePointerId !== null) return;
1850
1862
  if (e.button !== void 0 && e.button !== 0) return;
1863
+ if (options.shouldStart && !options.shouldStart(e)) return;
1851
1864
  activePointerId = e.pointerId;
1852
1865
  activePointerType = e.pointerType || "touch";
1853
1866
  startCoord = coordOf(e);
1854
1867
  lastCoord = startCoord;
1855
1868
  samples.reset();
1856
1869
  samples.push(e.timeStamp, startCoord);
1857
- try {
1858
- handle.setPointerCapture(e.pointerId);
1859
- } catch {
1870
+ if (options.deferStart) {
1871
+ pendingStart = true;
1872
+ return;
1860
1873
  }
1874
+ capture(e.pointerId);
1861
1875
  callbacks.onStart(startCoord, activePointerType);
1862
1876
  };
1863
1877
  const onPointerMove = (e) => {
@@ -1865,6 +1879,22 @@ const installGestures = (handle, mode, callbacks) => {
1865
1879
  const coord = coordOf(e);
1866
1880
  const rawDelta = coord - startCoord;
1867
1881
  const delta = rawDelta * sign;
1882
+ if (pendingStart) {
1883
+ const verdict = options.deferStart(e, delta);
1884
+ if (verdict === false) {
1885
+ releaseTracking();
1886
+ return;
1887
+ }
1888
+ if (verdict !== true) return;
1889
+ pendingStart = false;
1890
+ startCoord = coord;
1891
+ lastCoord = coord;
1892
+ samples.reset();
1893
+ samples.push(e.timeStamp, coord);
1894
+ capture(e.pointerId);
1895
+ callbacks.onStart(startCoord, activePointerType);
1896
+ return;
1897
+ }
1868
1898
  lastCoord = coord;
1869
1899
  samples.push(e.timeStamp, coord);
1870
1900
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1873,6 +1903,10 @@ const installGestures = (handle, mode, callbacks) => {
1873
1903
  };
1874
1904
  const finishGesture = (e) => {
1875
1905
  if (e.pointerId !== activePointerId) return;
1906
+ if (pendingStart) {
1907
+ releaseTracking();
1908
+ return;
1909
+ }
1876
1910
  const rawDelta = lastCoord - startCoord;
1877
1911
  const delta = rawDelta * sign;
1878
1912
  let velocity = 0;
@@ -1895,22 +1929,26 @@ const installGestures = (handle, mode, callbacks) => {
1895
1929
  };
1896
1930
  const onPointerCancel = (e) => {
1897
1931
  if (e.pointerId !== activePointerId) return;
1898
- activePointerId = null;
1899
- samples.reset();
1932
+ const wasPending = pendingStart;
1933
+ releaseTracking();
1934
+ if (wasPending) return;
1900
1935
  callbacks.onCancel?.();
1901
1936
  };
1902
1937
  handle.addEventListener("pointerdown", onPointerDown);
1903
1938
  handle.addEventListener("pointermove", onPointerMove);
1904
1939
  handle.addEventListener("pointerup", finishGesture);
1905
1940
  handle.addEventListener("pointercancel", onPointerCancel);
1941
+ const managesTouchAction = options.manageTouchAction !== false;
1906
1942
  const prevTouchAction = handle.style.touchAction;
1907
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1943
+ if (managesTouchAction) {
1944
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1945
+ }
1908
1946
  return () => {
1909
1947
  handle.removeEventListener("pointerdown", onPointerDown);
1910
1948
  handle.removeEventListener("pointermove", onPointerMove);
1911
1949
  handle.removeEventListener("pointerup", finishGesture);
1912
1950
  handle.removeEventListener("pointercancel", onPointerCancel);
1913
- handle.style.touchAction = prevTouchAction;
1951
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1914
1952
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1915
1953
  handle.releasePointerCapture(activePointerId);
1916
1954
  }
@@ -2028,7 +2066,7 @@ class GestureController {
2028
2066
  const restoreTo = this.dragStartSize;
2029
2067
  void this.deps.animateTo(restoreTo, 0);
2030
2068
  }
2031
- });
2069
+ }, this.deps.gestureOptions);
2032
2070
  return this.detach;
2033
2071
  }
2034
2072
  forceClearDragState() {
@@ -2039,6 +2077,37 @@ class GestureController {
2039
2077
  this.deps.element.style.willChange = "";
2040
2078
  }
2041
2079
  }
2080
+ const DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2081
+ const NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2082
+ const asElement = (target) => {
2083
+ const candidate = target;
2084
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2085
+ };
2086
+ function isDragAllowedFrom(target, mode) {
2087
+ const el = asElement(target);
2088
+ if (!el) return mode !== "zones";
2089
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2090
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2091
+ return true;
2092
+ }
2093
+ const CONTENT_DRAG_SLOP = 6;
2094
+ function decideContentGesture(input) {
2095
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2096
+ if (Math.abs(input.delta) < slop) return "pending";
2097
+ if (input.scrollTop > 0) return "scroll";
2098
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2099
+ return "drag";
2100
+ }
2101
+ function installTouchScrollGuard(surface, isDragging) {
2102
+ const onTouchMove = (e) => {
2103
+ if (!isDragging()) return;
2104
+ if (e.cancelable) e.preventDefault();
2105
+ };
2106
+ surface.addEventListener("touchmove", onTouchMove, {
2107
+ passive: false
2108
+ });
2109
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2110
+ }
2042
2111
  class AriaSliderWriter {
2043
2112
  constructor(handle, mode) {
2044
2113
  __publicField(this, "handle");
@@ -2432,6 +2501,8 @@ function resolveEngineOptions(opts, extras) {
2432
2501
  mode: opts.mode ?? "bottom",
2433
2502
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2434
2503
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2504
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2505
+ dragFromContent: opts.dragFromContent ?? true,
2435
2506
  rubberBandEnabled: opts.rubberBand ?? true,
2436
2507
  closeOnBack: opts.closeOnBack ?? false,
2437
2508
  persistent: opts.persistent ?? false,
@@ -2513,6 +2584,8 @@ class BottomSheetCore {
2513
2584
  __publicField(this, "mode");
2514
2585
  __publicField(this, "flickVelocity");
2515
2586
  __publicField(this, "dragThreshold");
2587
+ __publicField(this, "dragFromMode");
2588
+ __publicField(this, "dragFromContentDefault");
2516
2589
  __publicField(this, "rubberBandEnabled");
2517
2590
  __publicField(this, "scrim");
2518
2591
  __publicField(this, "aria");
@@ -2530,8 +2603,11 @@ class BottomSheetCore {
2530
2603
  __publicField(this, "activeId");
2531
2604
  __publicField(this, "size", 0);
2532
2605
  __publicField(this, "gesture");
2606
+ __publicField(this, "contentGesture");
2607
+ __publicField(this, "detachSheetGesture", null);
2533
2608
  __publicField(this, "rootEl", null);
2534
2609
  __publicField(this, "destroyed", false);
2610
+ __publicField(this, "restClosed", false);
2535
2611
  __publicField(this, "currentViewTransition", null);
2536
2612
  __publicField(this, "currentAbort", new AbortController());
2537
2613
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2585,6 +2661,8 @@ class BottomSheetCore {
2585
2661
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2586
2662
  this.flickVelocity = resolved.flickVelocity;
2587
2663
  this.dragThreshold = resolved.dragThreshold;
2664
+ this.dragFromMode = resolved.dragFrom;
2665
+ this.dragFromContentDefault = resolved.dragFromContent;
2588
2666
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2589
2667
  this.scrim = new ScrimController({
2590
2668
  mode: this.mode,
@@ -2607,7 +2685,7 @@ class BottomSheetCore {
2607
2685
  getRootEl: () => this.rootEl,
2608
2686
  applySize: (size) => this.applySize(size),
2609
2687
  getSize: () => this.size,
2610
- isDragging: () => this.gesture?.isDragging ?? false,
2688
+ isDragging: () => this.isDraggingAny(),
2611
2689
  applyAux: (size) => this.applySize(size, true),
2612
2690
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2613
2691
  }, resolved.animation);
@@ -2657,6 +2735,7 @@ class BottomSheetCore {
2657
2735
  const initial = this.snaps.findById(this.activeId);
2658
2736
  if (initial) this.size = initial.size;
2659
2737
  this.applySize(this.size);
2738
+ this.setRestClosed(this.size === 0);
2660
2739
  this.updateAriaSlider();
2661
2740
  this.scrollCache = createScrollCache({
2662
2741
  scrollContainer: this.scrollContainer,
@@ -2684,7 +2763,7 @@ class BottomSheetCore {
2684
2763
  scrollContainer: this.scrollContainer,
2685
2764
  hasFitSnap: () => this.snapPointsRaw.some((p) => p.size === "fit" || p.size === "content"),
2686
2765
  isDestroyed: () => this.destroyed,
2687
- isDragging: () => this.gesture?.isDragging ?? false,
2766
+ isDragging: () => this.isDraggingAny(),
2688
2767
  recompute: () => this.recompute()
2689
2768
  }));
2690
2769
  if (resolved.radius !== void 0) this.setRadius(resolved.radius);
@@ -2709,7 +2788,7 @@ class BottomSheetCore {
2709
2788
  autoCollapseAfter
2710
2789
  },
2711
2790
  isDestroyed: () => this.destroyed,
2712
- isDragging: () => this.gesture?.isDragging ?? false,
2791
+ isDragging: () => this.isDraggingAny(),
2713
2792
  isAnimating: () => this.animation.isAnimating,
2714
2793
  isTopSheet: () => this.isTopSheet,
2715
2794
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2739,6 +2818,7 @@ class BottomSheetCore {
2739
2818
  void this.snapTo(id);
2740
2819
  },
2741
2820
  close: (reason) => this.close(reason),
2821
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2742
2822
  on: (event, fn) => this.on(event, fn),
2743
2823
  addTeardown: (fn) => this.teardowns.add(fn)
2744
2824
  };
@@ -2763,7 +2843,7 @@ class BottomSheetCore {
2763
2843
  return {
2764
2844
  size: this.size,
2765
2845
  activeId: this.activeId,
2766
- isDragging: this.gesture?.isDragging ?? false,
2846
+ isDragging: this.isDraggingAny(),
2767
2847
  isAnimating: this.animation.isAnimating,
2768
2848
  progress: this.computeProgress(this.size)
2769
2849
  };
@@ -2885,9 +2965,16 @@ class BottomSheetCore {
2885
2965
  progress: this.computeProgress(this.size)
2886
2966
  });
2887
2967
  if (withHaptic) this.haptic();
2968
+ this.setRestClosed(targetSize === 0);
2888
2969
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2889
2970
  if (targetSize === 0) this.emitCloseSequence();
2890
2971
  }
2972
+ setRestClosed(closed) {
2973
+ if (closed === this.restClosed) return;
2974
+ this.restClosed = closed;
2975
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2976
+ else this.element.removeAttribute("data-bs-rest");
2977
+ }
2891
2978
  emitOpenSequence(id) {
2892
2979
  this.emit("open", {
2893
2980
  id
@@ -2951,6 +3038,20 @@ class BottomSheetCore {
2951
3038
  if (this.destroyed) return;
2952
3039
  this.disableDragFlag = value;
2953
3040
  }
3041
+ setDragFromContent(value) {
3042
+ if (this.destroyed) return;
3043
+ this.dragFromContentDefault = value;
3044
+ }
3045
+ setDragFrom(mode) {
3046
+ if (this.destroyed || mode === this.dragFromMode) return;
3047
+ this.gesture?.forceClearDragState();
3048
+ this.detachSheetGesture?.();
3049
+ this.dragFromMode = mode;
3050
+ this.mountSheetGesture();
3051
+ }
3052
+ getDragFrom() {
3053
+ return this.dragFromMode;
3054
+ }
2954
3055
  isTop() {
2955
3056
  return this.isTopSheet;
2956
3057
  }
@@ -3097,12 +3198,24 @@ class BottomSheetCore {
3097
3198
  auditVhUsage(points);
3098
3199
  this.auditDuplicateSnapIds(points);
3099
3200
  this.snaps.setRaw(points);
3100
- if (allowed) this.snaps.setAllowedIds(allowed);
3201
+ if (allowed) {
3202
+ this.snaps.setAllowedIds(allowed);
3203
+ } else {
3204
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3205
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3206
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3207
+ }
3101
3208
  this.scrollCache.clear();
3102
3209
  this.scrim.invalidateOpacityCache();
3103
3210
  const current = this.snaps.findById(this.activeId);
3104
3211
  if (current) {
3105
3212
  this.applySize(current.size);
3213
+ } else {
3214
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3215
+ if (fallback) {
3216
+ this.activeId = fallback.id;
3217
+ this.applySize(fallback.size);
3218
+ }
3106
3219
  }
3107
3220
  this.updateAriaSlider();
3108
3221
  }
@@ -3112,7 +3225,7 @@ class BottomSheetCore {
3112
3225
  this.snaps.recompute();
3113
3226
  this.maxHeight.clampTo();
3114
3227
  this.scrim.invalidateOpacityCache();
3115
- if (this.gesture?.isDragging) return;
3228
+ if (this.isDraggingAny()) return;
3116
3229
  this.healActiveSnap();
3117
3230
  }
3118
3231
  healActiveSnap() {
@@ -3134,6 +3247,8 @@ class BottomSheetCore {
3134
3247
  this.bus.clear();
3135
3248
  this.animation.cancel();
3136
3249
  this.gesture?.forceClearDragState();
3250
+ this.contentGesture?.forceClearDragState();
3251
+ this.setRestClosed(false);
3137
3252
  this.teardowns.drain();
3138
3253
  this.scrim.destroy();
3139
3254
  this.animation.destroy();
@@ -3244,11 +3359,12 @@ class BottomSheetCore {
3244
3359
  const scale = Math.max(1 - depth * 0.04, 0.86);
3245
3360
  this.element.style.scale = scale === 1 ? "" : String(scale);
3246
3361
  }
3247
- attach() {
3248
- this.gesture = new GestureController({
3249
- handle: this.handle,
3362
+ buildGestureDeps(surface, gestureOptions) {
3363
+ return {
3364
+ handle: surface,
3250
3365
  element: this.element,
3251
3366
  mode: this.mode,
3367
+ gestureOptions,
3252
3368
  getRoot: () => this.rootEl,
3253
3369
  getDragContext: () => {
3254
3370
  const buf = this.dragContextBuf;
@@ -3267,8 +3383,81 @@ class BottomSheetCore {
3267
3383
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3268
3384
  emit: (event, payload) => this.emit(event, payload),
3269
3385
  listenerCount: (event) => this.bus.listenerCount(event)
3386
+ };
3387
+ }
3388
+ isContentDragAllowed() {
3389
+ if (this.disableDragFlag) return false;
3390
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3391
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3392
+ }
3393
+ decideContentDrag(surface, e, delta) {
3394
+ if (this.destroyed) return false;
3395
+ if (e.pointerType === "mouse") return false;
3396
+ if (this.gesture?.isDragging) return false;
3397
+ if (!this.isContentDragAllowed()) return false;
3398
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3399
+ const decision = decideContentGesture({
3400
+ delta,
3401
+ scrollTop: surface.scrollTop,
3402
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3403
+ });
3404
+ if (decision === "pending") return null;
3405
+ return decision === "drag";
3406
+ }
3407
+ attachDragSurface(surface, kind) {
3408
+ if (this.destroyed || kind !== "content") return;
3409
+ if (this.contentGesture) return;
3410
+ const controller = new GestureController(this.buildGestureDeps(surface, {
3411
+ manageTouchAction: false,
3412
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3413
+ }));
3414
+ this.contentGesture = controller;
3415
+ const detachGestures = controller.install();
3416
+ const detachGuard = installTouchScrollGuard(surface, () => controller.isDragging);
3417
+ return () => {
3418
+ detachGuard();
3419
+ detachGestures();
3420
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3421
+ };
3422
+ }
3423
+ sheetDragSurface() {
3424
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3425
+ }
3426
+ canStartSheetDrag(e) {
3427
+ if (this.contentGesture?.isDragging) return false;
3428
+ const target = e.target;
3429
+ const scoped = typeof target?.closest === "function";
3430
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3431
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3432
+ return true;
3433
+ }
3434
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3435
+ if (this.dragFromMode === "handle") return true;
3436
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3437
+ return false;
3438
+ }
3439
+ return true;
3440
+ }
3441
+ mountSheetGesture() {
3442
+ const surface = this.sheetDragSurface();
3443
+ const controller = new GestureController(this.buildGestureDeps(surface, {
3444
+ shouldStart: (e) => this.canStartSheetDrag(e),
3445
+ manageTouchAction: this.dragFromMode === "handle"
3446
+ }));
3447
+ this.gesture = controller;
3448
+ const detachGestures = controller.install();
3449
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3450
+ this.detachSheetGesture = () => {
3451
+ detachGuard?.();
3452
+ detachGestures();
3453
+ };
3454
+ }
3455
+ attach() {
3456
+ this.mountSheetGesture();
3457
+ this.teardowns.add(() => {
3458
+ this.detachSheetGesture?.();
3459
+ this.detachSheetGesture = null;
3270
3460
  });
3271
- this.teardowns.add(this.gesture.install());
3272
3461
  try {
3273
3462
  this.attachFeatures();
3274
3463
  } catch (err) {
@@ -3305,7 +3494,7 @@ class BottomSheetCore {
3305
3494
  element: this.element,
3306
3495
  getMode: () => this.mode,
3307
3496
  isDestroyed: () => this.destroyed,
3308
- isDragging: () => this.gesture?.isDragging ?? false,
3497
+ isDragging: () => this.isDraggingAny(),
3309
3498
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3310
3499
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3311
3500
  getSize: () => this.size,
@@ -3395,8 +3584,11 @@ class BottomSheetCore {
3395
3584
  this.completeSnap(target.id, target.size, previousSize, previousSnapSize === 0, true);
3396
3585
  });
3397
3586
  }
3587
+ isDraggingAny() {
3588
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3589
+ }
3398
3590
  clearWillChangeIfIdle() {
3399
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3591
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3400
3592
  this.element.style.willChange = "";
3401
3593
  }
3402
3594
  haptic() {
@@ -3409,8 +3601,9 @@ class BottomSheetCore {
3409
3601
  }
3410
3602
  applySize(size, skipTransform = false) {
3411
3603
  const cap = this.snaps.getMaxAxisSize();
3412
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3604
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3413
3605
  this.size = clamped;
3606
+ if (clamped > 0) this.setRestClosed(false);
3414
3607
  const offset = cap - clamped;
3415
3608
  const style = this.element.style;
3416
3609
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3782,55 +3975,9 @@ function installAutoCollapse(deps) {
3782
3975
  unsubscribe();
3783
3976
  };
3784
3977
  }
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
- };
3978
+ function installContentDrag(deps) {
3979
+ const detach = deps.attachDragSurface(deps.container, "content");
3980
+ return () => detach?.();
3834
3981
  }
3835
3982
  const SAFETY_PAD = 8;
3836
3983
  function installVisualViewport(deps) {
@@ -3932,13 +4079,9 @@ function contentSwipeFeature() {
3932
4079
  stage: "attach",
3933
4080
  install: (ctx) => {
3934
4081
  if (!ctx.scrollContainer) return;
3935
- return installContentSwipe({
4082
+ return installContentDrag({
3936
4083
  container: ctx.scrollContainer,
3937
- isDragging: ctx.isDragging,
3938
- isAnimating: ctx.isAnimating,
3939
- getAllowedIds: ctx.allowedIdsBySize,
3940
- getActiveId: ctx.getActiveId,
3941
- snapTo: ctx.snapTo
4084
+ attachDragSurface: ctx.attachDragSurface
3942
4085
  });
3943
4086
  }
3944
4087
  };
@@ -4069,6 +4212,8 @@ const BottomSheet = qwik.component$((props) => {
4069
4212
  persistent: props.persistent,
4070
4213
  disableClose: props.disableClose,
4071
4214
  disableDrag: props.disableDrag,
4215
+ dragFrom: props.dragFrom,
4216
+ dragFromContent: props.dragFromContent,
4072
4217
  closeOnRouteChange: props.closeOnRouteChange,
4073
4218
  stackEffect: props.stackEffect,
4074
4219
  returnFocusTo: props.returnFocusTo,
@@ -4176,11 +4321,17 @@ const BottomSheet = qwik.component$((props) => {
4176
4321
  const persistent = track(() => props.persistent);
4177
4322
  const disableClose = track(() => props.disableClose);
4178
4323
  const disableDrag = track(() => props.disableDrag);
4324
+ const dragFromContent = track(() => props.dragFromContent);
4325
+ const dragFrom = track(() => props.dragFrom);
4179
4326
  const engine = engineStore.engine;
4180
4327
  if (!engine) return;
4181
4328
  if (persistent !== void 0) engine.setPersistent(persistent);
4182
4329
  if (disableClose !== void 0) engine.setDisableClose(disableClose);
4183
4330
  if (disableDrag !== void 0) engine.setDisableDrag(disableDrag);
4331
+ if (dragFromContent !== void 0) {
4332
+ engine.setDragFromContent(dragFromContent);
4333
+ }
4334
+ if (dragFrom !== void 0) engine.setDragFrom(dragFrom);
4184
4335
  });
4185
4336
  const showBackdrop = props.backdrop !== false;
4186
4337
  const mode = props.mode ?? "bottom";