@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
@@ -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);
@@ -1520,7 +1520,7 @@ var AnimationRunner = class {
1520
1520
  }
1521
1521
  };
1522
1522
 
1523
- // src/core/lifecycle/focusTrap.ts
1523
+ // src/core/lifecycle/focus-trap.ts
1524
1524
  var FOCUSABLE = [
1525
1525
  "a[href]",
1526
1526
  "area[href]",
@@ -1638,7 +1638,7 @@ var installFocusTrap = (container, options = {}) => {
1638
1638
  };
1639
1639
  };
1640
1640
 
1641
- // src/core/lifecycle/scrollLock.ts
1641
+ // src/core/lifecycle/scroll-lock.ts
1642
1642
  var lockCount = 0;
1643
1643
  var savedStyles = null;
1644
1644
  var lockBodyScroll = () => {
@@ -1820,30 +1820,44 @@ var SampleRing = class {
1820
1820
  this.size = 0;
1821
1821
  }
1822
1822
  };
1823
- var installGestures = (handle, mode, callbacks) => {
1823
+ var installGestures = (handle, mode, callbacks, options = {}) => {
1824
1824
  let activePointerId = null;
1825
1825
  let activePointerType = "touch";
1826
1826
  let startCoord = 0;
1827
1827
  let lastCoord = 0;
1828
+ let pendingStart = false;
1828
1829
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1829
1830
  const MAX_SAMPLES = 32;
1830
1831
  const samples = new SampleRing(MAX_SAMPLES);
1831
1832
  const axis = isAxisVertical(mode) ? "Y" : "X";
1832
1833
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1833
1834
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1835
+ const capture = (pointerId) => {
1836
+ try {
1837
+ handle.setPointerCapture(pointerId);
1838
+ } catch {
1839
+ }
1840
+ };
1841
+ const releaseTracking = () => {
1842
+ activePointerId = null;
1843
+ pendingStart = false;
1844
+ samples.reset();
1845
+ };
1834
1846
  const onPointerDown = (e) => {
1835
1847
  if (activePointerId !== null) return;
1836
1848
  if (e.button !== void 0 && e.button !== 0) return;
1849
+ if (options.shouldStart && !options.shouldStart(e)) return;
1837
1850
  activePointerId = e.pointerId;
1838
1851
  activePointerType = e.pointerType || "touch";
1839
1852
  startCoord = coordOf(e);
1840
1853
  lastCoord = startCoord;
1841
1854
  samples.reset();
1842
1855
  samples.push(e.timeStamp, startCoord);
1843
- try {
1844
- handle.setPointerCapture(e.pointerId);
1845
- } catch {
1856
+ if (options.deferStart) {
1857
+ pendingStart = true;
1858
+ return;
1846
1859
  }
1860
+ capture(e.pointerId);
1847
1861
  callbacks.onStart(startCoord, activePointerType);
1848
1862
  };
1849
1863
  const onPointerMove = (e) => {
@@ -1851,6 +1865,22 @@ var installGestures = (handle, mode, callbacks) => {
1851
1865
  const coord = coordOf(e);
1852
1866
  const rawDelta = coord - startCoord;
1853
1867
  const delta = rawDelta * sign;
1868
+ if (pendingStart) {
1869
+ const verdict = options.deferStart(e, delta);
1870
+ if (verdict === false) {
1871
+ releaseTracking();
1872
+ return;
1873
+ }
1874
+ if (verdict !== true) return;
1875
+ pendingStart = false;
1876
+ startCoord = coord;
1877
+ lastCoord = coord;
1878
+ samples.reset();
1879
+ samples.push(e.timeStamp, coord);
1880
+ capture(e.pointerId);
1881
+ callbacks.onStart(startCoord, activePointerType);
1882
+ return;
1883
+ }
1854
1884
  lastCoord = coord;
1855
1885
  samples.push(e.timeStamp, coord);
1856
1886
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1859,6 +1889,10 @@ var installGestures = (handle, mode, callbacks) => {
1859
1889
  };
1860
1890
  const finishGesture = (e) => {
1861
1891
  if (e.pointerId !== activePointerId) return;
1892
+ if (pendingStart) {
1893
+ releaseTracking();
1894
+ return;
1895
+ }
1862
1896
  const rawDelta = lastCoord - startCoord;
1863
1897
  const delta = rawDelta * sign;
1864
1898
  let velocity = 0;
@@ -1881,22 +1915,26 @@ var installGestures = (handle, mode, callbacks) => {
1881
1915
  };
1882
1916
  const onPointerCancel = (e) => {
1883
1917
  if (e.pointerId !== activePointerId) return;
1884
- activePointerId = null;
1885
- samples.reset();
1918
+ const wasPending = pendingStart;
1919
+ releaseTracking();
1920
+ if (wasPending) return;
1886
1921
  callbacks.onCancel?.();
1887
1922
  };
1888
1923
  handle.addEventListener("pointerdown", onPointerDown);
1889
1924
  handle.addEventListener("pointermove", onPointerMove);
1890
1925
  handle.addEventListener("pointerup", finishGesture);
1891
1926
  handle.addEventListener("pointercancel", onPointerCancel);
1927
+ const managesTouchAction = options.manageTouchAction !== false;
1892
1928
  const prevTouchAction = handle.style.touchAction;
1893
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1929
+ if (managesTouchAction) {
1930
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1931
+ }
1894
1932
  return () => {
1895
1933
  handle.removeEventListener("pointerdown", onPointerDown);
1896
1934
  handle.removeEventListener("pointermove", onPointerMove);
1897
1935
  handle.removeEventListener("pointerup", finishGesture);
1898
1936
  handle.removeEventListener("pointercancel", onPointerCancel);
1899
- handle.style.touchAction = prevTouchAction;
1937
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1900
1938
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1901
1939
  handle.releasePointerCapture(activePointerId);
1902
1940
  }
@@ -2015,7 +2053,7 @@ var GestureController = class {
2015
2053
  const restoreTo = this.dragStartSize;
2016
2054
  void this.deps.animateTo(restoreTo, 0);
2017
2055
  }
2018
- });
2056
+ }, this.deps.gestureOptions);
2019
2057
  return this.detach;
2020
2058
  }
2021
2059
  forceClearDragState() {
@@ -2027,6 +2065,41 @@ var GestureController = class {
2027
2065
  }
2028
2066
  };
2029
2067
 
2068
+ // src/core/primitives/drag-zones.ts
2069
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2070
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2071
+ var asElement = (target) => {
2072
+ const candidate = target;
2073
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2074
+ };
2075
+ function isDragAllowedFrom(target, mode) {
2076
+ const el = asElement(target);
2077
+ if (!el) return mode !== "zones";
2078
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2079
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2080
+ return true;
2081
+ }
2082
+
2083
+ // src/core/primitives/content-gesture.ts
2084
+ var CONTENT_DRAG_SLOP = 6;
2085
+ function decideContentGesture(input) {
2086
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2087
+ if (Math.abs(input.delta) < slop) return "pending";
2088
+ if (input.scrollTop > 0) return "scroll";
2089
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2090
+ return "drag";
2091
+ }
2092
+
2093
+ // src/core/primitives/touch-scroll-guard.ts
2094
+ function installTouchScrollGuard(surface, isDragging) {
2095
+ const onTouchMove = (e) => {
2096
+ if (!isDragging()) return;
2097
+ if (e.cancelable) e.preventDefault();
2098
+ };
2099
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2100
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2101
+ }
2102
+
2030
2103
  // src/core/primitives/aria-slider-writer.ts
2031
2104
  var AriaSliderWriter = class {
2032
2105
  constructor(handle, mode) {
@@ -2383,6 +2456,8 @@ function resolveEngineOptions(opts, extras) {
2383
2456
  mode: opts.mode ?? "bottom",
2384
2457
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2385
2458
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2459
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2460
+ dragFromContent: opts.dragFromContent ?? true,
2386
2461
  rubberBandEnabled: opts.rubberBand ?? true,
2387
2462
  closeOnBack: opts.closeOnBack ?? false,
2388
2463
  persistent: opts.persistent ?? false,
@@ -2465,6 +2540,8 @@ var BottomSheetCore = class {
2465
2540
  mode;
2466
2541
  flickVelocity;
2467
2542
  dragThreshold;
2543
+ dragFromMode;
2544
+ dragFromContentDefault;
2468
2545
  rubberBandEnabled;
2469
2546
  scrim;
2470
2547
  aria;
@@ -2482,8 +2559,11 @@ var BottomSheetCore = class {
2482
2559
  activeId;
2483
2560
  size = 0;
2484
2561
  gesture;
2562
+ contentGesture;
2563
+ detachSheetGesture = null;
2485
2564
  rootEl = null;
2486
2565
  destroyed = false;
2566
+ restClosed = false;
2487
2567
  currentViewTransition = null;
2488
2568
  currentAbort = new AbortController();
2489
2569
  sizeWriteSentinel = new WriteSentinel();
@@ -2537,6 +2617,8 @@ var BottomSheetCore = class {
2537
2617
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2538
2618
  this.flickVelocity = resolved.flickVelocity;
2539
2619
  this.dragThreshold = resolved.dragThreshold;
2620
+ this.dragFromMode = resolved.dragFrom;
2621
+ this.dragFromContentDefault = resolved.dragFromContent;
2540
2622
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2541
2623
  this.scrim = new ScrimController(
2542
2624
  {
@@ -2563,7 +2645,7 @@ var BottomSheetCore = class {
2563
2645
  getRootEl: () => this.rootEl,
2564
2646
  applySize: (size) => this.applySize(size),
2565
2647
  getSize: () => this.size,
2566
- isDragging: () => this.gesture?.isDragging ?? false,
2648
+ isDragging: () => this.isDraggingAny(),
2567
2649
  applyAux: (size) => this.applySize(size, true),
2568
2650
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2569
2651
  },
@@ -2624,6 +2706,7 @@ var BottomSheetCore = class {
2624
2706
  const initial = this.snaps.findById(this.activeId);
2625
2707
  if (initial) this.size = initial.size;
2626
2708
  this.applySize(this.size);
2709
+ this.setRestClosed(this.size === 0);
2627
2710
  this.updateAriaSlider();
2628
2711
  this.scrollCache = createScrollCache({
2629
2712
  scrollContainer: this.scrollContainer,
@@ -2654,7 +2737,7 @@ var BottomSheetCore = class {
2654
2737
  (p) => p.size === "fit" || p.size === "content"
2655
2738
  ),
2656
2739
  isDestroyed: () => this.destroyed,
2657
- isDragging: () => this.gesture?.isDragging ?? false,
2740
+ isDragging: () => this.isDraggingAny(),
2658
2741
  recompute: () => this.recompute()
2659
2742
  })
2660
2743
  );
@@ -2680,7 +2763,7 @@ var BottomSheetCore = class {
2680
2763
  autoCollapseAfter
2681
2764
  },
2682
2765
  isDestroyed: () => this.destroyed,
2683
- isDragging: () => this.gesture?.isDragging ?? false,
2766
+ isDragging: () => this.isDraggingAny(),
2684
2767
  isAnimating: () => this.animation.isAnimating,
2685
2768
  isTopSheet: () => this.isTopSheet,
2686
2769
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2710,6 +2793,7 @@ var BottomSheetCore = class {
2710
2793
  void this.snapTo(id);
2711
2794
  },
2712
2795
  close: (reason) => this.close(reason),
2796
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2713
2797
  on: (event, fn) => this.on(event, fn),
2714
2798
  addTeardown: (fn) => this.teardowns.add(fn)
2715
2799
  };
@@ -2738,7 +2822,7 @@ var BottomSheetCore = class {
2738
2822
  return {
2739
2823
  size: this.size,
2740
2824
  activeId: this.activeId,
2741
- isDragging: this.gesture?.isDragging ?? false,
2825
+ isDragging: this.isDraggingAny(),
2742
2826
  isAnimating: this.animation.isAnimating,
2743
2827
  progress: this.computeProgress(this.size)
2744
2828
  };
@@ -2858,9 +2942,16 @@ var BottomSheetCore = class {
2858
2942
  progress: this.computeProgress(this.size)
2859
2943
  });
2860
2944
  if (withHaptic) this.haptic();
2945
+ this.setRestClosed(targetSize === 0);
2861
2946
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2862
2947
  if (targetSize === 0) this.emitCloseSequence();
2863
2948
  }
2949
+ setRestClosed(closed) {
2950
+ if (closed === this.restClosed) return;
2951
+ this.restClosed = closed;
2952
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
2953
+ else this.element.removeAttribute("data-bs-rest");
2954
+ }
2864
2955
  emitOpenSequence(id) {
2865
2956
  this.emit("open", { id });
2866
2957
  this.handleOpen();
@@ -2916,6 +3007,20 @@ var BottomSheetCore = class {
2916
3007
  if (this.destroyed) return;
2917
3008
  this.disableDragFlag = value;
2918
3009
  }
3010
+ setDragFromContent(value) {
3011
+ if (this.destroyed) return;
3012
+ this.dragFromContentDefault = value;
3013
+ }
3014
+ setDragFrom(mode) {
3015
+ if (this.destroyed || mode === this.dragFromMode) return;
3016
+ this.gesture?.forceClearDragState();
3017
+ this.detachSheetGesture?.();
3018
+ this.dragFromMode = mode;
3019
+ this.mountSheetGesture();
3020
+ }
3021
+ getDragFrom() {
3022
+ return this.dragFromMode;
3023
+ }
2919
3024
  isTop() {
2920
3025
  return this.isTopSheet;
2921
3026
  }
@@ -3070,12 +3175,24 @@ var BottomSheetCore = class {
3070
3175
  auditVhUsage(points);
3071
3176
  this.auditDuplicateSnapIds(points);
3072
3177
  this.snaps.setRaw(points);
3073
- if (allowed) this.snaps.setAllowedIds(allowed);
3178
+ if (allowed) {
3179
+ this.snaps.setAllowedIds(allowed);
3180
+ } else {
3181
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3182
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3183
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3184
+ }
3074
3185
  this.scrollCache.clear();
3075
3186
  this.scrim.invalidateOpacityCache();
3076
3187
  const current = this.snaps.findById(this.activeId);
3077
3188
  if (current) {
3078
3189
  this.applySize(current.size);
3190
+ } else {
3191
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3192
+ if (fallback) {
3193
+ this.activeId = fallback.id;
3194
+ this.applySize(fallback.size);
3195
+ }
3079
3196
  }
3080
3197
  this.updateAriaSlider();
3081
3198
  }
@@ -3085,7 +3202,7 @@ var BottomSheetCore = class {
3085
3202
  this.snaps.recompute();
3086
3203
  this.maxHeight.clampTo();
3087
3204
  this.scrim.invalidateOpacityCache();
3088
- if (this.gesture?.isDragging) return;
3205
+ if (this.isDraggingAny()) return;
3089
3206
  this.healActiveSnap();
3090
3207
  }
3091
3208
  healActiveSnap() {
@@ -3107,6 +3224,8 @@ var BottomSheetCore = class {
3107
3224
  this.bus.clear();
3108
3225
  this.animation.cancel();
3109
3226
  this.gesture?.forceClearDragState();
3227
+ this.contentGesture?.forceClearDragState();
3228
+ this.setRestClosed(false);
3110
3229
  this.teardowns.drain();
3111
3230
  this.scrim.destroy();
3112
3231
  this.animation.destroy();
@@ -3219,11 +3338,12 @@ var BottomSheetCore = class {
3219
3338
  const scale = Math.max(1 - depth * 0.04, 0.86);
3220
3339
  this.element.style.scale = scale === 1 ? "" : String(scale);
3221
3340
  }
3222
- attach() {
3223
- this.gesture = new GestureController({
3224
- handle: this.handle,
3341
+ buildGestureDeps(surface, gestureOptions) {
3342
+ return {
3343
+ handle: surface,
3225
3344
  element: this.element,
3226
3345
  mode: this.mode,
3346
+ gestureOptions,
3227
3347
  getRoot: () => this.rootEl,
3228
3348
  getDragContext: () => {
3229
3349
  const buf = this.dragContextBuf;
@@ -3242,8 +3362,88 @@ var BottomSheetCore = class {
3242
3362
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3243
3363
  emit: (event, payload) => this.emit(event, payload),
3244
3364
  listenerCount: (event) => this.bus.listenerCount(event)
3365
+ };
3366
+ }
3367
+ isContentDragAllowed() {
3368
+ if (this.disableDragFlag) return false;
3369
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3370
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3371
+ }
3372
+ decideContentDrag(surface, e, delta) {
3373
+ if (this.destroyed) return false;
3374
+ if (e.pointerType === "mouse") return false;
3375
+ if (this.gesture?.isDragging) return false;
3376
+ if (!this.isContentDragAllowed()) return false;
3377
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3378
+ const decision = decideContentGesture({
3379
+ delta,
3380
+ scrollTop: surface.scrollTop,
3381
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3382
+ });
3383
+ if (decision === "pending") return null;
3384
+ return decision === "drag";
3385
+ }
3386
+ attachDragSurface(surface, kind) {
3387
+ if (this.destroyed || kind !== "content") return;
3388
+ if (this.contentGesture) return;
3389
+ const controller = new GestureController(
3390
+ this.buildGestureDeps(surface, {
3391
+ manageTouchAction: false,
3392
+ deferStart: (e, delta) => this.decideContentDrag(surface, e, delta)
3393
+ })
3394
+ );
3395
+ this.contentGesture = controller;
3396
+ const detachGestures = controller.install();
3397
+ const detachGuard = installTouchScrollGuard(
3398
+ surface,
3399
+ () => controller.isDragging
3400
+ );
3401
+ return () => {
3402
+ detachGuard();
3403
+ detachGestures();
3404
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3405
+ };
3406
+ }
3407
+ sheetDragSurface() {
3408
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3409
+ }
3410
+ canStartSheetDrag(e) {
3411
+ if (this.contentGesture?.isDragging) return false;
3412
+ const target = e.target;
3413
+ const scoped = typeof target?.closest === "function";
3414
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3415
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3416
+ return true;
3417
+ }
3418
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3419
+ if (this.dragFromMode === "handle") return true;
3420
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3421
+ return false;
3422
+ }
3423
+ return true;
3424
+ }
3425
+ mountSheetGesture() {
3426
+ const surface = this.sheetDragSurface();
3427
+ const controller = new GestureController(
3428
+ this.buildGestureDeps(surface, {
3429
+ shouldStart: (e) => this.canStartSheetDrag(e),
3430
+ manageTouchAction: this.dragFromMode === "handle"
3431
+ })
3432
+ );
3433
+ this.gesture = controller;
3434
+ const detachGestures = controller.install();
3435
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3436
+ this.detachSheetGesture = () => {
3437
+ detachGuard?.();
3438
+ detachGestures();
3439
+ };
3440
+ }
3441
+ attach() {
3442
+ this.mountSheetGesture();
3443
+ this.teardowns.add(() => {
3444
+ this.detachSheetGesture?.();
3445
+ this.detachSheetGesture = null;
3245
3446
  });
3246
- this.teardowns.add(this.gesture.install());
3247
3447
  try {
3248
3448
  this.attachFeatures();
3249
3449
  } catch (err) {
@@ -3280,7 +3480,7 @@ var BottomSheetCore = class {
3280
3480
  element: this.element,
3281
3481
  getMode: () => this.mode,
3282
3482
  isDestroyed: () => this.destroyed,
3283
- isDragging: () => this.gesture?.isDragging ?? false,
3483
+ isDragging: () => this.isDraggingAny(),
3284
3484
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3285
3485
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3286
3486
  getSize: () => this.size,
@@ -3378,8 +3578,11 @@ var BottomSheetCore = class {
3378
3578
  );
3379
3579
  });
3380
3580
  }
3581
+ isDraggingAny() {
3582
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3583
+ }
3381
3584
  clearWillChangeIfIdle() {
3382
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3585
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3383
3586
  this.element.style.willChange = "";
3384
3587
  }
3385
3588
  haptic() {
@@ -3392,8 +3595,9 @@ var BottomSheetCore = class {
3392
3595
  }
3393
3596
  applySize(size, skipTransform = false) {
3394
3597
  const cap = this.snaps.getMaxAxisSize();
3395
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3598
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3396
3599
  this.size = clamped;
3600
+ if (clamped > 0) this.setRestClosed(false);
3397
3601
  const offset = cap - clamped;
3398
3602
  const style = this.element.style;
3399
3603
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3773,50 +3977,10 @@ function installAutoCollapse(deps) {
3773
3977
  };
3774
3978
  }
3775
3979
 
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
- };
3980
+ // src/core/features/content-drag.ts
3981
+ function installContentDrag(deps) {
3982
+ const detach = deps.attachDragSurface(deps.container, "content");
3983
+ return () => detach?.();
3820
3984
  }
3821
3985
 
3822
3986
  // src/core/features/visual-viewport.ts
@@ -3924,13 +4088,9 @@ function contentSwipeFeature() {
3924
4088
  stage: "attach",
3925
4089
  install: (ctx) => {
3926
4090
  if (!ctx.scrollContainer) return;
3927
- return installContentSwipe({
4091
+ return installContentDrag({
3928
4092
  container: ctx.scrollContainer,
3929
- isDragging: ctx.isDragging,
3930
- isAnimating: ctx.isAnimating,
3931
- getAllowedIds: ctx.allowedIdsBySize,
3932
- getActiveId: ctx.getActiveId,
3933
- snapTo: ctx.snapTo
4093
+ attachDragSurface: ctx.attachDragSurface
3934
4094
  });
3935
4095
  }
3936
4096
  };
@@ -4056,6 +4216,8 @@ var BottomSheet = (props) => {
4056
4216
  persistent: props.persistent,
4057
4217
  disableClose: props.disableClose,
4058
4218
  disableDrag: props.disableDrag,
4219
+ dragFrom: props.dragFrom,
4220
+ dragFromContent: props.dragFromContent,
4059
4221
  closeOnRouteChange: props.closeOnRouteChange,
4060
4222
  stackEffect: props.stackEffect,
4061
4223
  returnFocusTo: props.returnFocusTo
@@ -4168,6 +4330,26 @@ var BottomSheet = (props) => {
4168
4330
  { defer: true }
4169
4331
  )
4170
4332
  );
4333
+ createEffect(
4334
+ on(
4335
+ () => props.dragFromContent,
4336
+ (value, prev) => {
4337
+ if (!engine || prev === void 0 || value === void 0) return;
4338
+ engine.setDragFromContent(value);
4339
+ },
4340
+ { defer: true }
4341
+ )
4342
+ );
4343
+ createEffect(
4344
+ on(
4345
+ () => props.dragFrom,
4346
+ (value, prev) => {
4347
+ if (!engine || prev === void 0 || value === void 0) return;
4348
+ engine.setDragFrom(value);
4349
+ },
4350
+ { defer: true }
4351
+ )
4352
+ );
4171
4353
  const showBackdrop = createMemo(() => props.backdrop !== false);
4172
4354
  const mode = createMemo(() => props.mode ?? "bottom");
4173
4355
  const ariaLabel = createMemo(() => props.ariaLabel ?? "Bottom sheet");
package/dist/styles.css CHANGED
@@ -41,6 +41,8 @@
41
41
  position: fixed;
42
42
  background: var(--bs-surface, var(--bs-surface-d));
43
43
  box-shadow: var(--bs-shadow, var(--bs-shadow-auto, none));
44
+
45
+ --bs-shadow-fade: clamp(0, calc(var(--bs-progress, 0) * 8), 1);
44
46
  z-index: var(--bs-z-sheet);
45
47
  pointer-events: auto;
46
48
  overflow: hidden;
@@ -67,7 +69,12 @@
67
69
  --bs-shadow-auto: 0
68
70
  calc(var(--bs-progress, 0) * -2px - 3px)
69
71
  calc(var(--bs-progress, 0) * 18px + 12px)
70
- rgba(0, 0, 0, calc(var(--bs-progress, 0) * 0.15 + 0.12));
72
+ rgba(
73
+ 0,
74
+ 0,
75
+ 0,
76
+ calc(var(--bs-shadow-fade) * (var(--bs-progress, 0) * 0.15 + 0.12))
77
+ );
71
78
  }
72
79
  .bs-sheet[data-mode="top"] {
73
80
  left: 0;
@@ -78,7 +85,7 @@
78
85
  border-bottom-right-radius: var(--bs-radius, var(--bs-radius-d));
79
86
  border-bottom: 1px solid var(--bs-surface-border, var(--bs-surface-border-d));
80
87
  flex-direction: column-reverse;
81
- --bs-shadow-auto: 0 3px 12px rgba(0, 0, 0, 0.12);
88
+ --bs-shadow-auto: 0 3px 12px rgba(0, 0, 0, calc(var(--bs-shadow-fade) * 0.12));
82
89
  padding-top: env(safe-area-inset-top);
83
90
  }
84
91
  .bs-sheet[data-mode="left"] {
@@ -90,7 +97,7 @@
90
97
  border-bottom-right-radius: var(--bs-radius, var(--bs-radius-d));
91
98
  border-right: 1px solid var(--bs-surface-border, var(--bs-surface-border-d));
92
99
 
93
- --bs-shadow-auto: 3px 0 12px rgba(0, 0, 0, 0.12);
100
+ --bs-shadow-auto: 3px 0 12px rgba(0, 0, 0, calc(var(--bs-shadow-fade) * 0.12));
94
101
  }
95
102
  .bs-sheet[data-mode="right"] {
96
103
  top: 0;
@@ -100,7 +107,20 @@
100
107
  border-top-left-radius: var(--bs-radius, var(--bs-radius-d));
101
108
  border-bottom-left-radius: var(--bs-radius, var(--bs-radius-d));
102
109
  border-left: 1px solid var(--bs-surface-border, var(--bs-surface-border-d));
103
- --bs-shadow-auto: -3px 0 12px rgba(0, 0, 0, 0.12);
110
+ --bs-shadow-auto: -3px 0 12px rgba(0, 0, 0, calc(var(--bs-shadow-fade) * 0.12));
111
+ }
112
+
113
+ .bs-sheet[data-bs-rest="closed"] {
114
+ box-shadow: none;
115
+ visibility: hidden;
116
+ }
117
+
118
+ .bs-sheet[data-bs-webgl="on"],
119
+ .bs-sheet[data-bs-webgl="on"] .bs-handle,
120
+ .bs-sheet[data-bs-webgl="on"] .bs-header,
121
+ .bs-sheet[data-bs-webgl="on"] .bs-footer {
122
+ background: transparent;
123
+ box-shadow: none;
104
124
  }
105
125
 
106
126
  .bs-sheet[data-mode="left"] .bs-content {