@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/solid.cjs CHANGED
@@ -7,7 +7,7 @@ var __defProp = Object.defineProperty;
7
7
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
8
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
9
9
 
10
- // src/core/lifecycle/sheetStack.ts
10
+ // src/core/lifecycle/sheet-stack.ts
11
11
  var BASE_Z = 100;
12
12
  var STEP = 10;
13
13
  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);
@@ -318,6 +318,11 @@ var SnapResolver = class {
318
318
  this.raw = raw;
319
319
  this.recompute();
320
320
  }
321
+ setMode(mode) {
322
+ if (mode === this.mode) return;
323
+ this.mode = mode;
324
+ this.recompute();
325
+ }
321
326
  setAllowedIds(ids) {
322
327
  this.allowedIds = ids;
323
328
  this.rangeCache = null;
@@ -901,6 +906,11 @@ var ScrimController = class {
901
906
  if (this.isDestroyed()) return;
902
907
  this.applyScrimStyles({ interactive });
903
908
  }
909
+ setSheetMode(mode) {
910
+ if (this.isDestroyed() || mode === this.mode) return;
911
+ this.mode = mode;
912
+ this.invalidateOpacityCache();
913
+ }
904
914
  setScrimMode(mode, applySize) {
905
915
  if (this.isDestroyed()) return;
906
916
  if (mode === this.scrimMode) return;
@@ -1519,7 +1529,7 @@ var AnimationRunner = class {
1519
1529
  }
1520
1530
  };
1521
1531
 
1522
- // src/core/lifecycle/focusTrap.ts
1532
+ // src/core/lifecycle/focus-trap.ts
1523
1533
  var FOCUSABLE = [
1524
1534
  "a[href]",
1525
1535
  "area[href]",
@@ -1637,7 +1647,7 @@ var installFocusTrap = (container, options = {}) => {
1637
1647
  };
1638
1648
  };
1639
1649
 
1640
- // src/core/lifecycle/scrollLock.ts
1650
+ // src/core/lifecycle/scroll-lock.ts
1641
1651
  var lockCount = 0;
1642
1652
  var savedStyles = null;
1643
1653
  var lockBodyScroll = () => {
@@ -1819,30 +1829,47 @@ var SampleRing = class {
1819
1829
  this.size = 0;
1820
1830
  }
1821
1831
  };
1822
- var installGestures = (handle, mode, callbacks) => {
1832
+ var installGestures = (handle, mode, callbacks, options = {}) => {
1823
1833
  let activePointerId = null;
1824
1834
  let activePointerType = "touch";
1825
1835
  let startCoord = 0;
1836
+ let startCross = 0;
1826
1837
  let lastCoord = 0;
1838
+ let pendingStart = false;
1827
1839
  const VELOCITY_WINDOW_MS = (kind) => kind === "mouse" ? 160 : 120;
1828
1840
  const MAX_SAMPLES = 32;
1829
1841
  const samples = new SampleRing(MAX_SAMPLES);
1830
1842
  const axis = isAxisVertical(mode) ? "Y" : "X";
1831
1843
  const sign = mode === "bottom" || mode === "right" ? -1 : 1;
1832
1844
  const coordOf = (e) => axis === "Y" ? e.clientY : e.clientX;
1845
+ const crossOf = (e) => axis === "Y" ? e.clientX : e.clientY;
1846
+ const capture = (pointerId) => {
1847
+ try {
1848
+ handle.setPointerCapture(pointerId);
1849
+ } catch {
1850
+ }
1851
+ };
1852
+ const releaseTracking = () => {
1853
+ activePointerId = null;
1854
+ pendingStart = false;
1855
+ samples.reset();
1856
+ };
1833
1857
  const onPointerDown = (e) => {
1834
1858
  if (activePointerId !== null) return;
1835
1859
  if (e.button !== void 0 && e.button !== 0) return;
1860
+ if (options.shouldStart && !options.shouldStart(e)) return;
1836
1861
  activePointerId = e.pointerId;
1837
1862
  activePointerType = e.pointerType || "touch";
1838
1863
  startCoord = coordOf(e);
1864
+ startCross = crossOf(e);
1839
1865
  lastCoord = startCoord;
1840
1866
  samples.reset();
1841
1867
  samples.push(e.timeStamp, startCoord);
1842
- try {
1843
- handle.setPointerCapture(e.pointerId);
1844
- } catch {
1868
+ if (options.deferStart) {
1869
+ pendingStart = true;
1870
+ return;
1845
1871
  }
1872
+ capture(e.pointerId);
1846
1873
  callbacks.onStart(startCoord, activePointerType);
1847
1874
  };
1848
1875
  const onPointerMove = (e) => {
@@ -1850,6 +1877,22 @@ var installGestures = (handle, mode, callbacks) => {
1850
1877
  const coord = coordOf(e);
1851
1878
  const rawDelta = coord - startCoord;
1852
1879
  const delta = rawDelta * sign;
1880
+ if (pendingStart) {
1881
+ const verdict = options.deferStart(e, delta, crossOf(e) - startCross);
1882
+ if (verdict === false) {
1883
+ releaseTracking();
1884
+ return;
1885
+ }
1886
+ if (verdict !== true) return;
1887
+ pendingStart = false;
1888
+ startCoord = coord;
1889
+ lastCoord = coord;
1890
+ samples.reset();
1891
+ samples.push(e.timeStamp, coord);
1892
+ capture(e.pointerId);
1893
+ callbacks.onStart(startCoord, activePointerType);
1894
+ return;
1895
+ }
1853
1896
  lastCoord = coord;
1854
1897
  samples.push(e.timeStamp, coord);
1855
1898
  const cutoff = e.timeStamp - VELOCITY_WINDOW_MS(activePointerType);
@@ -1858,6 +1901,10 @@ var installGestures = (handle, mode, callbacks) => {
1858
1901
  };
1859
1902
  const finishGesture = (e) => {
1860
1903
  if (e.pointerId !== activePointerId) return;
1904
+ if (pendingStart) {
1905
+ releaseTracking();
1906
+ return;
1907
+ }
1861
1908
  const rawDelta = lastCoord - startCoord;
1862
1909
  const delta = rawDelta * sign;
1863
1910
  let velocity = 0;
@@ -1880,22 +1927,26 @@ var installGestures = (handle, mode, callbacks) => {
1880
1927
  };
1881
1928
  const onPointerCancel = (e) => {
1882
1929
  if (e.pointerId !== activePointerId) return;
1883
- activePointerId = null;
1884
- samples.reset();
1930
+ const wasPending = pendingStart;
1931
+ releaseTracking();
1932
+ if (wasPending) return;
1885
1933
  callbacks.onCancel?.();
1886
1934
  };
1887
1935
  handle.addEventListener("pointerdown", onPointerDown);
1888
1936
  handle.addEventListener("pointermove", onPointerMove);
1889
1937
  handle.addEventListener("pointerup", finishGesture);
1890
1938
  handle.addEventListener("pointercancel", onPointerCancel);
1939
+ const managesTouchAction = options.manageTouchAction !== false;
1891
1940
  const prevTouchAction = handle.style.touchAction;
1892
- handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1941
+ if (managesTouchAction) {
1942
+ handle.style.touchAction = isAxisVertical(mode) ? "pan-x" : "pan-y";
1943
+ }
1893
1944
  return () => {
1894
1945
  handle.removeEventListener("pointerdown", onPointerDown);
1895
1946
  handle.removeEventListener("pointermove", onPointerMove);
1896
1947
  handle.removeEventListener("pointerup", finishGesture);
1897
1948
  handle.removeEventListener("pointercancel", onPointerCancel);
1898
- handle.style.touchAction = prevTouchAction;
1949
+ if (managesTouchAction) handle.style.touchAction = prevTouchAction;
1899
1950
  if (activePointerId !== null && handle.hasPointerCapture(activePointerId)) {
1900
1951
  handle.releasePointerCapture(activePointerId);
1901
1952
  }
@@ -2014,7 +2065,7 @@ var GestureController = class {
2014
2065
  const restoreTo = this.dragStartSize;
2015
2066
  void this.deps.animateTo(restoreTo, 0);
2016
2067
  }
2017
- });
2068
+ }, this.deps.gestureOptions);
2018
2069
  return this.detach;
2019
2070
  }
2020
2071
  forceClearDragState() {
@@ -2026,6 +2077,74 @@ var GestureController = class {
2026
2077
  }
2027
2078
  };
2028
2079
 
2080
+ // src/core/primitives/drag-zones.ts
2081
+ var DRAG_ZONE_SELECTOR = "[data-bs-drag]";
2082
+ var NO_DRAG_SELECTOR = "[data-bs-no-drag]";
2083
+ var asElement = (target) => {
2084
+ const candidate = target;
2085
+ return candidate && typeof candidate.closest === "function" ? candidate : null;
2086
+ };
2087
+ function isDragAllowedFrom(target, mode) {
2088
+ const el = asElement(target);
2089
+ if (!el) return mode !== "zones";
2090
+ if (el.closest(NO_DRAG_SELECTOR)) return false;
2091
+ if (mode === "zones") return el.closest(DRAG_ZONE_SELECTOR) !== null;
2092
+ return true;
2093
+ }
2094
+
2095
+ // src/core/primitives/content-gesture.ts
2096
+ var CONTENT_DRAG_SLOP = 6;
2097
+ function decideContentGesture(input) {
2098
+ const slop = input.slop ?? CONTENT_DRAG_SLOP;
2099
+ const cross = input.crossDelta ?? 0;
2100
+ const sharesScrollAxis = input.sharesScrollAxis ?? true;
2101
+ if (!sharesScrollAxis) {
2102
+ if (Math.abs(input.delta) < slop && Math.abs(cross) < slop) return "pending";
2103
+ if (Math.abs(input.delta) <= Math.abs(cross)) return "scroll";
2104
+ return "drag";
2105
+ }
2106
+ if (Math.abs(input.delta) < slop) return "pending";
2107
+ if (input.scrollTop > 0) return "scroll";
2108
+ if (input.delta > 0 && input.atMaxSnap) return "scroll";
2109
+ return "drag";
2110
+ }
2111
+
2112
+ // src/core/primitives/logical-mode.ts
2113
+ var isLogicalMode = (mode) => mode === "start" || mode === "end";
2114
+ var readDirection = (el) => {
2115
+ if (!el) return "ltr";
2116
+ if (typeof getComputedStyle === "function") {
2117
+ try {
2118
+ if (getComputedStyle(el).direction === "rtl") return "rtl";
2119
+ } catch {
2120
+ }
2121
+ }
2122
+ try {
2123
+ const marked = el.closest?.("[dir]");
2124
+ if (marked) {
2125
+ return marked.getAttribute("dir")?.toLowerCase() === "rtl" ? "rtl" : "ltr";
2126
+ }
2127
+ } catch {
2128
+ }
2129
+ return "ltr";
2130
+ };
2131
+ var resolveMode = (mode, el) => {
2132
+ if (!isLogicalMode(mode)) return mode;
2133
+ const rtl = readDirection(el) === "rtl";
2134
+ if (mode === "start") return rtl ? "right" : "left";
2135
+ return rtl ? "left" : "right";
2136
+ };
2137
+
2138
+ // src/core/primitives/touch-scroll-guard.ts
2139
+ function installTouchScrollGuard(surface, isDragging) {
2140
+ const onTouchMove = (e) => {
2141
+ if (!isDragging()) return;
2142
+ if (e.cancelable) e.preventDefault();
2143
+ };
2144
+ surface.addEventListener("touchmove", onTouchMove, { passive: false });
2145
+ return () => surface.removeEventListener("touchmove", onTouchMove);
2146
+ }
2147
+
2029
2148
  // src/core/primitives/aria-slider-writer.ts
2030
2149
  var AriaSliderWriter = class {
2031
2150
  constructor(handle, mode) {
@@ -2378,9 +2497,11 @@ function resolveEngineOptions(opts, extras) {
2378
2497
  }
2379
2498
  }
2380
2499
  return {
2381
- mode: opts.mode ?? "bottom",
2500
+ mode: resolveMode(opts.mode ?? "bottom", opts.element),
2382
2501
  flickVelocity: opts.flickVelocity ?? DEFAULT_FLICK_VELOCITY,
2383
2502
  dragThreshold: opts.dragThreshold ?? DEFAULT_DRAG_THRESHOLD,
2503
+ dragFrom: opts.dragFrom ?? (opts.handle ? "handle" : "sheet"),
2504
+ dragFromContent: opts.dragFromContent ?? true,
2384
2505
  rubberBandEnabled: opts.rubberBand ?? true,
2385
2506
  closeOnBack: opts.closeOnBack ?? false,
2386
2507
  persistent: opts.persistent ?? false,
@@ -2466,6 +2587,8 @@ var BottomSheetCore = class {
2466
2587
  __publicField(this, "mode");
2467
2588
  __publicField(this, "flickVelocity");
2468
2589
  __publicField(this, "dragThreshold");
2590
+ __publicField(this, "dragFromMode");
2591
+ __publicField(this, "dragFromContentDefault");
2469
2592
  __publicField(this, "rubberBandEnabled");
2470
2593
  __publicField(this, "scrim");
2471
2594
  __publicField(this, "aria");
@@ -2478,13 +2601,18 @@ var BottomSheetCore = class {
2478
2601
  __publicField(this, "disableDragFlag");
2479
2602
  __publicField(this, "closeOnRouteChange");
2480
2603
  __publicField(this, "maxHeight");
2604
+ __publicField(this, "maxHeightDeps");
2481
2605
  __publicField(this, "snapPointsRaw");
2482
2606
  __publicField(this, "snaps");
2483
2607
  __publicField(this, "activeId");
2484
2608
  __publicField(this, "size", 0);
2485
2609
  __publicField(this, "gesture");
2610
+ __publicField(this, "contentGesture");
2611
+ __publicField(this, "detachSheetGesture", null);
2612
+ __publicField(this, "detachSliderKeyboard", null);
2486
2613
  __publicField(this, "rootEl", null);
2487
2614
  __publicField(this, "destroyed", false);
2615
+ __publicField(this, "restClosed", false);
2488
2616
  __publicField(this, "currentViewTransition", null);
2489
2617
  __publicField(this, "currentAbort", new AbortController());
2490
2618
  __publicField(this, "sizeWriteSentinel", new WriteSentinel());
@@ -2533,10 +2661,13 @@ var BottomSheetCore = class {
2533
2661
  this.screenComponent = opts.scrim ?? opts.screenComponent;
2534
2662
  this.scrimParent = this.screenComponent?.parentElement ?? null;
2535
2663
  this.mode = resolved.mode;
2664
+ this.element.dataset.mode = this.mode;
2536
2665
  this.transformTemplate = buildTransformTemplate(this.mode);
2537
2666
  this.aria = new AriaSliderWriter(this.handle, this.mode);
2538
2667
  this.flickVelocity = resolved.flickVelocity;
2539
2668
  this.dragThreshold = resolved.dragThreshold;
2669
+ this.dragFromMode = resolved.dragFrom;
2670
+ this.dragFromContentDefault = resolved.dragFromContent;
2540
2671
  this.rubberBandEnabled = resolved.rubberBandEnabled;
2541
2672
  this.scrim = new ScrimController(
2542
2673
  {
@@ -2563,7 +2694,7 @@ var BottomSheetCore = class {
2563
2694
  getRootEl: () => this.rootEl,
2564
2695
  applySize: (size) => this.applySize(size),
2565
2696
  getSize: () => this.size,
2566
- isDragging: () => this.gesture?.isDragging ?? false,
2697
+ isDragging: () => this.isDraggingAny(),
2567
2698
  applyAux: (size) => this.applySize(size, true),
2568
2699
  getTransformFor: (size) => this.transformTemplate(this.snaps.getMaxAxisSize() - size)
2569
2700
  },
@@ -2597,7 +2728,7 @@ var BottomSheetCore = class {
2597
2728
  auditVhUsage(this.snapPointsRaw);
2598
2729
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2599
2730
  this.rootEl = this.element.closest(".bs-root");
2600
- this.maxHeight = createMaxHeightController({
2731
+ this.maxHeightDeps = {
2601
2732
  element: this.element,
2602
2733
  mode: this.mode,
2603
2734
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
@@ -2605,7 +2736,8 @@ var BottomSheetCore = class {
2605
2736
  this.snaps.setMaxAxisSize(size);
2606
2737
  },
2607
2738
  recompute: () => this.recompute()
2608
- });
2739
+ };
2740
+ this.maxHeight = createMaxHeightController(this.maxHeightDeps);
2609
2741
  this.fitDeps = {
2610
2742
  element: this.element,
2611
2743
  scrollContainer: this.scrollContainer,
@@ -2624,6 +2756,7 @@ var BottomSheetCore = class {
2624
2756
  const initial = this.snaps.findById(this.activeId);
2625
2757
  if (initial) this.size = initial.size;
2626
2758
  this.applySize(this.size);
2759
+ this.setRestClosed(this.size === 0);
2627
2760
  this.updateAriaSlider();
2628
2761
  this.scrollCache = createScrollCache({
2629
2762
  scrollContainer: this.scrollContainer,
@@ -2654,7 +2787,7 @@ var BottomSheetCore = class {
2654
2787
  (p) => p.size === "fit" || p.size === "content"
2655
2788
  ),
2656
2789
  isDestroyed: () => this.destroyed,
2657
- isDragging: () => this.gesture?.isDragging ?? false,
2790
+ isDragging: () => this.isDraggingAny(),
2658
2791
  recompute: () => this.recompute()
2659
2792
  })
2660
2793
  );
@@ -2680,7 +2813,7 @@ var BottomSheetCore = class {
2680
2813
  autoCollapseAfter
2681
2814
  },
2682
2815
  isDestroyed: () => this.destroyed,
2683
- isDragging: () => this.gesture?.isDragging ?? false,
2816
+ isDragging: () => this.isDraggingAny(),
2684
2817
  isAnimating: () => this.animation.isAnimating,
2685
2818
  isTopSheet: () => this.isTopSheet,
2686
2819
  isVerticalAxis: () => this.mode === "bottom" || this.mode === "top",
@@ -2710,6 +2843,7 @@ var BottomSheetCore = class {
2710
2843
  void this.snapTo(id);
2711
2844
  },
2712
2845
  close: (reason) => this.close(reason),
2846
+ attachDragSurface: (surface, kind) => this.attachDragSurface(surface, kind),
2713
2847
  on: (event, fn) => this.on(event, fn),
2714
2848
  addTeardown: (fn) => this.teardowns.add(fn)
2715
2849
  };
@@ -2738,7 +2872,7 @@ var BottomSheetCore = class {
2738
2872
  return {
2739
2873
  size: this.size,
2740
2874
  activeId: this.activeId,
2741
- isDragging: this.gesture?.isDragging ?? false,
2875
+ isDragging: this.isDraggingAny(),
2742
2876
  isAnimating: this.animation.isAnimating,
2743
2877
  progress: this.computeProgress(this.size)
2744
2878
  };
@@ -2858,9 +2992,16 @@ var BottomSheetCore = class {
2858
2992
  progress: this.computeProgress(this.size)
2859
2993
  });
2860
2994
  if (withHaptic) this.haptic();
2995
+ this.setRestClosed(targetSize === 0);
2861
2996
  if (wasClosed && targetSize > 0) this.emitOpenSequence(id);
2862
2997
  if (targetSize === 0) this.emitCloseSequence();
2863
2998
  }
2999
+ setRestClosed(closed) {
3000
+ if (closed === this.restClosed) return;
3001
+ this.restClosed = closed;
3002
+ if (closed) this.element.setAttribute("data-bs-rest", "closed");
3003
+ else this.element.removeAttribute("data-bs-rest");
3004
+ }
2864
3005
  emitOpenSequence(id) {
2865
3006
  this.emit("open", { id });
2866
3007
  this.handleOpen();
@@ -2916,6 +3057,51 @@ var BottomSheetCore = class {
2916
3057
  if (this.destroyed) return;
2917
3058
  this.disableDragFlag = value;
2918
3059
  }
3060
+ setDragFromContent(value) {
3061
+ if (this.destroyed) return;
3062
+ this.dragFromContentDefault = value;
3063
+ }
3064
+ setDragFrom(mode) {
3065
+ if (this.destroyed || mode === this.dragFromMode) return;
3066
+ this.gesture?.forceClearDragState();
3067
+ this.detachSheetGesture?.();
3068
+ this.dragFromMode = mode;
3069
+ this.mountSheetGesture();
3070
+ }
3071
+ getDragFrom() {
3072
+ return this.dragFromMode;
3073
+ }
3074
+ setMode(mode) {
3075
+ if (this.destroyed) return;
3076
+ const next = resolveMode(mode, this.element);
3077
+ if (next === this.mode) return;
3078
+ const wasVertical = this.mode === "bottom" || this.mode === "top";
3079
+ const nowVertical = next === "bottom" || next === "top";
3080
+ this.gesture?.forceClearDragState();
3081
+ this.detachSheetGesture?.();
3082
+ this.animation.cancel();
3083
+ this.element.style.removeProperty(layoutAxis(this.mode));
3084
+ this.mode = next;
3085
+ this.element.dataset.mode = next;
3086
+ this.transformTemplate = buildTransformTemplate(next);
3087
+ this.maxHeightDeps.mode = next;
3088
+ this.fitDeps.mode = next;
3089
+ this.snaps.setMode(next);
3090
+ this.scrim.setSheetMode(next);
3091
+ if (wasVertical !== nowVertical) {
3092
+ this.aria = new AriaSliderWriter(this.handle, next);
3093
+ }
3094
+ this.detachSliderKeyboard?.();
3095
+ this.mountSliderKeyboard();
3096
+ this.mountSheetGesture();
3097
+ this.recompute();
3098
+ const active = this.snaps.findById(this.activeId);
3099
+ if (active) this.applySize(active.size);
3100
+ this.updateAriaSlider();
3101
+ }
3102
+ getMode() {
3103
+ return this.mode;
3104
+ }
2919
3105
  isTop() {
2920
3106
  return this.isTopSheet;
2921
3107
  }
@@ -3070,12 +3256,24 @@ var BottomSheetCore = class {
3070
3256
  auditVhUsage(points);
3071
3257
  this.auditDuplicateSnapIds(points);
3072
3258
  this.snaps.setRaw(points);
3073
- if (allowed) this.snaps.setAllowedIds(allowed);
3259
+ if (allowed) {
3260
+ this.snaps.setAllowedIds(allowed);
3261
+ } else {
3262
+ const present = this.snaps.getResolvedSnaps().map((s) => s.id);
3263
+ const kept = this.snaps.getAllowedIds().filter((id) => present.includes(id));
3264
+ this.snaps.setAllowedIds(kept.length ? kept : present);
3265
+ }
3074
3266
  this.scrollCache.clear();
3075
3267
  this.scrim.invalidateOpacityCache();
3076
3268
  const current = this.snaps.findById(this.activeId);
3077
3269
  if (current) {
3078
3270
  this.applySize(current.size);
3271
+ } else {
3272
+ const fallback = this.snaps.getResolvedSnaps().find((s) => this.snaps.getAllowedIds().includes(s.id)) ?? this.snaps.getResolvedSnaps()[0];
3273
+ if (fallback) {
3274
+ this.activeId = fallback.id;
3275
+ this.applySize(fallback.size);
3276
+ }
3079
3277
  }
3080
3278
  this.updateAriaSlider();
3081
3279
  }
@@ -3085,7 +3283,7 @@ var BottomSheetCore = class {
3085
3283
  this.snaps.recompute();
3086
3284
  this.maxHeight.clampTo();
3087
3285
  this.scrim.invalidateOpacityCache();
3088
- if (this.gesture?.isDragging) return;
3286
+ if (this.isDraggingAny()) return;
3089
3287
  this.healActiveSnap();
3090
3288
  }
3091
3289
  healActiveSnap() {
@@ -3107,6 +3305,8 @@ var BottomSheetCore = class {
3107
3305
  this.bus.clear();
3108
3306
  this.animation.cancel();
3109
3307
  this.gesture?.forceClearDragState();
3308
+ this.contentGesture?.forceClearDragState();
3309
+ this.setRestClosed(false);
3110
3310
  this.teardowns.drain();
3111
3311
  this.scrim.destroy();
3112
3312
  this.animation.destroy();
@@ -3219,11 +3419,12 @@ var BottomSheetCore = class {
3219
3419
  const scale = Math.max(1 - depth * 0.04, 0.86);
3220
3420
  this.element.style.scale = scale === 1 ? "" : String(scale);
3221
3421
  }
3222
- attach() {
3223
- this.gesture = new GestureController({
3224
- handle: this.handle,
3422
+ buildGestureDeps(surface, gestureOptions) {
3423
+ return {
3424
+ handle: surface,
3225
3425
  element: this.element,
3226
3426
  mode: this.mode,
3427
+ gestureOptions,
3227
3428
  getRoot: () => this.rootEl,
3228
3429
  getDragContext: () => {
3229
3430
  const buf = this.dragContextBuf;
@@ -3242,8 +3443,90 @@ var BottomSheetCore = class {
3242
3443
  settleAfterDrag: (delta, velocity, kind) => this.settleAfterDrag(delta, velocity, kind),
3243
3444
  emit: (event, payload) => this.emit(event, payload),
3244
3445
  listenerCount: (event) => this.bus.listenerCount(event)
3446
+ };
3447
+ }
3448
+ isContentDragAllowed() {
3449
+ if (this.disableDragFlag) return false;
3450
+ const point = this.snapPointsRaw.find((p) => p.id === this.activeId);
3451
+ return point?.dragFromContent ?? this.dragFromContentDefault;
3452
+ }
3453
+ decideContentDrag(surface, e, delta, crossDelta) {
3454
+ if (this.destroyed) return false;
3455
+ if (e.pointerType === "mouse") return false;
3456
+ if (this.gesture?.isDragging) return false;
3457
+ if (!this.isContentDragAllowed()) return false;
3458
+ if (!isDragAllowedFrom(e.target, "sheet")) return false;
3459
+ const decision = decideContentGesture({
3460
+ delta,
3461
+ crossDelta,
3462
+ sharesScrollAxis: this.mode === "bottom" || this.mode === "top",
3463
+ scrollTop: surface.scrollTop,
3464
+ atMaxSnap: this.size >= this.getAllowedRange().max - 0.5
3465
+ });
3466
+ if (decision === "pending") return null;
3467
+ return decision === "drag";
3468
+ }
3469
+ attachDragSurface(surface, kind) {
3470
+ if (this.destroyed || kind !== "content") return;
3471
+ if (this.contentGesture) return;
3472
+ const controller = new GestureController(
3473
+ this.buildGestureDeps(surface, {
3474
+ manageTouchAction: false,
3475
+ deferStart: (e, delta, crossDelta) => this.decideContentDrag(surface, e, delta, crossDelta)
3476
+ })
3477
+ );
3478
+ this.contentGesture = controller;
3479
+ const detachGestures = controller.install();
3480
+ const detachGuard = installTouchScrollGuard(
3481
+ surface,
3482
+ () => controller.isDragging
3483
+ );
3484
+ return () => {
3485
+ detachGuard();
3486
+ detachGestures();
3487
+ if (this.contentGesture === controller) this.contentGesture = void 0;
3488
+ };
3489
+ }
3490
+ sheetDragSurface() {
3491
+ return this.dragFromMode === "handle" ? this.handle : this.element;
3492
+ }
3493
+ canStartSheetDrag(e) {
3494
+ if (this.contentGesture?.isDragging) return false;
3495
+ const target = e.target;
3496
+ const scoped = typeof target?.closest === "function";
3497
+ if (scoped && target.closest(NO_DRAG_SELECTOR)) return false;
3498
+ if (this.handle !== this.element && scoped && this.handle.contains(target)) {
3499
+ return true;
3500
+ }
3501
+ if (!isDragAllowedFrom(e.target, this.dragFromMode)) return false;
3502
+ if (this.dragFromMode === "handle") return true;
3503
+ if (this.scrollContainer && scoped && this.scrollContainer.contains(target) && !target.closest(DRAG_ZONE_SELECTOR)) {
3504
+ return false;
3505
+ }
3506
+ return true;
3507
+ }
3508
+ mountSheetGesture() {
3509
+ const surface = this.sheetDragSurface();
3510
+ const controller = new GestureController(
3511
+ this.buildGestureDeps(surface, {
3512
+ shouldStart: (e) => this.canStartSheetDrag(e),
3513
+ manageTouchAction: this.dragFromMode === "handle"
3514
+ })
3515
+ );
3516
+ this.gesture = controller;
3517
+ const detachGestures = controller.install();
3518
+ const detachGuard = this.dragFromMode === "handle" ? null : installTouchScrollGuard(surface, () => controller.isDragging);
3519
+ this.detachSheetGesture = () => {
3520
+ detachGuard?.();
3521
+ detachGestures();
3522
+ };
3523
+ }
3524
+ attach() {
3525
+ this.mountSheetGesture();
3526
+ this.teardowns.add(() => {
3527
+ this.detachSheetGesture?.();
3528
+ this.detachSheetGesture = null;
3245
3529
  });
3246
- this.teardowns.add(this.gesture.install());
3247
3530
  try {
3248
3531
  this.attachFeatures();
3249
3532
  } catch (err) {
@@ -3280,7 +3563,7 @@ var BottomSheetCore = class {
3280
3563
  element: this.element,
3281
3564
  getMode: () => this.mode,
3282
3565
  isDestroyed: () => this.destroyed,
3283
- isDragging: () => this.gesture?.isDragging ?? false,
3566
+ isDragging: () => this.isDraggingAny(),
3284
3567
  resolveActiveSnap: () => this.snaps.findById(this.activeId),
3285
3568
  getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
3286
3569
  getSize: () => this.size,
@@ -3315,7 +3598,11 @@ var BottomSheetCore = class {
3315
3598
  );
3316
3599
  }
3317
3600
  }
3318
- this.teardowns.add(installSliderKeyboard({
3601
+ this.mountSliderKeyboard();
3602
+ this.scrim.attach();
3603
+ }
3604
+ mountSliderKeyboard() {
3605
+ const detach = installSliderKeyboard({
3319
3606
  handle: this.handle,
3320
3607
  mode: this.mode,
3321
3608
  isDestroyed: () => this.destroyed,
@@ -3324,8 +3611,12 @@ var BottomSheetCore = class {
3324
3611
  snapTo: (id) => {
3325
3612
  void this.snapTo(id);
3326
3613
  }
3327
- }));
3328
- this.scrim.attach();
3614
+ });
3615
+ this.detachSliderKeyboard = detach;
3616
+ this.teardowns.add(() => {
3617
+ this.detachSliderKeyboard?.();
3618
+ this.detachSliderKeyboard = null;
3619
+ });
3329
3620
  }
3330
3621
  settleAfterDrag(delta, velocity, kind = "touch") {
3331
3622
  const signal = this.newCycle();
@@ -3378,8 +3669,11 @@ var BottomSheetCore = class {
3378
3669
  );
3379
3670
  });
3380
3671
  }
3672
+ isDraggingAny() {
3673
+ return (this.gesture?.isDragging ?? false) || (this.contentGesture?.isDragging ?? false);
3674
+ }
3381
3675
  clearWillChangeIfIdle() {
3382
- if (this.animation.isAnimating || this.gesture?.isDragging) return;
3676
+ if (this.animation.isAnimating || this.isDraggingAny()) return;
3383
3677
  this.element.style.willChange = "";
3384
3678
  }
3385
3679
  haptic() {
@@ -3392,8 +3686,9 @@ var BottomSheetCore = class {
3392
3686
  }
3393
3687
  applySize(size, skipTransform = false) {
3394
3688
  const cap = this.snaps.getMaxAxisSize();
3395
- const clamped = cap > 0 && size > cap && !this.gesture?.isDragging && !this.allowOvershoot ? cap : size;
3689
+ const clamped = cap > 0 && size > cap && !this.isDraggingAny() && !this.allowOvershoot ? cap : size;
3396
3690
  this.size = clamped;
3691
+ if (clamped > 0) this.setRestClosed(false);
3397
3692
  const offset = cap - clamped;
3398
3693
  const style = this.element.style;
3399
3694
  if (!skipTransform) style.transform = this.transformTemplate(offset);
@@ -3773,50 +4068,10 @@ function installAutoCollapse(deps) {
3773
4068
  };
3774
4069
  }
3775
4070
 
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
- };
4071
+ // src/core/features/content-drag.ts
4072
+ function installContentDrag(deps) {
4073
+ const detach = deps.attachDragSurface(deps.container, "content");
4074
+ return () => detach?.();
3820
4075
  }
3821
4076
 
3822
4077
  // src/core/features/visual-viewport.ts
@@ -3924,13 +4179,9 @@ function contentSwipeFeature() {
3924
4179
  stage: "attach",
3925
4180
  install: (ctx) => {
3926
4181
  if (!ctx.scrollContainer) return;
3927
- return installContentSwipe({
4182
+ return installContentDrag({
3928
4183
  container: ctx.scrollContainer,
3929
- isDragging: ctx.isDragging,
3930
- isAnimating: ctx.isAnimating,
3931
- getAllowedIds: ctx.allowedIdsBySize,
3932
- getActiveId: ctx.getActiveId,
3933
- snapTo: ctx.snapTo
4184
+ attachDragSurface: ctx.attachDragSurface
3934
4185
  });
3935
4186
  }
3936
4187
  };
@@ -4062,6 +4313,8 @@ var BottomSheet = (props) => {
4062
4313
  persistent: props.persistent,
4063
4314
  disableClose: props.disableClose,
4064
4315
  disableDrag: props.disableDrag,
4316
+ dragFrom: props.dragFrom,
4317
+ dragFromContent: props.dragFromContent,
4065
4318
  closeOnRouteChange: props.closeOnRouteChange,
4066
4319
  stackEffect: props.stackEffect,
4067
4320
  returnFocusTo: props.returnFocusTo
@@ -4150,6 +4403,18 @@ var BottomSheet = (props) => {
4150
4403
  }, {
4151
4404
  defer: true
4152
4405
  }));
4406
+ solidJs.createEffect(solidJs.on(() => props.dragFromContent, (value, prev) => {
4407
+ if (!engine || prev === void 0 || value === void 0) return;
4408
+ engine.setDragFromContent(value);
4409
+ }, {
4410
+ defer: true
4411
+ }));
4412
+ solidJs.createEffect(solidJs.on(() => props.dragFrom, (value, prev) => {
4413
+ if (!engine || prev === void 0 || value === void 0) return;
4414
+ engine.setDragFrom(value);
4415
+ }, {
4416
+ defer: true
4417
+ }));
4153
4418
  const showBackdrop = solidJs.createMemo(() => props.backdrop !== false);
4154
4419
  const mode = solidJs.createMemo(() => props.mode ?? "bottom");
4155
4420
  const ariaLabel = solidJs.createMemo(() => props.ariaLabel ?? "Bottom sheet");