@surdeddd/bottom-sheet 0.23.3 → 0.24.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 (80) hide show
  1. package/README.md +513 -525
  2. package/README.ru.md +685 -0
  3. package/dist/{BottomSheetCore-COvILxmR.d.ts → BottomSheetCore-B_r4KCQL.d.cts} +48 -12
  4. package/dist/{BottomSheetCore-CaJwHnxY.d.cts → BottomSheetCore-B_r4KCQL.d.ts} +48 -12
  5. package/dist/{BottomSheetEngine-C_1aPJsU.d.cts → BottomSheetEngine-0F7mjyvF.d.cts} +1 -1
  6. package/dist/{BottomSheetEngine-CsgCcCSO.d.ts → BottomSheetEngine-CJV_DDrM.d.ts} +1 -1
  7. package/dist/core.cjs +810 -436
  8. package/dist/core.d.cts +2 -3
  9. package/dist/core.d.ts +2 -3
  10. package/dist/core.js +810 -436
  11. package/dist/debug.d.cts +3 -3
  12. package/dist/debug.d.ts +3 -3
  13. package/dist/{default-features-BJx7J0NH.d.ts → default-features-4Xwv6aVt.d.ts} +4 -2
  14. package/dist/{default-features-CTJb8Wdk.d.cts → default-features-DX2NOxP6.d.cts} +4 -2
  15. package/dist/element.cjs +1428 -699
  16. package/dist/element.d.cts +15 -5
  17. package/dist/element.d.ts +15 -5
  18. package/dist/element.iife.global.js +2 -2
  19. package/dist/element.js +1428 -699
  20. package/dist/features.cjs +334 -190
  21. package/dist/features.d.cts +3 -4
  22. package/dist/features.d.ts +3 -4
  23. package/dist/features.js +334 -191
  24. package/dist/{index-CXN7t3rr.d.ts → index-2shlxhcG.d.cts} +3 -2
  25. package/dist/{index-BBrJJjy5.d.cts → index-CNCRQnpc.d.ts} +3 -2
  26. package/dist/index.cjs +1129 -631
  27. package/dist/index.d.cts +13 -7
  28. package/dist/index.d.ts +13 -7
  29. package/dist/index.js +1129 -632
  30. package/dist/integrations/formik.cjs +21 -10
  31. package/dist/integrations/formik.d.cts +3 -3
  32. package/dist/integrations/formik.d.ts +3 -3
  33. package/dist/integrations/formik.js +22 -11
  34. package/dist/integrations/react-hook-form.cjs +21 -12
  35. package/dist/integrations/react-hook-form.d.cts +3 -3
  36. package/dist/integrations/react-hook-form.d.ts +3 -3
  37. package/dist/integrations/react-hook-form.js +21 -12
  38. package/dist/option-keys-DItuAjUw.d.cts +6 -0
  39. package/dist/option-keys-a5DYNwwO.d.ts +6 -0
  40. package/dist/overlay.cjs +386 -189
  41. package/dist/overlay.d.cts +3 -4
  42. package/dist/overlay.d.ts +3 -4
  43. package/dist/overlay.js +386 -189
  44. package/dist/preact.d.cts +3 -4
  45. package/dist/preact.d.ts +3 -4
  46. package/dist/qwik-lib/index.qwik.cjs +1306 -671
  47. package/dist/qwik-lib/index.qwik.mjs +1306 -671
  48. package/dist/qwik.cjs +1326 -701
  49. package/dist/qwik.d.cts +14 -30
  50. package/dist/qwik.d.ts +14 -30
  51. package/dist/qwik.js +1326 -701
  52. package/dist/react.cjs +1373 -750
  53. package/dist/react.d.cts +31 -14
  54. package/dist/react.d.ts +31 -14
  55. package/dist/react.js +1375 -752
  56. package/dist/solid.cjs +1332 -727
  57. package/dist/solid.d.cts +13 -437
  58. package/dist/solid.d.ts +13 -437
  59. package/dist/solid.js +1334 -729
  60. package/dist/solid.source.jsx +1374 -751
  61. package/dist/styles.css +20 -6
  62. package/dist/svelte-core.cjs +1211 -676
  63. package/dist/svelte-core.js +1207 -677
  64. package/dist/svelte-src/BottomSheet.svelte +102 -59
  65. package/dist/svelte.cjs +1324 -707
  66. package/dist/svelte.d.cts +45 -25
  67. package/dist/svelte.d.ts +45 -25
  68. package/dist/svelte.js +1324 -707
  69. package/dist/vue.cjs +1354 -773
  70. package/dist/vue.d.cts +20 -3
  71. package/dist/vue.d.ts +20 -3
  72. package/dist/vue.js +1354 -773
  73. package/dist/webgl.cjs +38 -7
  74. package/dist/webgl.d.cts +3 -3
  75. package/dist/webgl.d.ts +3 -3
  76. package/dist/webgl.js +38 -7
  77. package/llms.txt +330 -0
  78. package/package.json +62 -42
  79. package/dist/transform-jVfAikE9.d.cts +0 -3
  80. package/dist/transform-jVfAikE9.d.ts +0 -3
