@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
package/dist/react.js CHANGED
@@ -6,7 +6,7 @@ var __defProp = Object.defineProperty;
6
6
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
7
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
8
8
 
9
- // src/core/lifecycle/sheetStack.ts
9
+ // src/core/lifecycle/sheet-stack.ts
10
10
  var BASE_Z = 100;
11
11
  var STEP = 10;
12
12
  var SheetStack = class {
@@ -137,7 +137,7 @@ function createEventBus() {
137
137
  };
138
138
  }
139
139
 
140
- // src/core/primitives/cssLength.ts
140
+ // src/core/primitives/css-length.ts
141
141
  var probe = null;
142
142
  var PROBE_ATTR = "data-bs-probe";
143
143
  var ensureProbe = (axis) => {
@@ -191,7 +191,7 @@ var resolveSnap = (point, mode, measureFit) => {
191
191
  return Math.max(0, el.getBoundingClientRect().width);
192
192
  };
193
193
 
194
- // src/core/primitives/devWarn.ts
194
+ // src/core/primitives/dev-warn.ts
195
195
  function devWarn(...args) {
196
196
  if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
197
197
  console.warn(...args);
@@ -323,6 +323,11 @@ var SnapResolver = class {
323
323
  this.raw = raw;
324
324
  this.recompute();
325
325
  }
326
+ setMode(mode) {
327
+ if (mode === this.mode) return;
328
+ this.mode = mode;
329
+ this.recompute();
330
+ }
326
331
  setAllowedIds(ids) {
327
332
  this.allowedIds = ids;
328
333
  this.rangeCache = null;
@@ -906,6 +911,11 @@ var ScrimController = class {
906
911
  if (this.isDestroyed()) return;
907
912
  this.applyScrimStyles({ interactive });
908
913
  }
914
+ setSheetMode(mode) {
915
+ if (this.isDestroyed() || mode === this.mode) return;
916
+ this.mode = mode;
917
+ this.invalidateOpacityCache();
918
+ }
909
919
  setScrimMode(mode, applySize) {
910
920
  if (this.isDestroyed()) return;
911
921
  if (mode === this.scrimMode) return;
@@ -1524,7 +1534,7 @@ var AnimationRunner = class {
1524
1534
  }
1525
1535
  };
1526
1536
 
1527
- // src/core/lifecycle/focusTrap.ts
1537
+ // src/core/lifecycle/focus-trap.ts
1528
1538
  var FOCUSABLE = [
1529
1539
  "a[href]",
1530
1540
  "area[href]",
@@ -1642,7 +1652,7 @@ var installFocusTrap = (container, options = {}) => {
1642
1652
  };
1643
1653
  };
1644
1654
 
1645
- // src/core/lifecycle/scrollLock.ts
1655
+ // src/core/lifecycle/scroll-lock.ts
1646
1656
  var lockCount = 0;
1647
1657
  var savedStyles = null;
1648
1658
  var lockBodyScroll = () => {
@@ -1824,30 +1834,47 @@ var SampleRing = class {
1824
1834
  this.size = 0;
1825
1835
  }
1826
1836
  };
1827
- var installGestures = (handle, mode, callbacks) => {
1837
+ var installGestures = (handle, mode, callbacks, options = {}) => {
1828
1838
  let activePointerId = null;
1829
1839
  let activePointerType = "touch";
1830
1840
  let startCoord = 0;
1841
+ let startCross = 0;
1831
1842
  let lastCoord = 0;
1843
+ let pendingStart = false;
1832
1844
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1833
1845
  const MAX_SAMPLES = 32;
1834
1846
  const samples = new SampleRing(MAX_SAMPLES);
1835
1847
  const axis = isAxisVertical(mode) ? "Y" : "X";
1836
1848
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1837
1849
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1850
+ const crossOf = (e) => axis === "Y" ? e.clientX : e.clientY;
1851
+ const capture = (pointerId) => {
1852
+ try {
1853
+ handle.setPointerCapture(pointerId);
1854
+ } catch {
1855
+ }
1856
+ };
1857
+ const releaseTracking = () => {
1858
+ activePointerId = null;
1859
+ pendingStart = false;
1860
+ samples.reset();
1861
+ };
1838
1862
  const onPointerDown = (e) => {
1839
1863
  if (activePointerId !== null) return;
1840
1864
  if (e.button !== void 0 && e.button !== 0) return;
1865
+ if (options.shouldStart && !options.shouldStart(e)) return;
1841
1866
  activePointerId = e.pointerId;
1842
1867
  activePointerType = e.pointerType || "touch";
1843
1868
  startCoord = coordOf(e);
1869
+ startCross = crossOf(e);
1844
1870
  lastCoord = startCoord;
1845
1871
  samples.reset();
1846
1872
  samples.push(e.timeStamp, startCoord);
1847
- try {
1848
- handle.setPointerCapture(e.pointerId);
1849
- } catch {
1873
+ if (options.deferStart) {
1874
+ pendingStart = true;
1875
+ return;
1850
1876
  }
1877
+ capture(e.pointerId);
1851
1878
  callbacks.onStart(startCoord, activePointerType);
1852
1879
  };
1853
1880
  const onPointerMove = (e) => {
@@ -1855,6 +1882,22 @@ var installGestures = (handle, mode, callbacks) => {
1855
1882
  const coord = coordOf(e);
1856
1883
  const rawDelta = coord - startCoord;
1857
1884
  const delta = rawDelta * sign;
1885
+ if (pendingStart) {
1886
+ const verdict = options.deferStart(e, delta, crossOf(e) - startCross);
1887
+ if (verdict === false) {
1888
+ releaseTracking();
1889
+ return;
1890
+ }
1891
+ if (verdict !== true) return;
1892
+ pendingStart = false;
1893
+ startCoord = coord;
1894
+ lastCoord = coord;
1895
+ samples.reset();
1896
+ samples.push(e.timeStamp, coord);
1897
+ capture(e.pointerId);
1898
+ callbacks.onStart(startCoord, activePointerType);
1899
+ return;
1900
+ }
1858
1901
  lastCoord = coord;
1859
1902
  samples.push(e.timeStamp, coord);
1860
1903
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1863,6 +1906,10 @@ var installGestures = (handle, mode, callbacks) => {
1863
1906
  };
1864
1907
  const finishGesture = (e) => {
1865
1908
  if (e.pointerId !== activePointerId) return;
1909
+ if (pendingStart) {
1910
+ releaseTracking();
1911
+ return;
1912
+ }
1866
1913
  const rawDelta = lastCoord - startCoord;
1867
1914
  const delta = rawDelta * sign;
1868
1915
  let velocity = 0;
@@ -1885,22 +1932,26 @@ var installGestures = (handle, mode, callbacks) => {
1885
1932
  };
1886
1933
  const onPointerCancel = (e) => {
1887
1934
  if (e.pointerId !== activePointerId) return;
1888
- activePointerId = null;
1889
- samples.reset();
1935
+ const wasPending = pendingStart;
1936
+ releaseTracking();
1937
+ if (wasPending) return;
1890
1938
  callbacks.onCancel?.();
1891
1939
  };
1892
1940
  handle.addEventListener("pointerdown", onPointerDown);
1893
1941
  handle.addEventListener("pointermove", onPointerMove);
1894
1942
  handle.addEventListener("pointerup", finishGesture);
1895
1943
  handle.addEventListener("pointercancel", onPointerCancel);
1944
+ const managesTouchAction = options.manageTouchAction !== false;
1896
1945
  const prevTouchAction = handle.style.touchAction;
1897
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1946
+ if (managesTouchAction) {
1947
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1948
+ }
1898
1949
  return () => {
1899
1950
  handle.removeEventListener("pointerdown", onPointerDown);
1900
1951
  handle.removeEventListener("pointermove", onPointerMove);
1901
1952
  handle.removeEventListener("pointerup", finishGesture);
1902
1953
  handle.removeEventListener("pointercancel", onPointerCancel);
1903
- handle.style.touchAction = prevTouchAction;
1954
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1904
1955
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1905
1956
  handle.releasePointerCapture(activePointerId);
1906
1957
  }
@@ -2019,7 +2070,7 @@ var GestureController = class {
2019
2070
  const restoreTo = this.dragStartSize;
2020
2071
  void this.deps.animateTo(restoreTo, 0);
2021
2072
  }
2022
- });
2073
+ }, this.deps.gestureOptions);
2023
2074
  return this.detach;
2024
2075
  }
2025
2076
  forceClearDragState() {
@@ -2031,6 +2082,74 @@ var GestureController = class {
2031
2082
  }
2032
2083
  };
2033
2084
 
2085
+ // src/core/primitives/drag-zones.ts
2086
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2087
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2088
+ var asElement = (target) => {
2089
+ const candidate = target;
2090
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2091
+ };
2092
+ function isDragAllowedFrom(target, mode) {
2093
+ const el = asElement(target);
2094
+ if (!el) return mode !== "zones";
2095
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2096
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2097
+ return true;
2098
+ }
2099
+
2100
+ // src/core/primitives/content-gesture.ts
2101
+ var CONTENT_DRAG_SLOP = 6;
2102
+ function decideContentGesture(input) {
2103
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2104
+ const cross = input.crossDelta ?? 0;
2105
+ const sharesScrollAxis = input.sharesScrollAxis ?? true;
2106
+ if (!sharesScrollAxis) {
2107
+ if (Math.abs(input.delta) < slop && Math.abs(cross) < slop) return "pending";
2108
+ if (Math.abs(input.delta) <= Math.abs(cross)) return "scroll";
2109
+ return "drag";
2110
+ }
2111
+ if (Math.abs(input.delta) < slop) return "pending";
2112
+ if (input.scrollTop > 0) return "scroll";
2113
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2114
+ return "drag";
2115
+ }
2116
+
2117
+ // src/core/primitives/logical-mode.ts
2118
+ var isLogicalMode = (mode) => mode === "start" || mode === "end";
2119
+ var readDirection = (el) => {
2120
+ if (!el) return "ltr";
2121
+ if (typeof getComputedStyle === "function") {
2122
+ try {
2123
+ if (getComputedStyle(el).direction === "rtl") return "rtl";
2124
+ } catch {
2125
+ }
2126
+ }
2127
+ try {
2128
+ const marked = el.closest?.("[dir]");
2129
+ if (marked) {
2130
+ return marked.getAttribute("dir")?.toLowerCase() === "rtl" ? "rtl" : "ltr";
2131
+ }
2132
+ } catch {
2133
+ }
2134
+ return "ltr";
2135
+ };
2136
+ var resolveMode = (mode, el) => {
2137
+ if (!isLogicalMode(mode)) return mode;
2138
+ const rtl = readDirection(el) === "rtl";
2139
+ if (mode === "start") return rtl ? "right" : "left";
2140
+ return rtl ? "left" : "right";
2141
+ };
2142
+
2143
+ // src/core/primitives/touch-scroll-guard.ts
2144
+ function installTouchScrollGuard(surface, isDragging) {
2145
+ const onTouchMove = (e) => {
2146
+ if (!isDragging()) return;
2147
+ if (e.cancelable) e.preventDefault();
2148
+ };
2149
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2150
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2151
+ }
2152
+
2034
2153
  // src/core/primitives/aria-slider-writer.ts
2035
2154
  var AriaSliderWriter = class {
2036
2155
  constructor(handle, mode) {
@@ -2383,9 +2502,11 @@ function resolveEngineOptions(opts, extras) {
2383
2502
  }
2384
2503
  }
2385
2504
  return {
2386
- mode: opts.mode ?? "bottom",
2505
+ mode: resolveMode(opts.mode ?? "bottom", opts.element),
2387
2506
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2388
2507
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2508
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2509
+ dragFromContent: opts.dragFromContent ?? true,
2389
2510
  rubberBandEnabled: opts.rubberBand ?? true,
2390
2511
  closeOnBack: opts.closeOnBack ?? false,
2391
2512
  persistent: opts.persistent ?? false,
@@ -2471,6 +2592,8 @@ var BottomSheetCore = class {
2471
2592
  __publicField(this, "mode");
2472
2593
  __publicField(this, "flickVelocity");
2473
2594
  __publicField(this, "dragThreshold");
2595
+ __publicField(this, "dragFromMode");
2596
+ __publicField(this, "dragFromContentDefault");
2474
2597
  __publicField(this, "rubberBandEnabled");
2475
2598
  __publicField(this, "scrim");
2476
2599
  __publicField(this, "aria");
@@ -2483,13 +2606,18 @@ var BottomSheetCore = class {
2483
2606
  __publicField(this, "disableDragFlag");
2484
2607
  __publicField(this, "closeOnRouteChange");
2485
2608
  __publicField(this, "maxHeight");
2609
+ __publicField(this, "maxHeightDeps");
2486
2610
  __publicField(this, "snapPointsRaw");
2487
2611
  __publicField(this, "snaps");
2488
2612
  __publicField(this, "activeId");
2489
2613
  __publicField(this, "size", 0);
2490
2614
  __publicField(this, "gesture");
2615
+ __publicField(this, "contentGesture");
2616
+ __publicField(this, "detachSheetGesture", null);
2617
+ __publicField(this, "detachSliderKeyboard", null);
2491
2618
  __publicField(this, "rootEl", null);
2492
2619
  __publicField(this, "destroyed", false);
2620
+ __publicField(this, "restClosed", false);
2493
2621
  __publicField(this, "currentViewTransition", null);
2494
2622
  __publicField(this, "currentAbort", new AbortController());
2495
2623
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2538,10 +2666,13 @@ var BottomSheetCore = class {
2538
2666
  this.screenComponent = opts.scrim ?? opts.screenComponent;
2539
2667
  this.scrimParent = this.screenComponent?.parentElement ?? null;
2540
2668
  this.mode = resolved.mode;
2669
+ this.element.dataset.mode = this.mode;
2541
2670
  this.transformTemplate = buildTransformTemplate(this.mode);
2542
2671
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2543
2672
  this.flickVelocity = resolved.flickVelocity;
2544
2673
  this.dragThreshold = resolved.dragThreshold;
2674
+ this.dragFromMode = resolved.dragFrom;
2675
+ this.dragFromContentDefault = resolved.dragFromContent;
2545
2676
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2546
2677
  this.scrim = new ScrimController(
2547
2678
  {
@@ -2568,7 +2699,7 @@ var BottomSheetCore = class {
2568
2699
  getRootEl: () => this.rootEl,
2569
2700
  applySize: (size) => this.applySize(size),
2570
2701
  getSize: () => this.size,
2571
- isDragging: () => this.gesture?.isDragging ?? false,
2702
+ isDragging: () => this.isDraggingAny(),
2572
2703
  applyAux: (size) => this.applySize(size, true),
2573
2704
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2574
2705
  },
@@ -2602,7 +2733,7 @@ var BottomSheetCore = class {
2602
2733
  auditVhUsage(this.snapPointsRaw);
2603
2734
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2604
2735
  this.rootEl = this.element.closest(".bs-root");
2605
- this.maxHeight = createMaxHeightController({
2736
+ this.maxHeightDeps = {
2606
2737
  element: this.element,
2607
2738
  mode: this.mode,
2608
2739
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
@@ -2610,7 +2741,8 @@ var BottomSheetCore = class {
2610
2741
  this.snaps.setMaxAxisSize(size);
2611
2742
  },
2612
2743
  recompute: () => this.recompute()
2613
- });
2744
+ };
2745
+ this.maxHeight = createMaxHeightController(this.maxHeightDeps);
2614
2746
  this.fitDeps = {
2615
2747
  element: this.element,
2616
2748
  scrollContainer: this.scrollContainer,
@@ -2629,6 +2761,7 @@ var BottomSheetCore = class {
2629
2761
  const initial = this.snaps.findById(this.activeId);
2630
2762
  if (initial) this.size = initial.size;
2631
2763
  this.applySize(this.size);
2764
+ this.setRestClosed(this.size === 0);
2632
2765
  this.updateAriaSlider();
2633
2766
  this.scrollCache = createScrollCache({
2634
2767
  scrollContainer: this.scrollContainer,
@@ -2659,7 +2792,7 @@ var BottomSheetCore = class {
2659
2792
  (p) => p.size === "fit" || p.size === "content"
2660
2793
  ),
2661
2794
  isDestroyed: () => this.destroyed,
2662
- isDragging: () => this.gesture?.isDragging ?? false,
2795
+ isDragging: () => this.isDraggingAny(),
2663
2796
  recompute: () => this.recompute()
2664
2797
  })
2665
2798
  );
@@ -2685,7 +2818,7 @@ var BottomSheetCore = class {
2685
2818
  autoCollapseAfter
2686
2819
  },
2687
2820
  isDestroyed: () => this.destroyed,
2688
- isDragging: () => this.gesture?.isDragging ?? false,
2821
+ isDragging: () => this.isDraggingAny(),
2689
2822
  isAnimating: () => this.animation.isAnimating,
2690
2823
  isTopSheet: () => this.isTopSheet,
2691
2824
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2715,6 +2848,7 @@ var BottomSheetCore = class {
2715
2848
  void this.snapTo(id);
2716
2849
  },
2717
2850
  close: (reason) => this.close(reason),
2851
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2718
2852
  on: (event, fn) => this.on(event, fn),
2719
2853
  addTeardown: (fn) => this.teardowns.add(fn)
2720
2854
  };
@@ -2743,7 +2877,7 @@ var BottomSheetCore = class {
2743
2877
  return {
2744
2878
  size: this.size,
2745
2879
  activeId: this.activeId,
2746
- isDragging: this.gesture?.isDragging ?? false,
2880
+ isDragging: this.isDraggingAny(),
2747
2881
  isAnimating: this.animation.isAnimating,
2748
2882
  progress: this.computeProgress(this.size)
2749
2883
  };
@@ -2863,9 +2997,16 @@ var BottomSheetCore = class {
2863
2997
  progress: this.computeProgress(this.size)
2864
2998
  });
2865
2999
  if (withHaptic) this.haptic();
3000
+ this.setRestClosed(targetSize === 0);
2866
3001
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2867
3002
  if (targetSize === 0) this.emitCloseSequence();
2868
3003
  }
3004
+ setRestClosed(closed) {
3005
+ if (closed === this.restClosed) return;
3006
+ this.restClosed = closed;
3007
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
3008
+ else this.element.removeAttribute("data-bs-rest");
3009
+ }
2869
3010
  emitOpenSequence(id) {
2870
3011
  this.emit("open", { id });
2871
3012
  this.handleOpen();
@@ -2921,6 +3062,51 @@ var BottomSheetCore = class {
2921
3062
  if (this.destroyed) return;
2922
3063
  this.disableDragFlag = value;
2923
3064
  }
3065
+ setDragFromContent(value) {
3066
+ if (this.destroyed) return;
3067
+ this.dragFromContentDefault = value;
3068
+ }
3069
+ setDragFrom(mode) {
3070
+ if (this.destroyed || mode === this.dragFromMode) return;
3071
+ this.gesture?.forceClearDragState();
3072
+ this.detachSheetGesture?.();
3073
+ this.dragFromMode = mode;
3074
+ this.mountSheetGesture();
3075
+ }
3076
+ getDragFrom() {
3077
+ return this.dragFromMode;
3078
+ }
3079
+ setMode(mode) {
3080
+ if (this.destroyed) return;
3081
+ const next = resolveMode(mode, this.element);
3082
+ if (next === this.mode) return;
3083
+ const wasVertical = this.mode === "bottom" || this.mode === "top";
3084
+ const nowVertical = next === "bottom" || next === "top";
3085
+ this.gesture?.forceClearDragState();
3086
+ this.detachSheetGesture?.();
3087
+ this.animation.cancel();
3088
+ this.element.style.removeProperty(layoutAxis(this.mode));
3089
+ this.mode = next;
3090
+ this.element.dataset.mode = next;
3091
+ this.transformTemplate = buildTransformTemplate(next);
3092
+ this.maxHeightDeps.mode = next;
3093
+ this.fitDeps.mode = next;
3094
+ this.snaps.setMode(next);
3095
+ this.scrim.setSheetMode(next);
3096
+ if (wasVertical !== nowVertical) {
3097
+ this.aria = new AriaSliderWriter(this.handle, next);
3098
+ }
3099
+ this.detachSliderKeyboard?.();
3100
+ this.mountSliderKeyboard();
3101
+ this.mountSheetGesture();
3102
+ this.recompute();
3103
+ const active = this.snaps.findById(this.activeId);
3104
+ if (active) this.applySize(active.size);
3105
+ this.updateAriaSlider();
3106
+ }
3107
+ getMode() {
3108
+ return this.mode;
3109
+ }
2924
3110
  isTop() {
2925
3111
  return this.isTopSheet;
2926
3112
  }
@@ -3075,12 +3261,24 @@ var BottomSheetCore = class {
3075
3261
  auditVhUsage(points);
3076
3262
  this.auditDuplicateSnapIds(points);
3077
3263
  this.snaps.setRaw(points);
3078
- if (allowed) this.snaps.setAllowedIds(allowed);
3264
+ if (allowed) {
3265
+ this.snaps.setAllowedIds(allowed);
3266
+ } else {
3267
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3268
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3269
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3270
+ }
3079
3271
  this.scrollCache.clear();
3080
3272
  this.scrim.invalidateOpacityCache();
3081
3273
  const current = this.snaps.findById(this.activeId);
3082
3274
  if (current) {
3083
3275
  this.applySize(current.size);
3276
+ } else {
3277
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3278
+ if (fallback) {
3279
+ this.activeId = fallback.id;
3280
+ this.applySize(fallback.size);
3281
+ }
3084
3282
  }
3085
3283
  this.updateAriaSlider();
3086
3284
  }
@@ -3090,7 +3288,7 @@ var BottomSheetCore = class {
3090
3288
  this.snaps.recompute();
3091
3289
  this.maxHeight.clampTo();
3092
3290
  this.scrim.invalidateOpacityCache();
3093
- if (this.gesture?.isDragging) return;
3291
+ if (this.isDraggingAny()) return;
3094
3292
  this.healActiveSnap();
3095
3293
  }
3096
3294
  healActiveSnap() {
@@ -3112,6 +3310,8 @@ var BottomSheetCore = class {
3112
3310
  this.bus.clear();
3113
3311
  this.animation.cancel();
3114
3312
  this.gesture?.forceClearDragState();
3313
+ this.contentGesture?.forceClearDragState();
3314
+ this.setRestClosed(false);
3115
3315
  this.teardowns.drain();
3116
3316
  this.scrim.destroy();
3117
3317
  this.animation.destroy();
@@ -3224,11 +3424,12 @@ var BottomSheetCore = class {
3224
3424
  const scale = Math.max(1 - depth * 0.04, 0.86);
3225
3425
  this.element.style.scale = scale === 1 ? "" : String(scale);
3226
3426
  }
3227
- attach() {
3228
- this.gesture = new GestureController({
3229
- handle: this.handle,
3427
+ buildGestureDeps(surface, gestureOptions) {
3428
+ return {
3429
+ handle: surface,
3230
3430
  element: this.element,
3231
3431
  mode: this.mode,
3432
+ gestureOptions,
3232
3433
  getRoot: () => this.rootEl,
3233
3434
  getDragContext: () => {
3234
3435
  const buf = this.dragContextBuf;
@@ -3247,8 +3448,90 @@ var BottomSheetCore = class {
3247
3448
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3248
3449
  emit: (event, payload) => this.emit(event, payload),
3249
3450
  listenerCount: (event) => this.bus.listenerCount(event)
3451
+ };
3452
+ }
3453
+ isContentDragAllowed() {
3454
+ if (this.disableDragFlag) return false;
3455
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3456
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3457
+ }
3458
+ decideContentDrag(surface, e, delta, crossDelta) {
3459
+ if (this.destroyed) return false;
3460
+ if (e.pointerType === "mouse") return false;
3461
+ if (this.gesture?.isDragging) return false;
3462
+ if (!this.isContentDragAllowed()) return false;
3463
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3464
+ const decision = decideContentGesture({
3465
+ delta,
3466
+ crossDelta,
3467
+ sharesScrollAxis: this.mode === "bottom" || this.mode === "top",
3468
+ scrollTop: surface.scrollTop,
3469
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3470
+ });
3471
+ if (decision === "pending") return null;
3472
+ return decision === "drag";
3473
+ }
3474
+ attachDragSurface(surface, kind) {
3475
+ if (this.destroyed || kind !== "content") return;
3476
+ if (this.contentGesture) return;
3477
+ const controller = new GestureController(
3478
+ this.buildGestureDeps(surface, {
3479
+ manageTouchAction: false,
3480
+ deferStart: (e, delta, crossDelta) => this.decideContentDrag(surface, e, delta, crossDelta)
3481
+ })
3482
+ );
3483
+ this.contentGesture = controller;
3484
+ const detachGestures = controller.install();
3485
+ const detachGuard = installTouchScrollGuard(
3486
+ surface,
3487
+ () => controller.isDragging
3488
+ );
3489
+ return () => {
3490
+ detachGuard();
3491
+ detachGestures();
3492
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3493
+ };
3494
+ }
3495
+ sheetDragSurface() {
3496
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3497
+ }
3498
+ canStartSheetDrag(e) {
3499
+ if (this.contentGesture?.isDragging) return false;
3500
+ const target = e.target;
3501
+ const scoped = typeof target?.closest === "function";
3502
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3503
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3504
+ return true;
3505
+ }
3506
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3507
+ if (this.dragFromMode === "handle") return true;
3508
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3509
+ return false;
3510
+ }
3511
+ return true;
3512
+ }
3513
+ mountSheetGesture() {
3514
+ const surface = this.sheetDragSurface();
3515
+ const controller = new GestureController(
3516
+ this.buildGestureDeps(surface, {
3517
+ shouldStart: (e) => this.canStartSheetDrag(e),
3518
+ manageTouchAction: this.dragFromMode === "handle"
3519
+ })
3520
+ );
3521
+ this.gesture = controller;
3522
+ const detachGestures = controller.install();
3523
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3524
+ this.detachSheetGesture = () => {
3525
+ detachGuard?.();
3526
+ detachGestures();
3527
+ };
3528
+ }
3529
+ attach() {
3530
+ this.mountSheetGesture();
3531
+ this.teardowns.add(() => {
3532
+ this.detachSheetGesture?.();
3533
+ this.detachSheetGesture = null;
3250
3534
  });
3251
- this.teardowns.add(this.gesture.install());
3252
3535
  try {
3253
3536
  this.attachFeatures();
3254
3537
  } catch (err) {
@@ -3285,7 +3568,7 @@ var BottomSheetCore = class {
3285
3568
  element: this.element,
3286
3569
  getMode: () => this.mode,
3287
3570
  isDestroyed: () => this.destroyed,
3288
- isDragging: () => this.gesture?.isDragging ?? false,
3571
+ isDragging: () => this.isDraggingAny(),
3289
3572
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3290
3573
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3291
3574
  getSize: () => this.size,
@@ -3320,7 +3603,11 @@ var BottomSheetCore = class {
3320
3603
  );
3321
3604
  }
3322
3605
  }
3323
- this.teardowns.add(installSliderKeyboard({
3606
+ this.mountSliderKeyboard();
3607
+ this.scrim.attach();
3608
+ }
3609
+ mountSliderKeyboard() {
3610
+ const detach = installSliderKeyboard({
3324
3611
  handle: this.handle,
3325
3612
  mode: this.mode,
3326
3613
  isDestroyed: () => this.destroyed,
@@ -3329,8 +3616,12 @@ var BottomSheetCore = class {
3329
3616
  snapTo: (id) => {
3330
3617
  void this.snapTo(id);
3331
3618
  }
3332
- }));
3333
- this.scrim.attach();
3619
+ });
3620
+ this.detachSliderKeyboard = detach;
3621
+ this.teardowns.add(() => {
3622
+ this.detachSliderKeyboard?.();
3623
+ this.detachSliderKeyboard = null;
3624
+ });
3334
3625
  }
3335
3626
  settleAfterDrag(delta, velocity, kind = "touch") {
3336
3627
  const signal = this.newCycle();
@@ -3383,8 +3674,11 @@ var BottomSheetCore = class {
3383
3674
  );
3384
3675
  });
3385
3676
  }
3677
+ isDraggingAny() {
3678
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3679
+ }
3386
3680
  clearWillChangeIfIdle() {
3387
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3681
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3388
3682
  this.element.style.willChange = "";
3389
3683
  }
3390
3684
  haptic() {
@@ -3397,8 +3691,9 @@ var BottomSheetCore = class {
3397
3691
  }
3398
3692
  applySize(size, skipTransform = false) {
3399
3693
  const cap = this.snaps.getMaxAxisSize();
3400
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3694
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3401
3695
  this.size = clamped;
3696
+ if (clamped > 0) this.setRestClosed(false);
3402
3697
  const offset = cap - clamped;
3403
3698
  const style = this.element.style;
3404
3699
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3778,50 +4073,10 @@ function installAutoCollapse(deps) {
3778
4073
  };
3779
4074
  }
3780
4075
 
3781
- // src/core/features/content-swipe.ts
3782
- var CONTENT_PULL_THRESHOLD = 60;
3783
- function installContentSwipe(deps) {
3784
- const { container } = deps;
3785
- let startY = 0;
3786
- let startScrollTop = 0;
3787
- let moved = false;
3788
- const onTouchStart = (e) => {
3789
- const t = e.touches[0];
3790
- if (!t) return;
3791
- startY = t.clientY;
3792
- startScrollTop = container.scrollTop;
3793
- moved = false;
3794
- };
3795
- const onTouchMove = (e) => {
3796
- const t = e.touches[0];
3797
- if (!t) return;
3798
- if (Math.abs(t.clientY - startY) > CONTENT_PULL_THRESHOLD) moved = true;
3799
- };
3800
- const onTouchEnd = (e) => {
3801
- if (!moved) return;
3802
- if (deps.isDragging() || deps.isAnimating()) return;
3803
- if (container.scrollTop !== startScrollTop) return;
3804
- const t = e.changedTouches[0];
3805
- if (!t) return;
3806
- const deltaY = t.clientY - startY;
3807
- const atTop = container.scrollTop <= 0;
3808
- const allowed = deps.getAllowedIds();
3809
- const idx = allowed.indexOf(deps.getActiveId());
3810
- if (idx === -1) return;
3811
- if (atTop && deltaY > CONTENT_PULL_THRESHOLD && idx > 0) {
3812
- deps.snapTo(allowed[idx - 1]);
3813
- } else if (deltaY < -CONTENT_PULL_THRESHOLD && idx < allowed.length - 1) {
3814
- deps.snapTo(allowed[idx + 1]);
3815
- }
3816
- };
3817
- container.addEventListener("touchstart", onTouchStart, { passive: true });
3818
- container.addEventListener("touchmove", onTouchMove, { passive: true });
3819
- container.addEventListener("touchend", onTouchEnd, { passive: true });
3820
- return () => {
3821
- container.removeEventListener("touchstart", onTouchStart);
3822
- container.removeEventListener("touchmove", onTouchMove);
3823
- container.removeEventListener("touchend", onTouchEnd);
3824
- };
4076
+ // src/core/features/content-drag.ts
4077
+ function installContentDrag(deps) {
4078
+ const detach = deps.attachDragSurface(deps.container, "content");
4079
+ return () => detach?.();
3825
4080
  }
3826
4081
 
3827
4082
  // src/core/features/visual-viewport.ts
@@ -3929,13 +4184,9 @@ function contentSwipeFeature() {
3929
4184
  stage: "attach",
3930
4185
  install: (ctx) => {
3931
4186
  if (!ctx.scrollContainer) return;
3932
- return installContentSwipe({
4187
+ return installContentDrag({
3933
4188
  container: ctx.scrollContainer,
3934
- isDragging: ctx.isDragging,
3935
- isAnimating: ctx.isAnimating,
3936
- getAllowedIds: ctx.allowedIdsBySize,
3937
- getActiveId: ctx.getActiveId,
3938
- snapTo: ctx.snapTo
4189
+ attachDragSurface: ctx.attachDragSurface
3939
4190
  });
3940
4191
  }
3941
4192
  };
@@ -4051,6 +4302,16 @@ function useEnginePropSync(engineRef, opts) {
4051
4302
  engineRef.current?.setDisableDrag(opts.disableDrag);
4052
4303
  }
4053
4304
  }, [opts.disableDrag]);
4305
+ useEffect(() => {
4306
+ if (opts.dragFrom !== void 0) {
4307
+ engineRef.current?.setDragFrom(opts.dragFrom);
4308
+ }
4309
+ }, [opts.dragFrom]);
4310
+ useEffect(() => {
4311
+ if (opts.dragFromContent !== void 0) {
4312
+ engineRef.current?.setDragFromContent(opts.dragFromContent);
4313
+ }
4314
+ }, [opts.disableDrag]);
4054
4315
  }
4055
4316
 
4056
4317
  // src/core/features/teleport.ts
@@ -4199,7 +4460,9 @@ function useBottomSheet(opts) {
4199
4460
  maxHeight: opts.maxHeight,
4200
4461
  persistent: opts.persistent,
4201
4462
  disableClose: opts.disableClose,
4202
- disableDrag: opts.disableDrag
4463
+ disableDrag: opts.disableDrag,
4464
+ dragFrom: opts.dragFrom,
4465
+ dragFromContent: opts.dragFromContent
4203
4466
  });
4204
4467
  const subscribe = useCallback((fn) => {
4205
4468
  subscribersRef.current.add(fn);
@@ -4267,7 +4530,6 @@ function useBottomSheet(opts) {
4267
4530
  addAnchor,
4268
4531
  setScrimStages,
4269
4532
  recompute,
4270
- engine: engineRef.current,
4271
4533
  getEngine
4272
4534
  }),
4273
4535
  [