@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.
- package/README.md +513 -525
- package/README.ru.md +685 -0
- package/dist/{BottomSheetCore-COvILxmR.d.ts → BottomSheetCore-B_r4KCQL.d.cts} +48 -12
- package/dist/{BottomSheetCore-CaJwHnxY.d.cts → BottomSheetCore-B_r4KCQL.d.ts} +48 -12
- package/dist/{BottomSheetEngine-C_1aPJsU.d.cts → BottomSheetEngine-0F7mjyvF.d.cts} +1 -1
- package/dist/{BottomSheetEngine-CsgCcCSO.d.ts → BottomSheetEngine-CJV_DDrM.d.ts} +1 -1
- package/dist/core.cjs +810 -436
- package/dist/core.d.cts +2 -3
- package/dist/core.d.ts +2 -3
- package/dist/core.js +810 -436
- package/dist/debug.d.cts +3 -3
- package/dist/debug.d.ts +3 -3
- package/dist/{default-features-BJx7J0NH.d.ts → default-features-4Xwv6aVt.d.ts} +4 -2
- package/dist/{default-features-CTJb8Wdk.d.cts → default-features-DX2NOxP6.d.cts} +4 -2
- package/dist/element.cjs +1428 -699
- package/dist/element.d.cts +15 -5
- package/dist/element.d.ts +15 -5
- package/dist/element.iife.global.js +2 -2
- package/dist/element.js +1428 -699
- package/dist/features.cjs +334 -190
- package/dist/features.d.cts +3 -4
- package/dist/features.d.ts +3 -4
- package/dist/features.js +334 -191
- package/dist/{index-CXN7t3rr.d.ts → index-2shlxhcG.d.cts} +3 -2
- package/dist/{index-BBrJJjy5.d.cts → index-CNCRQnpc.d.ts} +3 -2
- package/dist/index.cjs +1129 -631
- package/dist/index.d.cts +13 -7
- package/dist/index.d.ts +13 -7
- package/dist/index.js +1129 -632
- package/dist/integrations/formik.cjs +21 -10
- package/dist/integrations/formik.d.cts +3 -3
- package/dist/integrations/formik.d.ts +3 -3
- package/dist/integrations/formik.js +22 -11
- package/dist/integrations/react-hook-form.cjs +21 -12
- package/dist/integrations/react-hook-form.d.cts +3 -3
- package/dist/integrations/react-hook-form.d.ts +3 -3
- package/dist/integrations/react-hook-form.js +21 -12
- package/dist/option-keys-DItuAjUw.d.cts +6 -0
- package/dist/option-keys-a5DYNwwO.d.ts +6 -0
- package/dist/overlay.cjs +386 -189
- package/dist/overlay.d.cts +3 -4
- package/dist/overlay.d.ts +3 -4
- package/dist/overlay.js +386 -189
- package/dist/preact.d.cts +3 -4
- package/dist/preact.d.ts +3 -4
- package/dist/qwik-lib/index.qwik.cjs +1306 -671
- package/dist/qwik-lib/index.qwik.mjs +1306 -671
- package/dist/qwik.cjs +1326 -701
- package/dist/qwik.d.cts +14 -30
- package/dist/qwik.d.ts +14 -30
- package/dist/qwik.js +1326 -701
- package/dist/react.cjs +1373 -750
- package/dist/react.d.cts +31 -14
- package/dist/react.d.ts +31 -14
- package/dist/react.js +1375 -752
- package/dist/solid.cjs +1332 -727
- package/dist/solid.d.cts +13 -437
- package/dist/solid.d.ts +13 -437
- package/dist/solid.js +1334 -729
- package/dist/solid.source.jsx +1374 -751
- package/dist/styles.css +20 -6
- package/dist/svelte-core.cjs +1211 -676
- package/dist/svelte-core.js +1207 -677
- package/dist/svelte-src/BottomSheet.svelte +102 -59
- package/dist/svelte.cjs +1324 -707
- package/dist/svelte.d.cts +45 -25
- package/dist/svelte.d.ts +45 -25
- package/dist/svelte.js +1324 -707
- package/dist/vue.cjs +1354 -773
- package/dist/vue.d.cts +20 -3
- package/dist/vue.d.ts +20 -3
- package/dist/vue.js +1354 -773
- package/dist/webgl.cjs +38 -7
- package/dist/webgl.d.cts +3 -3
- package/dist/webgl.d.ts +3 -3
- package/dist/webgl.js +38 -7
- package/llms.txt +330 -0
- package/package.json +62 -42
- package/dist/transform-jVfAikE9.d.cts +0 -3
- 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
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
687
|
-
|
|
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 {
|
|
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 {
|
|
16
|
+
export { webglRenderer };
|
|
17
|
+
export type { WebGLRendererOptions, WebGLUnsupportedReason };
|
package/dist/webgl.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
590
|
-
if (
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
685
|
-
|
|
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)
|