package/dist/webgl.cjs CHANGED
@@ -587,12 +587,34 @@ var BEND_DECAY_HELD = 0.86;
587
587
  var BEND_DECAY_FREE = 0.62;
588
588
  var BEND_REST = 0.35;
589
589
  var prefersReducedMotion = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
590
+ var WHITE = [1, 1, 1, 1];
591
+ var SENTINEL = "#010203";
592
+ var colorProbe;
593
+ var readColor = (value) => {
594
+ if (colorProbe === void 0) {
595
+ const canvas = document.createElement("canvas");
596
+ canvas.width = 1;
597
+ canvas.height = 1;
598
+ colorProbe = canvas.getContext("2d", { willReadFrequently: true });
599
+ }
600
+ const probe = colorProbe;
601
+ if (!probe) return WHITE;
602
+ probe.fillStyle = SENTINEL;
603
+ probe.fillStyle = value;
604
+ if (probe.fillStyle === SENTINEL) return WHITE;
605
+ probe.clearRect(0, 0, 1, 1);
606
+ probe.fillRect(0, 0, 1, 1);
607
+ const [r = 255, g = 255, b = 255, a = 255] = probe.getImageData(0, 0, 1, 1).data;
608
+ return [r / 255, g / 255, b / 255, a / 255];
609
+ };
590
610
  var parseColor = (value) => {
591
- const m = value.match(/rgba?\(([^)]+)\)/);
592
- if (!m) return [1, 1, 1, 1];
593
- const parts = m[1].split(",").map((p) => parseFloat(p.trim()));
594
- const [r = 255, g = 255, b = 255, a = 1] = parts;
595
- return [r / 255, g / 255, b / 255, a];
611
+ const legacy = value.match(/^rgba?\(([^()]+)\)$/);
612
+ if (legacy && legacy[1].includes(",")) {
613
+ const parts = legacy[1].split(",").map((p) => parseFloat(p.trim()));
614
+ const [r = 255, g = 255, b = 255, a = 1] = parts;
615
+ return [r / 255, g / 255, b / 255, a];
616
+ }
617
+ return readColor(value);
596
618
  };
