@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/README.ru.md
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
# @surdeddd/bottom-sheet
|
|
2
|
+
|
|
3
|
+
Нижняя шторка (bottom sheet), боковая панель и модальный лист для веба. Один
|
|
4
|
+
движок жестов с точками привязки, пружинной физикой и управлением с
|
|
5
|
+
клавиатуры, и тот же движок под адаптерами для **React, Vue 3, Svelte 5,
|
|
6
|
+
Solid, Qwik, Preact** и под **веб-компонентом** `<bottom-sheet>`, который
|
|
7
|
+
работает в Angular, Lit, Astro и в обычном HTML.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@surdeddd/bottom-sheet)
|
|
10
|
+
[](https://www.npmjs.com/package/@surdeddd/bottom-sheet)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
**[Живое демо](https://bottom-sheet-demo-ashy.vercel.app)** ·
|
|
17
|
+
**[Справочник API](https://surdeddd.github.io/BottomSheet/)** ·
|
|
18
|
+
[История изменений](CHANGELOG.md) ·
|
|
19
|
+
[English](https://github.com/Surdeddd/BottomSheet/blob/main/README.md)
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<picture>
|
|
23
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/sheet-dark.png" />
|
|
24
|
+
<img src="docs/screenshots/sheet-light.png" alt="Шторка на средней точке привязки поверх затемнённой страницы" width="380" />
|
|
25
|
+
</picture>
|
|
26
|
+
<img src="https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/gifs/01-drag-to-close.gif" alt="Шторку перетаскивают между точками привязки и закрывают броском" width="300" />
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
## Чем она отличается
|
|
30
|
+
|
|
31
|
+
- **Один движок на все фреймворки.** Компонент в React ведёт себя так же,
|
|
32
|
+
как в Vue, Svelte, Solid, Qwik или в веб-компоненте: это тонкие адаптеры
|
|
33
|
+
над одним ядром.
|
|
34
|
+
- **Точки привязки в любых единицах.** Пиксели, проценты, `fit` (высота
|
|
35
|
+
самого содержимого) или любая длина CSS: `50dvh`,
|
|
36
|
+
`clamp(200px, 60%, 800px)`.
|
|
37
|
+
- **Четыре края.** `bottom`, `top`, `left`, `right`, а также `start` и `end`,
|
|
38
|
+
которые следуют за направлением письма.
|
|
39
|
+
- **Ощущается как нативная.** Пружина, которая подхватывает скорость пальца,
|
|
40
|
+
резиновое сопротивление за крайней точкой, отдельные пороги для пальца и
|
|
41
|
+
мыши, движение только на композиторе.
|
|
42
|
+
- **Доступность.** Ручка работает как слайдер с клавиатуры, фокус можно
|
|
43
|
+
запереть в шторке и вернуть обратно, всё за модальной шторкой получает
|
|
44
|
+
`inert`, учитываются reduced motion и forced colors.
|
|
45
|
+
- **Знает про телефон.** Встаёт на экранную клавиатуру, учитывает безопасные
|
|
46
|
+
зоны, закрывается кнопкой «Назад», блокирует прокрутку страницы способом,
|
|
47
|
+
который работает в iOS.
|
|
48
|
+
- **Стек.** Несколько шторок открыты одновременно, у каждой свой бэкдроп и
|
|
49
|
+
своя область фокуса.
|
|
50
|
+
- **Небольшой размер.** 25.6 KB gzip у полного движка, 21.9 KB у
|
|
51
|
+
облегчённого ядра, зависимостей в рантайме нет.
|
|
52
|
+
|
|
53
|
+
## Установка
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm i @surdeddd/bottom-sheet
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Фреймворки объявлены необязательными peer-зависимостями: ставится только
|
|
60
|
+
тот, которым вы пользуетесь.
|
|
61
|
+
|
|
62
|
+
## Быстрый старт
|
|
63
|
+
|
|
64
|
+
### Модальная шторка по кнопке
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { useState } from 'react';
|
|
68
|
+
import { BottomSheet } from '@surdeddd/bottom-sheet/react';
|
|
69
|
+
import '@surdeddd/bottom-sheet/styles';
|
|
70
|
+
|
|
71
|
+
export function Filters() {
|
|
72
|
+
const [open, setOpen] = useState(false);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<button onClick={() => setOpen(true)}>Filters</button>
|
|
77
|
+
|
|
78
|
+
<BottomSheet
|
|
79
|
+
open={open}
|
|
80
|
+
onOpenChange={setOpen}
|
|
81
|
+
snapPoints={[
|
|
82
|
+
{ id: 'closed', size: 0 },
|
|
83
|
+
{ id: 'open', size: 'fit' },
|
|
84
|
+
]}
|
|
85
|
+
focusTrap
|
|
86
|
+
header={<h2>Filters</h2>}
|
|
87
|
+
>
|
|
88
|
+
<FilterForm />
|
|
89
|
+
</BottomSheet>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Пропсу `open` нужна точка привязки размером `0`: это и есть закрытое
|
|
96
|
+
состояние. Тап по бэкдропу, Escape и перетаскивание вниз закрывают шторку и
|
|
97
|
+
сообщают об этом через `onOpenChange`.
|
|
98
|
+
|
|
99
|
+
### Шторка, которая всегда на экране
|
|
100
|
+
|
|
101
|
+
Как в картах: полоса снизу, которую тянут вверх.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<BottomSheet
|
|
105
|
+
snapPoints={[
|
|
106
|
+
{ id: 'peek', size: 96 },
|
|
107
|
+
{ id: 'half', size: '45dvh' },
|
|
108
|
+
{ id: 'full', size: '90%' },
|
|
109
|
+
]}
|
|
110
|
+
initial="peek"
|
|
111
|
+
header={<h2>Nearby</h2>}
|
|
112
|
+
>
|
|
113
|
+
<PlaceList />
|
|
114
|
+
</BottomSheet>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Другие фреймворки
|
|
118
|
+
|
|
119
|
+
<details>
|
|
120
|
+
<summary><b>Vue 3</b></summary>
|
|
121
|
+
|
|
122
|
+
```vue
|
|
123
|
+
<script setup lang="ts">
|
|
124
|
+
import { ref } from 'vue';
|
|
125
|
+
import { BottomSheet } from '@surdeddd/bottom-sheet/vue';
|
|
126
|
+
import '@surdeddd/bottom-sheet/styles';
|
|
127
|
+
|
|
128
|
+
const open = ref(false);
|
|
129
|
+
const points = [
|
|
130
|
+
{ id: 'closed', size: 0 },
|
|
131
|
+
{ id: 'open', size: 'fit' },
|
|
132
|
+
];
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<template>
|
|
136
|
+
<button @click="open = true">Filters</button>
|
|
137
|
+
<BottomSheet v-model:open="open" :snap-points="points" focus-trap>
|
|
138
|
+
<template #header><h2>Filters</h2></template>
|
|
139
|
+
<FilterForm />
|
|
140
|
+
</BottomSheet>
|
|
141
|
+
</template>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
[Гайд по Vue →](docs/vue.md)
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
<details>
|
|
149
|
+
<summary><b>Svelte 5</b></summary>
|
|
150
|
+
|
|
151
|
+
```svelte
|
|
152
|
+
<script lang="ts">
|
|
153
|
+
import { BottomSheet } from '@surdeddd/bottom-sheet/svelte';
|
|
154
|
+
import '@surdeddd/bottom-sheet/styles';
|
|
155
|
+
|
|
156
|
+
let open = $state(false);
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<button onclick={() => (open = true)}>Filters</button>
|
|
160
|
+
|
|
161
|
+
<BottomSheet
|
|
162
|
+
bind:open
|
|
163
|
+
snapPoints={[{ id: 'closed', size: 0 }, { id: 'open', size: 'fit' }]}
|
|
164
|
+
focusTrap
|
|
165
|
+
>
|
|
166
|
+
{#snippet header()}<h2>Filters</h2>{/snippet}
|
|
167
|
+
{#snippet children()}<FilterForm />{/snippet}
|
|
168
|
+
</BottomSheet>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
[Гайд по Svelte →](docs/svelte.md)
|
|
172
|
+
|
|
173
|
+
</details>
|
|
174
|
+
|
|
175
|
+
<details>
|
|
176
|
+
<summary><b>Solid</b></summary>
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { createSignal } from 'solid-js';
|
|
180
|
+
import { BottomSheet } from '@surdeddd/bottom-sheet/solid';
|
|
181
|
+
import '@surdeddd/bottom-sheet/styles';
|
|
182
|
+
|
|
183
|
+
export function Filters() {
|
|
184
|
+
const [open, setOpen] = createSignal(false);
|
|
185
|
+
return (
|
|
186
|
+
<>
|
|
187
|
+
<button onClick={() => setOpen(true)}>Filters</button>
|
|
188
|
+
<BottomSheet
|
|
189
|
+
open={open()}
|
|
190
|
+
onOpenChange={setOpen}
|
|
191
|
+
snapPoints={[{ id: 'closed', size: 0 }, { id: 'open', size: 'fit' }]}
|
|
192
|
+
focusTrap
|
|
193
|
+
header={<h2>Filters</h2>}
|
|
194
|
+
>
|
|
195
|
+
<FilterForm />
|
|
196
|
+
</BottomSheet>
|
|
197
|
+
</>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
[Гайд по Solid →](docs/solid.md)
|
|
203
|
+
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
<details>
|
|
207
|
+
<summary><b>Qwik</b></summary>
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { component$, useSignal } from '@builder.io/qwik';
|
|
211
|
+
import { BottomSheet } from '@surdeddd/bottom-sheet/qwik';
|
|
212
|
+
import '@surdeddd/bottom-sheet/styles';
|
|
213
|
+
|
|
214
|
+
export const Filters = component$(() => {
|
|
215
|
+
const open = useSignal(false);
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
<button onClick$={() => (open.value = true)}>Filters</button>
|
|
219
|
+
<BottomSheet
|
|
220
|
+
open={open.value}
|
|
221
|
+
onOpenChange$={next => (open.value = next)}
|
|
222
|
+
snapPoints={[{ id: 'closed', size: 0 }, { id: 'open', size: 'fit' }]}
|
|
223
|
+
focusTrap
|
|
224
|
+
>
|
|
225
|
+
<h2 q:slot="header">Filters</h2>
|
|
226
|
+
<FilterForm />
|
|
227
|
+
</BottomSheet>
|
|
228
|
+
</>
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
[Гайд по Qwik →](docs/qwik.md)
|
|
234
|
+
|
|
235
|
+
</details>
|
|
236
|
+
|
|
237
|
+
<details>
|
|
238
|
+
<summary><b>Веб-компонент</b> (Angular, Lit, Astro, обычный HTML)</summary>
|
|
239
|
+
|
|
240
|
+
```html
|
|
241
|
+
<script type="module">
|
|
242
|
+
import '@surdeddd/bottom-sheet/element';
|
|
243
|
+
</script>
|
|
244
|
+
|
|
245
|
+
<button onclick="document.querySelector('bottom-sheet').open()">Filters</button>
|
|
246
|
+
|
|
247
|
+
<bottom-sheet snap-points="closed:0,open:fit" focus-trap="true">
|
|
248
|
+
<h2 slot="header">Filters</h2>
|
|
249
|
+
<form>…</form>
|
|
250
|
+
</bottom-sheet>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Стили элемент несёт в собственном shadow root, отдельная таблица стилей не
|
|
254
|
+
нужна. Без сборщика подключите его с CDN:
|
|
255
|
+
|
|
256
|
+
```html
|
|
257
|
+
<script src="https://unpkg.com/@surdeddd/bottom-sheet/dist/element.iife.global.js"></script>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
[Гайд по веб-компоненту →](docs/web-component.md)
|
|
261
|
+
|
|
262
|
+
</details>
|
|
263
|
+
|
|
264
|
+
<details>
|
|
265
|
+
<summary><b>Без фреймворка</b></summary>
|
|
266
|
+
|
|
267
|
+
```html
|
|
268
|
+
<div class="bs-root">
|
|
269
|
+
<div class="bs-backdrop"></div>
|
|
270
|
+
<section class="bs-sheet" data-mode="bottom" role="dialog" aria-label="Filters">
|
|
271
|
+
<div class="bs-handle" role="slider" tabindex="0" aria-label="Resize sheet"></div>
|
|
272
|
+
<div class="bs-content">…</div>
|
|
273
|
+
</section>
|
|
274
|
+
</div>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { BottomSheetEngine } from '@surdeddd/bottom-sheet';
|
|
279
|
+
import '@surdeddd/bottom-sheet/styles';
|
|
280
|
+
|
|
281
|
+
const root = document.querySelector('.bs-root')!;
|
|
282
|
+
const sheet = new BottomSheetEngine({
|
|
283
|
+
element: root.querySelector('.bs-sheet')!,
|
|
284
|
+
handle: root.querySelector('.bs-handle')!,
|
|
285
|
+
scrollContainer: root.querySelector('.bs-content')!,
|
|
286
|
+
backdrop: root.querySelector('.bs-backdrop')!,
|
|
287
|
+
snapPoints: [
|
|
288
|
+
{ id: 'closed', size: 0 },
|
|
289
|
+
{ id: 'open', size: 'fit' },
|
|
290
|
+
],
|
|
291
|
+
initial: 'closed',
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
document.querySelector('#filters')!.addEventListener('click', () => sheet.open());
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
[Гайд по движку без фреймворка →](docs/vanilla.md)
|
|
298
|
+
|
|
299
|
+
</details>
|
|
300
|
+
|
|
301
|
+
Preact: `@surdeddd/bottom-sheet/preact` — это React-адаптер, работающий через
|
|
302
|
+
`preact/compat` ([гайд](docs/preact.md)). В Angular, Lit и Astro используется
|
|
303
|
+
веб-компонент или сам движок: [Angular](docs/angular.md), [Lit](docs/lit.md),
|
|
304
|
+
[Astro](docs/astro.md).
|
|
305
|
+
|
|
306
|
+
Гайды в `docs/` написаны на английском.
|
|
307
|
+
|
|
308
|
+
## Основные понятия
|
|
309
|
+
|
|
310
|
+
**Точки привязки** (snap points) — размеры, на которых шторка может
|
|
311
|
+
остановиться. У каждой есть `id`, по которому к ней обращаются, и `size`:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
type SnapPoint =
|
|
315
|
+
| number // пиксели
|
|
316
|
+
| `${number}%` // доля вьюпорта вдоль оси шторки
|
|
317
|
+
| 'fit' | 'content' // высота самой шторки: ручка, шапка, содержимое, подвал
|
|
318
|
+
| 'full' // вся ось
|
|
319
|
+
| string; // любая длина CSS: "50dvh", "clamp(200px, 60%, 800px)"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Точка размером `0` — это **закрытое** состояние. Шторку без такой точки
|
|
323
|
+
нельзя закрыть, только свернуть до самой маленькой.
|
|
324
|
+
|
|
325
|
+
**Три слоя.** Сама *шторка*, *бэкдроп*, который затемняет страницу, и
|
|
326
|
+
необязательный *скрим* (`screen`) для содержимого, которое проявляется за
|
|
327
|
+
шторкой: превью карты, размытая подложка.
|
|
328
|
+
|
|
329
|
+
**Состояние** — один объект: `{ activeId, size, progress, phase, isDragging,
|
|
330
|
+
isAnimating }`. `progress` идёт от 0 на самой маленькой разрешённой точке до
|
|
331
|
+
1 на самой большой. `phase` принимает значения `closed`, `opening`, `open` и
|
|
332
|
+
`closing`.
|
|
333
|
+
|
|
334
|
+
## API
|
|
335
|
+
|
|
336
|
+
Все опции, методы и события ниже работают одинаково в каждом адаптере.
|
|
337
|
+
Компоненты принимают опции как пропсы (`focus-trap` в шаблонах Vue,
|
|
338
|
+
`focus-trap="true"` у веб-компонента).
|
|
339
|
+
|
|
340
|
+
### Опции
|
|
341
|
+
|
|
342
|
+
Размер и положение
|
|
343
|
+
|
|
344
|
+
| Опция | По умолчанию | Что делает |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| `snapPoints` | обязательна | Список `{ id, size }` |
|
|
347
|
+
| `allowed` | все id | Точки, на которых шторка может остановиться сейчас |
|
|
348
|
+
| `initial` | первая разрешённая | Точка, с которой шторка начинает. Если её размер больше `0`, шторка появляется открытой, без события `open` |
|
|
349
|
+
| `mode` | `"bottom"` | Край: `bottom`, `top`, `left`, `right` или `start` / `end` |
|
|
350
|
+
| `maxHeight` | нет | Предел размера шторки: пиксели или длина CSS, которая пересчитывается при изменении вьюпорта |
|
|
351
|
+
| `radius` | `20px` | Радиус скругления |
|
|
352
|
+
| `fitContentToSnap` | `false` | Вся прокручиваемая область и подвал достижимы на каждой точке, а не только на самой большой. [Подробнее](docs/content-fit.md) |
|
|
353
|
+
|
|
354
|
+
Движение
|
|
355
|
+
|
|
356
|
+
| Опция | По умолчанию | Что делает |
|
|
357
|
+
| --- | --- | --- |
|
|
358
|
+
| `animation` | `"spring"` | `spring`, `ios-spring`, `material-bounce`, `snappy`, `tween`, `linear` |
|
|
359
|
+
| `spring` | из пресета | `{ stiffness, damping, mass }` |
|
|
360
|
+
| `duration`, `easing` | `220`, ease-out-back | Длительность в мс и кривая `(t) => number` для анимации `tween` |
|
|
361
|
+
| `settleAnimation` | выключена | `"waapi"` выполняет посадку как Web Animation на композиторе |
|
|
362
|
+
| `respectReducedMotion` | `true` | При `prefers-reduced-motion: reduce` шторка сразу встаёт на точку |
|
|
363
|
+
| `viewTransitions` | `false` | Переход между точками через `document.startViewTransition()`, если браузер его умеет |
|
|
364
|
+
|
|
365
|
+
Жесты
|
|
366
|
+
|
|
367
|
+
| Опция | По умолчанию | Что делает |
|
|
368
|
+
| --- | --- | --- |
|
|
369
|
+
| `dragFrom` | `"handle"` | Откуда можно начать перетаскивание: с ручки (`handle`), со всей шторки (`sheet`) или только из зон `[data-bs-drag]` (`zones`). `[data-bs-no-drag]` исключает поддерево. У шторки без ручки по умолчанию `sheet` |
|
|
370
|
+
| `dragFromContent` | `true` | Тянет ли шторку касание по прокручиваемой области. Можно задать для отдельной точки: `{ id, size, dragFromContent: false }` |
|
|
371
|
+
| `disableDrag` | `false` | Отключает жест; методы продолжают работать |
|
|
372
|
+
| `rubberBand` | `true` | Мягкое сопротивление за крайней точкой |
|
|
373
|
+
| `flickVelocity` | `0.65` px/мс | Скорость, с которой отпускание пальца или пера считается броском. Для мыши порог фиксированный: 0.4 |
|
|
374
|
+
| `dragThreshold` | `18` px | Перетаскивание пальцем или пером короче этого возвращает шторку назад. Для мыши порог фиксированный: 12 |
|
|
375
|
+
|
|
376
|
+
Закрытие
|
|
377
|
+
|
|
378
|
+
| Опция | По умолчанию | Что делает |
|
|
379
|
+
| --- | --- | --- |
|
|
380
|
+
| `closeOnEscape` | `true` | Escape закрывает верхнюю шторку |
|
|
381
|
+
| `closeOnBack` | `false` | Кнопка «Назад» закрывает шторку и не уводит со страницы |
|
|
382
|
+
| `routedTo` | нет | URL, который показывается, пока шторка открыта; «Назад» закрывает её и возвращает адрес |
|
|
383
|
+
| `closeOnRouteChange` | `false` | Закрыть при смене URL |
|
|
384
|
+
| `persistent` | `false` | Пользователь не может закрыть шторку: ни тапом по бэкдропу, ни Escape, ни «Назад», ни жестом, ни клавишами. `close()` по-прежнему закрывает |
|
|
385
|
+
| `disableClose` | `false` | Шторку не закрывает ничто, включая `close()` |
|
|
386
|
+
|
|
387
|
+
Фокус и доступность
|
|
388
|
+
|
|
389
|
+
| Опция | По умолчанию | Что делает |
|
|
390
|
+
| --- | --- | --- |
|
|
391
|
+
| `focusTrap` | `false` | Переносит фокус в шторку при открытии, держит Tab внутри, пока она верхняя, и возвращает фокус при закрытии. Попап, который рисуется в другом месте, пометьте `data-bs-focus-allow`, и фокус сможет туда уйти |
|
|
392
|
+
| `initialFocus` | первый фокусируемый | Вместе с `focusTrap`: элемент или селектор, который получит фокус при открытии. Первый фокусируемый элемент — ручка; `false` ставит фокус на саму шторку |
|
|
393
|
+
| `returnFocusTo` | нет | Элемент, селектор или функция: куда отправить фокус при закрытии. Ловушка фокуса и так возвращает его туда, где он был |
|
|
394
|
+
| `inertSiblings` | `false` | Делает всё вне шторки `inert`, пока она верхняя |
|
|
395
|
+
| `lockBodyScroll` | `true` | Блокирует прокрутку страницы, пока шторка открыта |
|
|
396
|
+
|
|
397
|
+
Бэкдроп и скрим
|
|
398
|
+
|
|
399
|
+
| Опция | По умолчанию | Что делает |
|
|
400
|
+
| --- | --- | --- |
|
|
401
|
+
| `backdropRange` | `[0, 1]` | Отрезок `progress`, на котором проявляется бэкдроп |
|
|
402
|
+
| `backdropOpacity` | `1` | Непрозрачность, которой бэкдроп достигает в конце этого отрезка |
|
|
403
|
+
| `backdropColor` | `rgb(0 0 0 / 0.5)` | Цвет бэкдропа; то же, что токен `--bs-backdrop-color` |
|
|
404
|
+
| `screenRange` | `[0, 1]` | Такой же отрезок для скрима |
|
|
405
|
+
| `scrimMode` | `"full"` | `full` — на весь экран, `above-sheet` — до края шторки, `off` — без слоя |
|
|
406
|
+
| `scrimPreset` | нет | `subtle`, `standard`, `monitoring`, `cinematic`: цвет, размытие и отрезок одной опцией |
|
|
407
|
+
| `scrimColor`, `scrimBlur` | из пресета | Заливка скрима и радиус его размытия `backdrop-filter` |
|
|
408
|
+
| `scrimInteractive` | `false` | Скрим принимает указатель сам, а не пропускает его на страницу |
|
|
409
|
+
| `scrimTapToClose` | `false` | Тап по скриму закрывает шторку |
|
|
410
|
+
|
|
411
|
+
Остальное
|
|
412
|
+
|
|
413
|
+
| Опция | По умолчанию | Что делает |
|
|
414
|
+
| --- | --- | --- |
|
|
415
|
+
| `stackEffect` | `false` | Уменьшает шторки под верхней, как стопка карточек в iOS |
|
|
416
|
+
| `persistKey` | нет | Ключ `localStorage`: шторка начинает с точки, на которой её оставили |
|
|
417
|
+
| `autoCollapseAfter` | выключена | Через сколько миллисекунд покоя шторка вернётся на самую маленькую открытую точку |
|
|
418
|
+
| `linkedSheets` | нет | Другие движки, которые сворачиваются, когда открывается этот |
|
|
419
|
+
| `features` | все | Список возможностей для [облегчённого ядра](#облегчённое-ядро) |
|
|
420
|
+
|
|
421
|
+
Компоненты добавляют несколько собственных пропсов:
|
|
422
|
+
|
|
423
|
+
| Проп | По умолчанию | Что делает |
|
|
424
|
+
| --- | --- | --- |
|
|
425
|
+
| `open`, `onOpenChange` | неуправляемый | Управляемое состояние «открыта». `v-model:open` во Vue, `bind:open` в Svelte, `onOpenChange$` в Qwik, атрибут `open` у веб-компонента |
|
|
426
|
+
| `snap` | неуправляемый | Активная точка привязки как проп: `v-model:snap` во Vue, `bind:snap` в Svelte, `snap` вместе с `onSnap$` в Qwik, атрибут `snap` у веб-компонента. В React и Solid для этого есть ref |
|
|
427
|
+
| `header`, `footer` | нет | Области над прокручиваемой частью и под ней (слоты во Vue, сниппеты в Svelte) |
|
|
428
|
+
| `backdrop` | `true` | Рисовать бэкдроп |
|
|
429
|
+
| `closeOnBackdrop` | `true` | Тап по бэкдропу закрывает шторку |
|
|
430
|
+
| `teleportTo` | нет | Переносит DOM шторки из предка, который её обрезает или трансформирует |
|
|
431
|
+
| `ariaLabel` | `"Bottom sheet"` | Доступное имя шторки |
|
|
432
|
+
| `handleLabel`, `contentLabel` | на английском | Доступные имена ручки и прокручиваемой области, для приложений на другом языке |
|
|
433
|
+
|
|
434
|
+
### Методы
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
sheet.open(); // закрыта → первая разрешённая открытая точка; открытая остаётся на месте
|
|
438
|
+
sheet.open('half'); // на конкретную точку
|
|
439
|
+
sheet.close(); // → закрытая точка; если её нет, самая маленькая
|
|
440
|
+
sheet.snapTo('full');
|
|
441
|
+
sheet.expand(); // → самая большая разрешённая точка
|
|
442
|
+
sheet.collapse(); // → самая маленькая разрешённая открытая точка
|
|
443
|
+
|
|
444
|
+
sheet.setAllowed(['half', 'full']); // сменить набор разрешённых точек
|
|
445
|
+
sheet.setSnapPoints(points); // заменить точки
|
|
446
|
+
sheet.recompute(); // перемерить точку 'fit'
|
|
447
|
+
sheet.setMode('right');
|
|
448
|
+
sheet.setPersistent(true);
|
|
449
|
+
sheet.setDisableClose(true);
|
|
450
|
+
sheet.setDisableDrag(true);
|
|
451
|
+
sheet.setRadius('28px');
|
|
452
|
+
sheet.setMaxHeight('92dvh');
|
|
453
|
+
sheet.setBackdropOpacity(0.6);
|
|
454
|
+
sheet.setBackdropColor('rgb(0 0 0 / 0.4)');
|
|
455
|
+
|
|
456
|
+
sheet.state; // { activeId, size, progress, phase, isDragging, isAnimating }
|
|
457
|
+
sheet.getResolvedSnaps(); // [{ id, size }] в пикселях на этот момент
|
|
458
|
+
sheet.canDismiss(); // false при persistent или disableClose
|
|
459
|
+
sheet.isTop(); // верхняя ли она в стеке
|
|
460
|
+
sheet.destroy();
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`open()`, `close()` и `snapTo()` возвращают промис, который завершается,
|
|
464
|
+
когда шторка встала на место. Их можно вызывать в том же тике, в котором
|
|
465
|
+
шторка смонтирована.
|
|
466
|
+
|
|
467
|
+
Если `setAllowed()` или `setSnapPoints()` убирают активную точку, открытая
|
|
468
|
+
шторка переходит на ближайшую точку, на которой она остаётся открытой.
|
|
469
|
+
|
|
470
|
+
В компонентах эти методы доступны через ref (`ref.current.open()`), у
|
|
471
|
+
веб-компонента — на самом элементе (`el.open()`), у композабла — на
|
|
472
|
+
объекте, который он возвращает. Всё, чего там нет, достаётся через
|
|
473
|
+
`getEngine()`.
|
|
474
|
+
|
|
475
|
+
### События
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
sheet.on('open', ({ id }) => {}); // шторка начинает появляться
|
|
479
|
+
sheet.on('opened', ({ id }) => {}); // встала на место
|
|
480
|
+
sheet.on('close', () => {}); // начинает уходить
|
|
481
|
+
sheet.on('closed', () => {}); // ушла: размонтировать нужно здесь
|
|
482
|
+
sheet.on('snap', ({ id, size, progress }) => {}); // остановилась на точке
|
|
483
|
+
|
|
484
|
+
sheet.on('dragstart', ({ size }) => {});
|
|
485
|
+
sheet.on('drag', ({ size, delta }) => {}); // каждый кадр
|
|
486
|
+
sheet.on('dragend', ({ size, velocity }) => {});
|
|
487
|
+
sheet.on('progress', ({ value, size }) => {}); // каждый кадр любого движения
|
|
488
|
+
|
|
489
|
+
sheet.on('before-snap', ({ id, previousId, cancel }) => {});
|
|
490
|
+
sheet.on('before-close', ({ reason, cancel }) => {});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Чтобы остановить переход, вызовите `cancel()` синхронно внутри `before-snap`
|
|
494
|
+
или `before-close`; `reason` — это `programmatic`, `backdrop`, `escape` или
|
|
495
|
+
`back`.
|
|
496
|
+
|
|
497
|
+
Блокировка прокрутки, ловушка фокуса и запись в истории включаются на
|
|
498
|
+
`open`. Запись в истории снимается на `close`, блокировка и ловушка — на
|
|
499
|
+
`closed`, когда шторки уже нет. Переход, развёрнутый на полпути, снова
|
|
500
|
+
вызывает `open` или `close`, поэтому эти два события всегда чередуются.
|
|
501
|
+
|
|
502
|
+
В компонентах события приходят как пропсы `onSnap`, `onOpen`… (React,
|
|
503
|
+
Solid), `@snap`, `@open`… (Vue), `onsnap`… (Svelte), `onSnap$`… (Qwik) и как
|
|
504
|
+
DOM-события у веб-компонента.
|
|
505
|
+
|
|
506
|
+
### Клавиатура
|
|
507
|
+
|
|
508
|
+
Ручка работает как слайдер. Стрелка, направленная от края шторки, увеличивает
|
|
509
|
+
её (`ArrowUp` для нижней, `ArrowLeft` для правой), противоположная уменьшает,
|
|
510
|
+
`Home` и `End` переводят на самую маленькую и самую большую точку, `Escape`
|
|
511
|
+
закрывает.
|
|
512
|
+
|
|
513
|
+
### Остальной API
|
|
514
|
+
|
|
515
|
+
| Тема | Гайд |
|
|
516
|
+
| --- | --- |
|
|
517
|
+
| Кнопки и панели, закреплённые у края шторки; содержимое скрима для разных состояний | [Anchors and scrim stages](docs/anchors.md) |
|
|
518
|
+
| Изменение скрима на лету | [Theming → the scrim](docs/theming.md#the-scrim) |
|
|
519
|
+
| Лёгкая выезжающая панель без точек привязки | [Overlay](docs/overlay.md) |
|
|
520
|
+
| Шторки, настроенные по маршрутам | [Vanilla → sheet manager](docs/vanilla.md#sheet-manager) |
|
|
521
|
+
| Навигация сразу после закрытия при `closeOnBack` | [Vanilla → waiting for the history](docs/vanilla.md#waiting-for-the-history) |
|
|
522
|
+
| Движение шторки из кода, покадрово | [Recipe: programmatic drag](docs/recipes/programmatic-drag.md) |
|
|
523
|
+
| Свои расширения движка | [Plugins](docs/plugins.md) |
|
|
524
|
+
| Интерфейсы справа налево | [RTL](docs/rtl.md) |
|
|
525
|
+
|
|
526
|
+
## Стилизация
|
|
527
|
+
|
|
528
|
+
Всё визуальное задано пользовательскими свойствами CSS. Их можно выставить на
|
|
529
|
+
`.bs-root`, на любом предке или на `:root`; до shadow root веб-компонента они
|
|
530
|
+
тоже доходят.
|
|
531
|
+
|
|
532
|
+
```css
|
|
533
|
+
.bs-root {
|
|
534
|
+
--bs-surface: #18181b;
|
|
535
|
+
--bs-radius: 28px;
|
|
536
|
+
--bs-handle-color: #71717a;
|
|
537
|
+
--bs-backdrop-color: rgb(0 0 0 / 0.6);
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Движок на каждом кадре пишет `--bs-progress` (от 0 до 1) и `--bs-size`
|
|
542
|
+
(пиксели) на шторку и на `.bs-root`, поэтому всё, что лежит внутри корня,
|
|
543
|
+
например слой `screen` или плавающая кнопка, следует за шторкой без
|
|
544
|
+
JavaScript:
|
|
545
|
+
|
|
546
|
+
```css
|
|
547
|
+
.bs-root .map-preview { filter: blur(calc(var(--bs-progress) * 8px)); }
|
|
548
|
+
.bs-root .fab { bottom: calc(var(--bs-size) + 16px); }
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Три готовых оформления лежат в отдельных таблицах стилей:
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
import '@surdeddd/bottom-sheet/themes/ios'; // а также /material, /vercel
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```html
|
|
558
|
+
<div class="bs-theme-ios"><!-- шторка --></div>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
[Все токены, темы, порядок наложения и части веб-компонента →](docs/theming.md)
|
|
562
|
+
|
|
563
|
+
## Доступность
|
|
564
|
+
|
|
565
|
+
Что встроено и что включается опцией:
|
|
566
|
+
|
|
567
|
+
- **Ручка — это слайдер.** У неё есть имя, текущее значение и шаги по
|
|
568
|
+
стрелкам, и она входит в порядок обхода по Tab. Назовите её на своём языке
|
|
569
|
+
через `handleLabel`; `contentLabel` делает то же для прокручиваемой
|
|
570
|
+
области.
|
|
571
|
+
- **У шторки есть имя.** `ariaLabel` или `ariaLabelledBy`, указывающий на
|
|
572
|
+
заголовок. С `focusTrap` шторка получает роль `dialog` и `aria-modal`.
|
|
573
|
+
- **Фокус.** `focusTrap` переносит фокус в шторку, держит Tab внутри и при
|
|
574
|
+
закрытии возвращает фокус туда, откуда шторку открыли. В стеке фокус держит
|
|
575
|
+
только верхняя шторка.
|
|
576
|
+
- **Страница за шторкой.** `inertSiblings` убирает всё вне верхней шторки из
|
|
577
|
+
досягаемости клавиатуры и экранных читалок.
|
|
578
|
+
- **Объявления.** Каждая остановка объявляется через вежливую live-область.
|
|
579
|
+
- **Движение и контраст.** При `prefers-reduced-motion` анимации заменяются
|
|
580
|
+
мгновенным переходом, при `forced-colors` шторка получает рамку, заметную
|
|
581
|
+
ручку и системное кольцо фокуса.
|
|
582
|
+
|
|
583
|
+
Модальной шторке нужны обе опции: `focusTrap` и `inertSiblings`.
|
|
584
|
+
|
|
585
|
+
## Формы и экранная клавиатура
|
|
586
|
+
|
|
587
|
+
Нижняя шторка встаёт на экранную клавиатуру, а не уходит под неё, поэтому
|
|
588
|
+
поле, в которое печатают, остаётся на виду. С `focusTrap` фокус при открытии
|
|
589
|
+
попадает на ручку, и клавиатура не поднимается, пока пользователь не коснётся
|
|
590
|
+
поля; `initialFocus="#email"` ставит фокус сразу в поле.
|
|
591
|
+
|
|
592
|
+
Чтобы шторка с несохранённым вводом не закрылась, отмените переход:
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
sheet.on('before-close', ({ cancel }) => {
|
|
596
|
+
if (form.isDirty) cancel();
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Для React Hook Form и Formik это одна строка:
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
import { useFormGuard } from '@surdeddd/bottom-sheet/integrations/react-hook-form';
|
|
604
|
+
|
|
605
|
+
useFormGuard(sheetRef, form); // не двигается во время отправки, не закрывается с несохранённым вводом
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Серверный рендеринг
|
|
609
|
+
|
|
610
|
+
Каждый адаптер можно импортировать и отрендерить на сервере; движок
|
|
611
|
+
подключается на клиенте. Шторка, которая начинает закрытой, скрыта уже в
|
|
612
|
+
серверной разметке, поэтому до гидратации ничего не мелькает. Это относится
|
|
613
|
+
к Next.js, Nuxt, SvelteKit, SolidStart, Qwik City и Astro; детали, например
|
|
614
|
+
`"use client"` в Next.js App Router, описаны в гайдах по фреймворкам.
|
|
615
|
+
|
|
616
|
+
## Облегчённое ядро
|
|
617
|
+
|
|
618
|
+
Движок по умолчанию включает все необязательные возможности. Если важнее
|
|
619
|
+
размер, возьмите облегчённое ядро и добавьте к нему только нужное:
|
|
620
|
+
|
|
621
|
+
```ts
|
|
622
|
+
import { BottomSheetCore } from '@surdeddd/bottom-sheet/core';
|
|
623
|
+
import { routeFeature, contentSwipeFeature } from '@surdeddd/bottom-sheet/features';
|
|
624
|
+
|
|
625
|
+
const sheet = new BottomSheetCore({
|
|
626
|
+
element, handle, snapPoints,
|
|
627
|
+
closeOnBack: true,
|
|
628
|
+
features: [routeFeature(), contentSwipeFeature()],
|
|
629
|
+
});
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
| Точка входа | gzip | Что внутри |
|
|
633
|
+
| --- | --- | --- |
|
|
634
|
+
| `@surdeddd/bottom-sheet` | 25.6 KB | Полный движок |
|
|
635
|
+
| `/core` | 21.9 KB | Жесты, точки привязки, фокус, блокировка прокрутки, скрим, стек |
|
|
636
|
+
| `/features` | 4.2 KB | `routeFeature`, `visualViewportFeature`, `contentSwipeFeature`, `contentFitFeature`, `persistFeature`, `autoCollapseFeature` |
|
|
637
|
+
| `/react` `/vue` `/svelte` `/solid` `/qwik` | от 27 до 33 KB | Полный движок вместе с адаптером |
|
|
638
|
+
| `/element` | 33.3 KB | Полный движок, элемент и его стили |
|
|
639
|
+
| `/overlay` | 8.4 KB | Отдельный [оверлей](docs/overlay.md) |
|
|
640
|
+
| `/webgl` | 5.9 KB | Необязательный [рендерер на GPU](docs/webgl.md) для поверхности шторки |
|
|
641
|
+
| `/debug` | 0.9 KB | `attachDebugOverlay(engine)`: панель с состоянием движка в реальном времени |
|
|
642
|
+
|
|
643
|
+
Опция, для которой не подключена возможность, один раз предупреждает в
|
|
644
|
+
режиме разработки и игнорируется.
|
|
645
|
+
|
|
646
|
+
## Поддержка браузеров
|
|
647
|
+
|
|
648
|
+
Chrome и Edge 80, Safari 13.1 (iOS 13.4), Firefox 72. Эту границу задают
|
|
649
|
+
сборка под ES2020 и API, без которых движок не работает: Pointer Events,
|
|
650
|
+
пользовательские свойства CSS, `ResizeObserver`. Всё остальное, от `inert`
|
|
651
|
+
до `backdrop-filter` и WebGL, определяется на месте и при отсутствии мягко
|
|
652
|
+
отключается. [Таблица поддержки →](docs/browser-support.md)
|
|
653
|
+
|
|
654
|
+
## Документация
|
|
655
|
+
|
|
656
|
+
| | |
|
|
657
|
+
| --- | --- |
|
|
658
|
+
| Фреймворки | [React](docs/react.md) · [Vue](docs/vue.md) · [Svelte](docs/svelte.md) · [Solid](docs/solid.md) · [Qwik](docs/qwik.md) · [Preact](docs/preact.md) · [Веб-компонент](docs/web-component.md) · [Без фреймворка](docs/vanilla.md) · [Angular](docs/angular.md) · [Lit](docs/lit.md) · [Astro](docs/astro.md) |
|
|
659
|
+
| Темы | [Стилизация](docs/theming.md) · [Якоря и сцены скрима](docs/anchors.md) · [Содержимое на каждой точке](docs/content-fit.md) · [Оверлей](docs/overlay.md) · [RTL](docs/rtl.md) · [Рендерер WebGL](docs/webgl.md) · [Плагины](docs/plugins.md) |
|
|
660
|
+
| Рецепты | [Десять разобранных примеров](docs/recipes/README.md): панель фильтров, оформление заказа, тред комментариев, навигационная панель, мини-плеер и другие |
|
|
661
|
+
| Примеры | [Запускаемые проекты](https://github.com/Surdeddd/BottomSheet/tree/main/examples) для React, Vue, Svelte, Solid, Lit, веб-компонента и движка без фреймворка |
|
|
662
|
+
| Переход с другой библиотеки | [С vaul или react-modal-sheet](docs/MIGRATION.md) |
|
|
663
|
+
| Проект | [Архитектура](docs/ARCHITECTURE.md) · [Стабильность API](docs/api-stability.md) · [Поддержка браузеров](docs/browser-support.md) · [Участие в разработке](CONTRIBUTING.md) |
|
|
664
|
+
|
|
665
|
+
## Для ИИ-ассистентов
|
|
666
|
+
|
|
667
|
+
[`llms.txt`](llms.txt) — компактное и полное описание API, написанное для
|
|
668
|
+
чтения моделью: точки входа, все опции, имена методов и событий и ошибки,
|
|
669
|
+
которых стоит избегать. Файл входит в npm-пакет и лежит рядом с этим README.
|
|
670
|
+
|
|
671
|
+
## Участие в разработке
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
npm install
|
|
675
|
+
npm run demo # демо-сайт на localhost:5173
|
|
676
|
+
npm test # юнит-тесты
|
|
677
|
+
npm run e2e # тесты в браузере
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Подробности в [CONTRIBUTING.md](CONTRIBUTING.md). Ошибки и идеи — в
|
|
681
|
+
[issues](https://github.com/Surdeddd/BottomSheet/issues).
|
|
682
|
+
|
|
683
|
+
## Лицензия
|
|
684
|
+
|
|
685
|
+
MIT, см. [LICENSE](LICENSE).
|