597
619
  var parseRadius = (value) => {
598
620
  const n = parseFloat(value);
@@ -683,8 +705,16 @@ var webglRenderer = (options = {}) => ({
683
705
  }
684
706
  const parent = element.parentElement ?? document.body;
685
707
  parent.insertBefore(handle.canvas, element);
686
- const elementZ = getComputedStyle(element).zIndex;
687
- handle.canvas.style.zIndex = elementZ && elementZ !== "auto" ? String(Number(elementZ) - 1) : "0";
708
+ let canvasZ = "";
709
+ const followStack = () => {
710
+ if (!handle) return;
711
+ const elementZ = getComputedStyle(element).zIndex;
712
+ const next = elementZ && elementZ !== "auto" ? String(Number(elementZ) - 1) : "0";
713
+ if (next === canvasZ) return;
714
+ canvasZ = next;
715
+ handle.canvas.style.zIndex = next;
716
+ };
717
+ followStack();
688
718
  suppressDomPaint();
689
719
  const readFrame = () => {
690
720
  if (!handle) return null;
@@ -815,6 +845,7 @@ var webglRenderer = (options = {}) => ({
815
845
  characterData: true
816
846
  });
817
847
  const geometryObserver = new MutationObserver(() => {
848
+ followStack();
818
849
  if (!ctx.isDragging()) paint();
819
850
  wake();
820
851
  });
package/dist/webgl.d.cts CHANGED
@@ -1,5 +1,4 @@
1
- import { E as EngineFeature } from './BottomSheetCore-CaJwHnxY.cjs';
2
- import './transform-jVfAikE9.cjs';
1
+ import { EngineFeature } from './core.cjs';
3
2
 
4
3
  type WebGLRendererOptions = {
5
4
  jelly?: number;
@@ -14,4 +13,5 @@ type WebGLUnsupportedReason = "no-document" | "no-webgl" | "reduced-motion" | "c
14
13
 
15
14
  declare const webglRenderer: (options?: WebGLRendererOptions) => EngineFeature;
16
15
 
17
- export { type WebGLRendererOptions, type WebGLUnsupportedReason, webglRenderer };
16
+ export { webglRenderer };
17
+ export type { WebGLRendererOptions, WebGLUnsupportedReason };
package/dist/webgl.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { E as EngineFeature } from './BottomSheetCore-COvILxmR.js';
2
- import './transform-jVfAikE9.js';
1
+ import { EngineFeature } from './core.js';
3
2
 
4
3
  type WebGLRendererOptions = {
5
4
  jelly?: number;
@@ -14,4 +13,5 @@ type WebGLUnsupportedReason = "no-document" | "no-webgl" | "reduced-motion" | "c
14
13
 
15
14
  declare const webglRenderer: (options?: WebGLRendererOptions) => EngineFeature;
16
15
 
17
- export { type WebGLRendererOptions, type WebGLUnsupportedReason, webglRenderer };
16
+ export { webglRenderer };
17
+ export type { WebGLRendererOptions, WebGLUnsupportedReason };
package/dist/webgl.js CHANGED
@@ -585,12 +585,34 @@ var BEND_DECAY_HELD = 0.86;
585
585
  var BEND_DECAY_FREE = 0.62;
586
586
  var BEND_REST = 0.35;
587
587
  var prefersReducedMotion = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
588
+ var WHITE = [1, 1, 1, 1];
589
+ var SENTINEL = "#010203";
590
+ var colorProbe;
591
+ var readColor = (value) => {
592
+ if (colorProbe === void 0) {
593
+ const canvas = document.createElement("canvas");
594
+ canvas.width = 1;
595
+ canvas.height = 1;
596
+ colorProbe = canvas.getContext("2d", { willReadFrequently: true });
597
+ }
598
+ const probe = colorProbe;
599
+ if (!probe) return WHITE;
600
+ probe.fillStyle = SENTINEL;
601
+ probe.fillStyle = value;
602
+ if (probe.fillStyle === SENTINEL) return WHITE;
603
+ probe.clearRect(0, 0, 1, 1);
604
+ probe.fillRect(0, 0, 1, 1);
605
+ const [r = 255, g = 255, b = 255, a = 255] = probe.getImageData(0, 0, 1, 1).data;
606
+ return [r / 255, g / 255, b / 255, a / 255];
607
+ };
588
608
  var parseColor = (value) => {
589
- const m = value.match(/rgba?\(([^)]+)\)/);
590
- if (!m) return [1, 1, 1, 1];
591
- const parts = m[1].split(",").map((p) => parseFloat(p.trim()));
592
- const [r = 255, g = 255, b = 255, a = 1] = parts;
593
- return [r / 255, g / 255, b / 255, a];
609
+ const legacy = value.match(/^rgba?\(([^()]+)\)$/);
610
+ if (legacy && legacy[1].includes(",")) {
611
+ const parts = legacy[1].split(",").map((p) => parseFloat(p.trim()));
612
+ const [r = 255, g = 255, b = 255, a = 1] = parts;
613
+ return [r / 255, g / 255, b / 255, a];
614
+ }
615
+ return readColor(value);
594
616
  };
595
617
  var parseRadius = (value) => {
596
618
  const n = parseFloat(value);
@@ -681,8 +703,16 @@ var webglRenderer = (options = {}) => ({
681
703
  }
682
704
  const parent = element.parentElement ?? document.body;
683
705
  parent.insertBefore(handle.canvas, element);
684
- const elementZ = getComputedStyle(element).zIndex;
685
- handle.canvas.style.zIndex = elementZ && elementZ !== "auto" ? String(Number(elementZ) - 1) : "0";
706
+ let canvasZ = "";
707
+ const followStack = () => {
708
+ if (!handle) return;
709
+ const elementZ = getComputedStyle(element).zIndex;
710
+ const next = elementZ && elementZ !== "auto" ? String(Number(elementZ) - 1) : "0";
711
+ if (next === canvasZ) return;
712
+ canvasZ = next;
713
+ handle.canvas.style.zIndex = next;
714
+ };
715
+ followStack();
686
716
  suppressDomPaint();
687
717
  const readFrame = () => {
688
718
  if (!handle) return null;
@@ -813,6 +843,7 @@ var webglRenderer = (options = {}) => ({
813
843
  characterData: true
814
844
  });
815
845
  const geometryObserver = new MutationObserver(() => {
846
+ followStack();
816
847
  if (!ctx.isDragging()) paint();
817
848
  wake();
818
849
  });
package/llms.txt ADDED
@@ -0,0 +1,330 @@
1
+ # @surdeddd/bottom-sheet
2
+
3
+ > A bottom sheet, drawer and modal sheet for the web. One gesture engine with snap points, spring physics and keyboard accessibility, and the same engine behind adapters for React, Vue 3, Svelte 5, Solid, Qwik and Preact and behind a `<bottom-sheet>` custom element for Angular, Lit, Astro and plain HTML. MIT, TypeScript, no runtime dependencies.
4
+
5
+ Install: `npm i @surdeddd/bottom-sheet`. Frameworks are optional peer dependencies.
6
+
7
+ This file is a complete reference written for AI coding assistants. Every name below is real; do not invent options, props or events that are not listed here.
8
+
9
+ ## Entry points
10
+
11
+ | Import | What it gives |
12
+ | --- | --- |
13
+ | `@surdeddd/bottom-sheet` | `BottomSheetEngine`, `BottomSheetCore`, `createSheetManager`, `historySettled`, types |
14
+ | `@surdeddd/bottom-sheet/react` | `BottomSheet`, `useBottomSheet`, `Overlay`, `useOverlay`, `BottomSheetFromConfig` |
15
+ | `@surdeddd/bottom-sheet/preact` | The React entry, for use with `preact/compat` |
16
+ | `@surdeddd/bottom-sheet/vue` | `BottomSheet`, `useBottomSheet` |
17
+ | `@surdeddd/bottom-sheet/svelte` | `BottomSheet`, `createBottomSheet` |
18
+ | `@surdeddd/bottom-sheet/solid` | `BottomSheet` |
19
+ | `@surdeddd/bottom-sheet/qwik` | `BottomSheet` |
20
+ | `@surdeddd/bottom-sheet/element` | Registers `<bottom-sheet>`; `BottomSheetElement`, `defineBottomSheet` |
21
+ | `@surdeddd/bottom-sheet/overlay` | `OverlayEngine`: a two-state slide-in panel without snap points |
22
+ | `@surdeddd/bottom-sheet/core` | `BottomSheetCore`: the engine without optional features |
23
+ | `@surdeddd/bottom-sheet/features` | `routeFeature`, `visualViewportFeature`, `contentSwipeFeature`, `contentFitFeature`, `persistFeature`, `autoCollapseFeature` |
24
+ | `@surdeddd/bottom-sheet/webgl` | `webglRenderer`: optional GPU renderer |
25
+ | `@surdeddd/bottom-sheet/debug` | `attachDebugOverlay(engine)` |
26
+ | `@surdeddd/bottom-sheet/integrations/react-hook-form` | `useFormGuard(sheetRef, form)` |
27
+ | `@surdeddd/bottom-sheet/integrations/formik` | `useFormikGuard(sheetRef, formikRef)` |
28
+ | `@surdeddd/bottom-sheet/styles` | The stylesheet. Required by everything except the custom element |
29
+ | `@surdeddd/bottom-sheet/themes/ios`, `/themes/material`, `/themes/vercel` | Optional themes; classes `bs-theme-ios`, `bs-theme-material`, `bs-theme-vercel` on an ancestor |
30
+
31
+ ## The canonical example
32
+
33
+ A modal sheet opened by a button, in React:
34
+
35
+ ```tsx
36
+ import { useState } from "react";
37
+ import { BottomSheet } from "@surdeddd/bottom-sheet/react";
38
+ import "@surdeddd/bottom-sheet/styles";
39
+
40
+ export function Filters() {
41
+ const [open, setOpen] = useState(false);
42
+ return (
43
+ <>
44
+ <button onClick={() => setOpen(true)}>Filters</button>
45
+ <BottomSheet
46
+ open={open}
47
+ onOpenChange={setOpen}
48
+ snapPoints={[
49
+ { id: "closed", size: 0 },
50
+ { id: "open", size: "fit" },
51
+ ]}
52
+ focusTrap
53
+ inertSiblings
54
+ header={<h2>Filters</h2>}
55
+ >
56
+ <FilterForm />
57
+ </BottomSheet>
58
+ </>
59
+ );
60
+ }
61
+ ```
62
+
63
+ A sheet that stays on screen (the maps pattern) has no closed point and no `open` prop:
64
+
65
+ ```tsx
66
+ <BottomSheet
67
+ snapPoints={[
68
+ { id: "peek", size: 96 },
69
+ { id: "half", size: "45dvh" },
70
+ { id: "full", size: "90%" },
71
+ ]}
72
+ initial="peek"
73
+ >
74
+ <PlaceList />
75
+ </BottomSheet>
76
+ ```
77
+
78
+ ## Rules that prevent the usual mistakes
79
+
80
+ 1. Snap points are objects, `{ id, size }`, and everything refers to them by `id`. `size` is a number of pixels, `"50%"`, `"fit"` (the content's own height), `"full"`, or any CSS length such as `"50dvh"`.
81
+ 2. A sheet can close only if it has a snap point of size `0`. The `open` prop, `v-model:open`, `bind:open` and `close()` all depend on it. By convention its id is `"closed"`.
82
+ 3. Import `@surdeddd/bottom-sheet/styles` once. Without it the sheet is unstyled and unpositioned. The custom element does not need it.
83
+ 4. Do not set `z-index` on `.bs-sheet`, `.bs-backdrop` or anchors. The engine writes it. Use `teleportTo` to place the sheet in another stacking context.
84
+ 5. `cancel()` in `before-snap` and `before-close` must be called synchronously. An async handler cannot veto.
85
+ 6. Unmount a sheet in `closed`, not in `close`. `close` fires when the exit starts.
86
+ 7. A modal sheet wants `focusTrap` and `inertSiblings`. Both are off by default.
87
+ 8. To let a finger scroll the content at a half point, set `fitContentToSnap` and `dragFromContent: false` (globally or per snap point). By default a swipe on the content drags the sheet until it is fully open.
88
+ 9. `backdropRange` is a range of progress, not of opacity. `backdropOpacity` is the opacity.
89
+ 10. `--bs-progress` and `--bs-size` are set on the sheet and on `.bs-root`. Elements outside `.bs-root` cannot read them; use the `progress` event there.
90
+ 11. `persistent` stops the user from closing the sheet; `close()` still works. `disableClose` stops `close()` too.
91
+ 12. In the Next.js App Router the component file needs `"use client"`.
92
+ 13. Options other than the ones listed as reactive are read once at mount. Change them by remounting with a new `key`.
93
+
94
+ ## Engine options
95
+
96
+ Shared by `new BottomSheetEngine(options)` and every adapter (as props).
97
+
98
+ Elements, engine only: `element` (required), `handle`, `scrollContainer`, `backdrop`, `scrim`.
99
+
100
+ | Option | Type | Default | Meaning |
101
+ | --- | --- | --- | --- |
102
+ | `snapPoints` | `{ id: string; size: SnapPoint; dragFromContent?: boolean }[]` | required | The sizes the sheet can rest at |
103
+ | `allowed` | `string[]` | all ids | Ids the sheet may rest at now |
104
+ | `initial` | `string` | first allowed | Id to start at |
105
+ | `mode` | `"bottom" \| "top" \| "left" \| "right" \| "start" \| "end"` | `"bottom"` | Edge |
106
+ | `maxHeight` | `number \| string` | none | Cap on the size |
107
+ | `radius` | `number \| string` | `20px` | Corner radius |
108
+ | `fitContentToSnap` | `boolean` | `false` | Keep the scroll area and footer reachable at every point |
109
+ | `animation` | `"spring" \| "ios-spring" \| "material-bounce" \| "snappy" \| "tween" \| "linear"` | `"spring"` | Motion preset |
110
+ | `spring` | `{ stiffness?, damping?, mass? }` | preset | Spring parameters |
111
+ | `duration` | `number` | `220` | Tween length, ms |
112
+ | `easing` | `(t: number) => number` | ease-out-back | Tween curve |
113
+ | `settleAnimation` | `"waapi"` | off | Run the settle as a Web Animation |
114
+ | `respectReducedMotion` | `boolean` | `true` | Jump under `prefers-reduced-motion` |
115
+ | `viewTransitions` | `boolean` | `false` | Use `document.startViewTransition()` |
116
+ | `dragFrom` | `"handle" \| "sheet" \| "zones"` | `"handle"` | Where a drag may start. `zones` = `[data-bs-drag]` subtrees. `[data-bs-no-drag]` opts out |
117
+ | `dragFromContent` | `boolean` | `true` | A touch on the scroll area drags the sheet |
118
+ | `disableDrag` | `boolean` | `false` | No gesture |
119
+ | `rubberBand` | `boolean` | `true` | Soft over-drag |
120
+ | `flickVelocity` | `number` | `0.65` | px/ms for a touch flick |
121
+ | `dragThreshold` | `number` | `18` | px before a touch drag counts |
122
+ | `closeOnEscape` | `boolean` | `true` | Escape closes the top sheet |
123
+ | `closeOnBack` | `boolean` | `false` | The Back button closes the sheet |
124
+ | `routedTo` | `string` | none | URL shown while open |
125
+ | `closeOnRouteChange` | `boolean` | `false` | Close when the URL changes |
126
+ | `persistent` | `boolean` | `false` | The user cannot dismiss |
127
+ | `disableClose` | `boolean` | `false` | Nothing closes |
128
+ | `focusTrap` | `boolean` | `false` | Trap and restore focus |
129
+ | `initialFocus` | `string \| HTMLElement \| false` | first focusable | With `focusTrap`: what gets focus on open |
130
+ | `returnFocusTo` | `string \| HTMLElement \| (() => HTMLElement \| null)` | none | Where focus goes on close |
131
+ | `inertSiblings` | `boolean` | `false` | Make the rest of the page `inert` |
132
+ | `lockBodyScroll` | `boolean` | `true` | Lock page scroll while open |
133
+ | `backdropRange` | `[number, number]` | `[0, 1]` | Progress range over which the backdrop fades in |
134
+ | `backdropOpacity` | `number` | `1` | Opacity the backdrop reaches |
135
+ | `backdropColor` | `string` | stylesheet | Colour of the backdrop |
136
+ | `screenRange` | `[number, number]` | `[0, 1]` | Progress range for the scrim layer |
137
+ | `scrimMode` | `"full" \| "above-sheet" \| "off"` | `"full"` | Extent of the scrim layer |
138
+ | `scrimPreset` | `"subtle" \| "standard" \| "monitoring" \| "cinematic"` | none | Colour, blur and range together |
139
+ | `scrimColor` | `string` | none | Scrim fill |
140
+ | `scrimBlur` | `string` | none | Scrim blur radius, e.g. `"8px"` |
141
+ | `scrimInteractive` | `boolean` | `false` | Scrim takes pointer input |
142
+ | `scrimTapToClose` | `boolean` | `false` | Tap on the scrim closes |
143
+ | `stackEffect` | `boolean` | `false` | Scale the sheets under the top one |
144
+ | `persistKey` | `string` | none | `localStorage` key for the last point |
145
+ | `autoCollapseAfter` | `number` | off | ms of rest before collapsing |
146
+ | `linkedSheets` | `BottomSheetEngine[]` | none | Engines that collapse when this one opens |
147
+ | `features` | `EngineFeature[]` | all | Features for `BottomSheetCore` |
148
+
149
+ ## Engine methods
150
+
151
+ ```ts
152
+ sheet.open(id?): Promise<void> // to the first allowed open point; no-op on an open sheet without id
153
+ sheet.close(reason?): Promise<void> // to the point of size 0; without one, the smallest point
154
+ sheet.snapTo(id, { velocity?, signal? }?): Promise<void>
155
+ sheet.expand(): Promise<void> // largest allowed point
156
+ sheet.collapse(): Promise<void> // smallest allowed open point
157
+ sheet.dragTo(sizePx, { velocity?, signal? }?): Promise<void>
158
+
159
+ sheet.setAllowed(ids, snapTo?)
160
+ sheet.setSnapPoints(points, allowed?)
161
+ sheet.recompute() // re-measure "fit"
162
+ sheet.getAllowedIds(); sheet.getResolvedSnaps()
163
+
164
+ sheet.setMode(mode); sheet.getMode()
165
+ sheet.setPersistent(bool); sheet.setDisableClose(bool); sheet.setDisableDrag(bool)
166
+ sheet.setDragFrom(mode); sheet.setDragFromContent(bool)
167
+ sheet.setRadius(value); sheet.setMaxHeight(value)
168
+
169
+ sheet.setBackdropRange([from, to]); sheet.setBackdropOpacity(n); sheet.setBackdropColor(color | null)
170
+ sheet.setScreenRange([from, to]); sheet.setScrimMode(mode); sheet.setScrimEnabled(bool)
171
+ sheet.setScrimColor(color | null); sheet.setScrimBlur(blur | null)
172
+ sheet.setScrimInteractive(bool); sheet.setScrimTapToClose(bool)
173
+ sheet.setScrim({ preset?, color?, blur?, range?, mode?, interactive?, tapToClose?, enabled? })
174
+ sheet.setScrimOverlay({ children, position?, inset?, interactive? }): () => void
175
+ sheet.addAnchor({ element, position?, inset?, showOn?, fadeRange?, interactive?, animation? }): () => void
176
+ sheet.setScrimStages({ stages } | null): () => void
177
+
178
+ sheet.state // { activeId, size, progress, phase, isDragging, isAnimating }
179
+ sheet.canDismiss(); sheet.isTop(); sheet.depth()
180
+ sheet.on(event, listener): () => void
181
+ sheet.use(plugin); sheet.destroy()
182
+ ```
183
+
184
+ `phase` is `"closed" | "opening" | "open" | "closing"`. `progress` is 0 at the smallest allowed point and 1 at the largest.
185
+
186
+ ## Events
187
+
188
+ | Event | Payload | When |
189
+ | --- | --- | --- |
190
+ | `open` | `{ id }` | The sheet starts to appear |
191
+ | `opened` | `{ id }` | It has landed |
192
+ | `close` | none | It starts to leave |
193
+ | `closed` | none | It is gone |
194
+ | `snap` | `{ id, size, progress }` | It settled on a point |
195
+ | `before-snap` | `{ id, size, previousId, cancel }` | Before a move; `cancel()` vetoes |
196
+ | `before-close` | `{ reason, cancel }` | Before a close; `reason`: `"programmatic" \| "backdrop" \| "escape" \| "back"` |
197
+ | `dragstart` | `{ size }` | A drag begins |
198
+ | `drag` | `{ size, delta }` | Every frame of a drag |
199
+ | `dragend` | `{ size, velocity }` | A drag ends |
200
+ | `progress` | `{ value, size }` | Every frame of any motion |
201
+
202
+ ## Adapters
203
+
204
+ All components take every engine option as a prop, plus: `backdrop` (default `true`), `closeOnBackdrop` (default `true`), `teleportTo`, `ariaLabel` (default `"Bottom sheet"`), `handleLabel`, `contentLabel`.
205
+
206
+ ### React (`/react`)
207
+
208
+ - Controlled open: `open`, `onOpenChange`.
209
+ - Regions: `header`, `footer` (node or `(state) => node`), `leftButton`, `rightButton`, `screen`, `children`.
210
+ - Callbacks: `onSnap(id)`, `onOpen(id)`, `onOpened(id)`, `onClose()`, `onClosed()`, `onBeforeSnap(e)`, `onBeforeClose(e)`, `onDragStart(e)`, `onDragEnd(e)`, `onDrag(e)`, `onProgress(e)`, `onChange(state)`.
211
+ - Other props: `className` and `style` (on the sheet element), `ariaLabelledBy`, `anchors`, `scrimStages`, `noSSR`.
212
+ - Ref (`BottomSheetHandle`): `snapTo`, `open`, `close`, `expand`, `collapse`, `setAllowed`, `setSnapPoints`, `recompute`, `isTop`, `depth`, `setScrim`, `setScrimOverlay`, `addAnchor`, `setScrimStages`, `on`, `getEngine`, `state`.
213
+ - Reactive props: `open`, `snapPoints`, `allowed`, `mode`, `radius`, `maxHeight`, `persistent`, `disableClose`, `disableDrag`, `dragFrom`, `dragFromContent`, `backdropColor`, `backdropOpacity`.
214
+ - Headless: `useBottomSheet(options)` returns `sheetRef`, `handleRef`, `contentRef`, `backdropRef`, `screenRef`, `state` and the methods. You render `.bs-root > .bs-backdrop + .bs-sheet > .bs-handle + .bs-content`.
215
+
216
+ ### Vue 3 (`/vue`)
217
+
218
+ - Props in kebab-case in templates: `:snap-points`, `focus-trap`.
219
+ - `v-model:open`, `v-model:snap`.
220
+ - Slots: default, `header`, `footer` (both get `{ state }`), `button-left`, `button-right`, `screen`, `anchor-<id>`.
221
+ - Events: `@snap`, `@open`, `@opened`, `@close`, `@closed`, `@before-snap`, `@before-close`, `@drag-start`, `@drag-end`, `@drag`, `@progress`, `@change`.
222
+ - `teleport` defaults to `true` and `teleport-to` to `"body"`.
223
+ - Template ref exposes the methods and `state`.
224
+ - Headless: `useBottomSheet(options)`.
225
+
226
+ ### Svelte 5 (`/svelte`)
227
+
228
+ - `bind:open`, `bind:snap`.
229
+ - Snippets: `children`, `header`, `footer`, `leftButton`, `rightButton`, `screen`.
230
+ - Callbacks, lower case: `onsnap`, `onopen`, `onopened`, `onclose`, `onclosed`, `onbeforesnap`, `onbeforeclose`, `ondragstart`, `ondragend`, `ondrag`, `onprogress`, `onchange`.
231
+ - `bind:this` exposes the methods and `getState()`, `getEngine()`.
232
+ - Headless: `createBottomSheet(options)` with `attach({ element, handle, scrollContainer, backdrop })` inside `$effect`.
233
+
234
+ ### Solid (`/solid`)
235
+
236
+ - Controlled open: `open`, `onOpenChange`.
237
+ - `header`, `footer`, `leftButton`, `rightButton`, `screen` are JSX props.
238
+ - Callbacks as in React. `engineRef={engine => …}` gives the engine.
239
+
240
+ ### Qwik (`/qwik`)
241
+
242
+ - Controlled: `open` with `onOpenChange$`, `snap` with `onSnap$`. There is no ref.
243
+ - Slots: default, `q:slot="header"`, `"footer"`, `"leftButton"`, `"rightButton"`, `"screen"`.
244
+ - Callbacks: `onSnap$`, `onOpen$`, `onOpened$`, `onClose$`, `onClosed$`, `onDragStart$`, `onDragEnd$`, `onChange$`.
245
+ - There is no `onBeforeSnap$`, `onBeforeClose$`, `onDrag$` or `onProgress$`, and no `easing`, `linkedSheets` or `features` props.
246
+
247
+ ### Custom element (`/element`)
248
+
249
+ ```html
250
+ <bottom-sheet snap-points="closed:0,half:50%,full:90%" focus-trap="true">
251
+ <h2 slot="header">Title</h2>
252
+ <p>Content</p>
253
+ <button slot="footer">Done</button>
254
+ </bottom-sheet>
255
+ ```
256
+
257
+ - Attributes: `snap-points` (shorthand `id:size,…` or JSON), `allowed`, `initial`, `snap`, `open`, `mode`, `animation`, `focus-trap`, `close-on-escape`, `close-on-route-change`, `lock-body-scroll`, `backdrop`, `backdrop-color`, `scrim-color`, `persistent`, `disable-close`, `disable-drag`, `drag-from`, `drag-from-content`, `fit-content-to-snap`, `stack-effect`, `radius`, `max-height`, `return-focus-to`, `sheet-label`, `handle-label`, `content-label`, `stylesheet`, `options` (JSON of any other engine option).
258
+ - Boolean attributes take the strings `"true"` and `"false"`.
259
+ - Property `options` for values JSON cannot hold.
260
+ - Methods: `open()`, `close()`, `snapTo(id)`, `expand()`, `collapse()`, `setAllowed()`, `setMode()`, `setRadius()`, `setMaxHeight()`, `recompute()`, `isTop()`, `depth()`, `canDismiss()`, `addAnchor()`, `setScrimStages()`, `getEngine()`. Properties: `snap`, `sheetState`.
261
+ - Events (`CustomEvent`, payload in `detail`): `snap`, `open`, `opened`, `close`, `closed`, `before-snap`, `before-close`, `drag-start`, `drag`, `drag-end`, `progress`. Veto `before-snap` and `before-close` with `event.preventDefault()`.
262
+ - Slots: default, `header`, `footer`, `leftButton`, `rightButton`, `screen`, `anchor`.
263
+ - Parts: `sheet`, `handle`, `header`, `content`, `footer`, `backdrop`, `screen`, `left-button`, `right-button`.
264
+ - Angular: add `CUSTOM_ELEMENTS_SCHEMA`. Astro: import the module from a client `<script>`, not from the frontmatter.
265
+
266
+ ### Without a framework
267
+
268
+ ```ts
269
+ import { BottomSheetEngine } from "@surdeddd/bottom-sheet";
270
+ import "@surdeddd/bottom-sheet/styles";
271
+
272
+ const sheet = new BottomSheetEngine({
273
+ element: document.querySelector(".bs-sheet"),
274
+ handle: document.querySelector(".bs-handle"),
275
+ scrollContainer: document.querySelector(".bs-content"),
276
+ backdrop: document.querySelector(".bs-backdrop"),
277
+ snapPoints: [{ id: "closed", size: 0 }, { id: "open", size: "fit" }],
278
+ initial: "closed",
279
+ });
280
+ ```
281
+
282
+ Markup: `div.bs-root > div.bs-backdrop + section.bs-sheet[data-mode="bottom"] > div.bs-handle[role="slider"][tabindex="0"] + div.bs-header? + div.bs-content + div.bs-footer?`. The engine does not listen to the backdrop: add `backdrop.addEventListener("click", () => sheet.canDismiss() && sheet.close("backdrop"))`.
283
+
284
+ ## Styling
285
+
286
+ CSS custom properties, set on `.bs-root`, an ancestor or `:root`; they cross the custom element's shadow boundary.
287
+
288
+ `--bs-surface`, `--bs-surface-border`, `--bs-radius`, `--bs-shadow`, `--bs-handle-color`, `--bs-handle-width`, `--bs-handle-height`, `--bs-handle-padding`, `--bs-header-padding`, `--bs-header-min-height`, `--bs-content-padding`, `--bs-footer-padding`, `--bs-backdrop-color`, `--bs-scrim-color`, `--bs-scrim-blur`.
289
+
290
+ Written by the engine, read-only: `--bs-size` (px), `--bs-progress` (0 to 1), `--bs-keyboard-inset` (px).
291
+
292
+ Class names: `bs-root`, `bs-backdrop`, `bs-screen`, `bs-sheet`, `bs-handle`, `bs-header`, `bs-content`, `bs-footer`, `bs-button-slot`.
293
+
294
+ ## Behaviour worth knowing
295
+
296
+ - The handle is a keyboard slider: the arrow pointing away from the sheet's edge grows it, the opposite one shrinks it, `Home` and `End` go to the smallest and largest point, `Escape` closes.
297
+ - Sheets stack. Each open sheet goes on top; Escape, Back and the focus trap act on the top one.
298
+ - A bottom sheet rests on top of the on-screen keyboard.
299
+ - Every adapter renders on the server. A sheet that starts closed is hidden in the server markup.
300
+ - `open` and `close` fire when a transition starts, `opened` and `closed` when it lands.
301
+
302
+ ## Documentation
303
+
304
+ - [README](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/README.md): overview, option tables, methods, events
305
+ - [React](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/react.md)
306
+ - [Vue](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/vue.md)
307
+ - [Svelte](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/svelte.md)
308
+ - [Solid](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/solid.md)
309
+ - [Qwik](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/qwik.md)
310
+ - [Preact](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/preact.md)
311
+ - [Custom element](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/web-component.md)
312
+ - [Without a framework](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/vanilla.md)
313
+ - [Angular](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/angular.md), [Lit](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/lit.md), [Astro](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/astro.md)
314
+ - [Theming](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/theming.md): tokens, themes, backdrop, scrim, stacking
315
+ - [Content at every snap point](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/content-fit.md): `fitContentToSnap`
316
+ - [Anchors and scrim stages](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/anchors.md)
317
+ - [Overlay](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/overlay.md)
318
+ - [Recipes](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/recipes/README.md): ten worked examples
319
+ - [Migrating from vaul or react-modal-sheet](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/MIGRATION.md)
320
+ - [Changelog](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/CHANGELOG.md)
321
+
322
+ ## Optional
323
+
324
+ - [Architecture](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/ARCHITECTURE.md)
325
+ - [API stability](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/api-stability.md)
326
+ - [WebGL renderer](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/webgl.md)
327
+ - [Plugins](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/plugins.md)
328
+ - [RTL](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/rtl.md)
329
+ - [Browser support](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/browser-support.md)
330
+ - [Live demo](https://bottom-sheet-demo-ashy.vercel.app)