@surdeddd/bottom-sheet 0.1.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/LICENSE +21 -0
- package/README.md +377 -0
- package/dist/BottomSheetEngine-CvSVbo6c.d.cts +704 -0
- package/dist/BottomSheetEngine-DbO8rW_3.d.ts +704 -0
- package/dist/element.cjs +3442 -0
- package/dist/element.d.cts +88 -0
- package/dist/element.d.ts +88 -0
- package/dist/element.iife.global.js +3 -0
- package/dist/element.js +3442 -0
- package/dist/index.cjs +3693 -0
- package/dist/index.d.cts +184 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +3669 -0
- package/dist/integrations/formik.cjs +27 -0
- package/dist/integrations/formik.d.cts +27 -0
- package/dist/integrations/formik.d.ts +27 -0
- package/dist/integrations/formik.js +25 -0
- package/dist/integrations/react-hook-form.cjs +28 -0
- package/dist/integrations/react-hook-form.d.cts +30 -0
- package/dist/integrations/react-hook-form.d.ts +30 -0
- package/dist/integrations/react-hook-form.js +26 -0
- package/dist/overlay.cjs +1058 -0
- package/dist/overlay.d.cts +282 -0
- package/dist/overlay.d.ts +282 -0
- package/dist/overlay.js +1053 -0
- package/dist/preact.cjs +2 -0
- package/dist/preact.d.cts +5 -0
- package/dist/preact.d.ts +5 -0
- package/dist/preact.js +2 -0
- package/dist/qwik.cjs +3046 -0
- package/dist/qwik.d.cts +55 -0
- package/dist/qwik.d.ts +55 -0
- package/dist/qwik.js +3044 -0
- package/dist/react.cjs +4162 -0
- package/dist/react.d.cts +301 -0
- package/dist/react.d.ts +301 -0
- package/dist/react.js +4156 -0
- package/dist/solid.cjs +3063 -0
- package/dist/solid.d.cts +66 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +3061 -0
- package/dist/styles.css +508 -0
- package/dist/svelte.cjs +3200 -0
- package/dist/svelte.d.ts +89 -0
- package/dist/svelte.js +3177 -0
- package/dist/themes/ios.css +24 -0
- package/dist/themes/material.css +24 -0
- package/dist/themes/vercel.css +24 -0
- package/dist/transform-Brkf-wzo.d.cts +4 -0
- package/dist/transform-Brkf-wzo.d.ts +4 -0
- package/dist/vue.cjs +3361 -0
- package/dist/vue.d.ts +68 -0
- package/dist/vue.js +3358 -0
- package/package.json +288 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { T as TransformAxis } from './transform-Brkf-wzo.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A snap point can be expressed as:
|
|
5
|
+
* - number → pixels
|
|
6
|
+
* - `${n}%` → percent of viewport height (or width for side sheets)
|
|
7
|
+
* - "fit" → fits to header / measured natural height
|
|
8
|
+
* - "full" → 100% of axis
|
|
9
|
+
* - any CSS length → resolved via probe element ("50dvh", "min(80dvh, 600px)",
|
|
10
|
+
* "clamp(200px, 60%, 800px)", "env(safe-area-inset-bottom)")
|
|
11
|
+
* Prefer `dvh` over legacy `vh` — iOS Safari URL bar
|
|
12
|
+
* collapse leaves `vh` stale; the engine warns on `vh`.
|
|
13
|
+
*/
|
|
14
|
+
type SnapPoint = number | `${number}%` | "fit" | "full" | (string & {});
|
|
15
|
+
type SnapPointDef<Id extends string = string> = {
|
|
16
|
+
/** Stable identifier used for `snapTo()` and events. */
|
|
17
|
+
id: Id;
|
|
18
|
+
/** Target size of the sheet in this state. */
|
|
19
|
+
size: SnapPoint;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Extract the union of `id` literals from a `SnapPointDef[]` (or from an
|
|
23
|
+
* `EngineOptions`-shaped object). For literal inference the consumer must
|
|
24
|
+
* pin the array with `as const`:
|
|
25
|
+
*
|
|
26
|
+
* const opts = {
|
|
27
|
+
* snapPoints: [
|
|
28
|
+
* { id: "min", size: 96 },
|
|
29
|
+
* { id: "full", size: "85%" },
|
|
30
|
+
* ] as const,
|
|
31
|
+
* };
|
|
32
|
+
* type Id = SnapId<typeof opts.snapPoints>; // "min" | "full"
|
|
33
|
+
*
|
|
34
|
+
* Without `as const` TS widens `id` to `string` and the helper resolves to
|
|
35
|
+
* `string` — no type error, but no narrowing either.
|
|
36
|
+
*/
|
|
37
|
+
type SnapId<T extends readonly SnapPointDef<string>[] | {
|
|
38
|
+
snapPoints: readonly SnapPointDef<string>[];
|
|
39
|
+
}> = T extends {
|
|
40
|
+
snapPoints: readonly SnapPointDef<infer U>[];
|
|
41
|
+
} ? U : T extends readonly SnapPointDef<infer U>[] ? U : never;
|
|
42
|
+
type SheetMode = TransformAxis;
|
|
43
|
+
/**
|
|
44
|
+
* Named scrim look-and-feel preset. Each preset bundles a `color`, optional
|
|
45
|
+
* `blur`, opacity `range`, and `interactive` default into one value so
|
|
46
|
+
* consumers don't need to remember the underlying scrim fields.
|
|
47
|
+
*
|
|
48
|
+
* - `subtle` — light dim, no blur, late fade-in (good for peeks).
|
|
49
|
+
* - `standard` — moderate dim, no blur, full-range fade (default-ish).
|
|
50
|
+
* - `monitoring` — bluish dim with light blur (dashboard / panel UX).
|
|
51
|
+
* - `cinematic` — heavy dim with strong blur (full-attention modal).
|
|
52
|
+
*/
|
|
53
|
+
type ScrimPreset = "subtle" | "standard" | "monitoring" | "cinematic";
|
|
54
|
+
/** Resolved field set for a `ScrimPreset` — used both at construction and by
|
|
55
|
+
* `setScrim({ preset })` to layer baseline values under explicit overrides. */
|
|
56
|
+
type ScrimPresetConfig = {
|
|
57
|
+
color: string;
|
|
58
|
+
blur: string | undefined;
|
|
59
|
+
range: [number, number];
|
|
60
|
+
interactive: boolean;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Frozen so a consumer can't mutate the table at runtime and silently change
|
|
64
|
+
* every engine that uses a preset. Each entry mirrors the `ScrimPresetConfig`
|
|
65
|
+
* fields exactly.
|
|
66
|
+
*/
|
|
67
|
+
declare const SCRIM_PRESETS: Readonly<Record<ScrimPreset, ScrimPresetConfig>>;
|
|
68
|
+
/**
|
|
69
|
+
* Batch-update payload for `setScrim`. `null` for `color` / `blur` means
|
|
70
|
+
* "clear the inline style" (i.e. revert to whatever CSS provides); `undefined`
|
|
71
|
+
* means "leave as-is". `preset` is applied first and any sibling field
|
|
72
|
+
* overrides it. A single recompute fires at the end so the per-frame opacity
|
|
73
|
+
* stays consistent.
|
|
74
|
+
*
|
|
75
|
+
* `mode` / `tapToClose` / `enabled` mirror the runtime setters
|
|
76
|
+
* `setScrimMode` / `setScrimTapToClose` / `setScrimEnabled` so consumers can
|
|
77
|
+
* flip a "monitoring on/off" toggle in one call without four separate
|
|
78
|
+
* setters racing each other.
|
|
79
|
+
*/
|
|
80
|
+
type ScrimUpdate = {
|
|
81
|
+
color?: string | null;
|
|
82
|
+
blur?: string | null;
|
|
83
|
+
interactive?: boolean;
|
|
84
|
+
range?: [number, number];
|
|
85
|
+
preset?: ScrimPreset;
|
|
86
|
+
mode?: "full" | "above-sheet" | "off";
|
|
87
|
+
tapToClose?: boolean;
|
|
88
|
+
enabled?: boolean;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Where in the scrim area a `setScrimOverlay` injection sits. The wrapper is
|
|
92
|
+
* `position: absolute` so the consumer's scrim must be a positioning context
|
|
93
|
+
* (the engine writes `position: fixed` on `screenComponent` only when
|
|
94
|
+
* `scrimMode: "above-sheet"` — for `"full"` you must size + position the
|
|
95
|
+
* scrim yourself).
|
|
96
|
+
*/
|
|
97
|
+
type ScrimOverlayPosition = "top-left" | "top-center" | "top-right" | "center-left" | "center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
98
|
+
/**
|
|
99
|
+
* Inject a positioned floating element into the scrim area. The classic use
|
|
100
|
+
* case is a "monitoring" UX where the rest of the page is dimmed but a small
|
|
101
|
+
* cluster of badges / quick-action buttons stays clickable above the sheet
|
|
102
|
+
* (the wrapper opts back into `pointer-events: auto` even when the scrim
|
|
103
|
+
* itself has `pointer-events: none`).
|
|
104
|
+
*/
|
|
105
|
+
type ScrimOverlayOptions = {
|
|
106
|
+
/** Element or fragment to inject as scrim children. */
|
|
107
|
+
children: HTMLElement | DocumentFragment;
|
|
108
|
+
/** Where in the scrim area to place. Default: "top-right". */
|
|
109
|
+
position?: ScrimOverlayPosition;
|
|
110
|
+
/**
|
|
111
|
+
* Wrapper gets `pointer-events: auto` so clicks land on children even when
|
|
112
|
+
* the scrim itself has `pointer-events: none`. Default: true.
|
|
113
|
+
*/
|
|
114
|
+
interactive?: boolean;
|
|
115
|
+
/** Padding from the scrim edge in CSS length. Default: "16px". */
|
|
116
|
+
inset?: string;
|
|
117
|
+
};
|
|
118
|
+
type SheetEventMap = {
|
|
119
|
+
/**
|
|
120
|
+
* Fires before a snap transition starts (programmatic or gesture-driven).
|
|
121
|
+
* Listeners may invoke `cancel()` synchronously to abort the transition —
|
|
122
|
+
* useful for gating closes (e.g. "form is dirty, ask first"). The check
|
|
123
|
+
* runs synchronously during emit; async cancellation is not supported.
|
|
124
|
+
*/
|
|
125
|
+
"before-snap": {
|
|
126
|
+
id: string;
|
|
127
|
+
size: number;
|
|
128
|
+
cancel: () => void;
|
|
129
|
+
previousId: string;
|
|
130
|
+
};
|
|
131
|
+
/** Fires when active snap changes (after animation settles). */
|
|
132
|
+
snap: {
|
|
133
|
+
id: string;
|
|
134
|
+
size: number;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Fires continuously during drag/animation: 0..1 within the allowed range.
|
|
138
|
+
*
|
|
139
|
+
* **Payload identity is reused across frames.** The engine mutates a single
|
|
140
|
+
* `{value, size}` object instead of allocating a fresh one per `applySize`
|
|
141
|
+
* (60-120Hz). Mirrors browser `PointerEvent` semantics — consumers must NOT
|
|
142
|
+
* retain a reference (e.g. push to an array) without cloning, or every
|
|
143
|
+
* stored entry will reflect the latest frame's values. Read fields
|
|
144
|
+
* synchronously inside the handler, or copy with `{...payload}` /
|
|
145
|
+
* `payload.value`/`payload.size` capture before any async hand-off.
|
|
146
|
+
*/
|
|
147
|
+
progress: {
|
|
148
|
+
value: number;
|
|
149
|
+
size: number;
|
|
150
|
+
};
|
|
151
|
+
/** Fires on user-initiated drag start. */
|
|
152
|
+
dragstart: {
|
|
153
|
+
size: number;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Fires on each pointer move during drag.
|
|
157
|
+
*
|
|
158
|
+
* **Payload identity is reused across frames.** The engine mutates a single
|
|
159
|
+
* `{size, delta}` object instead of allocating a fresh one per `onMove`
|
|
160
|
+
* (60-120Hz). Mirrors browser `PointerEvent` semantics — consumers must NOT
|
|
161
|
+
* retain a reference (e.g. push to an array) without cloning, or every
|
|
162
|
+
* stored entry will reflect the latest frame's values. Read fields
|
|
163
|
+
* synchronously inside the handler, or copy with `{...payload}` /
|
|
164
|
+
* `payload.size`/`payload.delta` capture before any async hand-off.
|
|
165
|
+
*/
|
|
166
|
+
drag: {
|
|
167
|
+
size: number;
|
|
168
|
+
delta: number;
|
|
169
|
+
};
|
|
170
|
+
/** Fires when drag ends, before snap settles. */
|
|
171
|
+
dragend: {
|
|
172
|
+
size: number;
|
|
173
|
+
velocity: number;
|
|
174
|
+
};
|
|
175
|
+
/** Fires when sheet becomes fully closed (size === 0 / "closed" snap). */
|
|
176
|
+
close: void;
|
|
177
|
+
/** Fires when sheet opens from a closed state. */
|
|
178
|
+
open: {
|
|
179
|
+
id: string;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
type EngineOptions = {
|
|
183
|
+
/** Element that becomes the sheet container. */
|
|
184
|
+
element: HTMLElement;
|
|
185
|
+
/** Element used as the drag handle (defaults to `element`). */
|
|
186
|
+
handle?: HTMLElement;
|
|
187
|
+
/** Element whose scroll position gates pull-to-close swipes. */
|
|
188
|
+
scrollContainer?: HTMLElement;
|
|
189
|
+
/** Optional backdrop element — opacity is bound to progress. */
|
|
190
|
+
backdrop?: HTMLElement;
|
|
191
|
+
/** Optional element that fades in behind the sheet driven by progress. */
|
|
192
|
+
screenComponent?: HTMLElement;
|
|
193
|
+
/** Direction the sheet expands from. Default: "bottom". */
|
|
194
|
+
mode?: SheetMode;
|
|
195
|
+
/** Ordered list of snap points, smallest → largest. */
|
|
196
|
+
snapPoints: SnapPointDef[];
|
|
197
|
+
/** Subset of snap-point ids the sheet may settle on right now. */
|
|
198
|
+
allowed?: string[];
|
|
199
|
+
/** Initial snap id. Falls back to first allowed (or first overall). */
|
|
200
|
+
initial?: string;
|
|
201
|
+
/** Animation duration in ms. Default: 220. */
|
|
202
|
+
duration?: number;
|
|
203
|
+
/** Easing function (t in 0..1 → 0..1). Default: critical-damped spring-ish. */
|
|
204
|
+
easing?: (t: number) => number;
|
|
205
|
+
/** Honour prefers-reduced-motion. Default: true. */
|
|
206
|
+
respectReducedMotion?: boolean;
|
|
207
|
+
/** Velocity threshold (px/ms) above which a flick promotes to next snap. */
|
|
208
|
+
flickVelocity?: number;
|
|
209
|
+
/** Distance threshold (px) below which a drag snaps back to origin. */
|
|
210
|
+
dragThreshold?: number;
|
|
211
|
+
/** Allow dragging beyond max with rubber-banding. Default: true. */
|
|
212
|
+
rubberBand?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* [start, end] progress range (0..1) over which the backdrop fades from 0
|
|
215
|
+
* to 1. Default [0, 1]. Set [0.3, 1] for a "peek" state where the backdrop
|
|
216
|
+
* stays invisible until the sheet expands past the peek.
|
|
217
|
+
*/
|
|
218
|
+
backdropRange?: [number, number];
|
|
219
|
+
/**
|
|
220
|
+
* [start, end] progress range (0..1) over which the screen overlay fades
|
|
221
|
+
* from 0 to 1. Mirror of `backdropRange` for `screenComponent`.
|
|
222
|
+
* Default [0, 1].
|
|
223
|
+
*/
|
|
224
|
+
screenRange?: [number, number];
|
|
225
|
+
/**
|
|
226
|
+
* Positioning of `screenComponent`:
|
|
227
|
+
* - `"full"` (default): consumer controls layout — no inline positioning
|
|
228
|
+
* is applied. Engine only writes opacity and display.
|
|
229
|
+
* - `"above-sheet"`: engine pins the screen overlay to the area NOT
|
|
230
|
+
* occupied by the sheet (above for `mode:"bottom"`, below for `"top"`,
|
|
231
|
+
* etc.) using `position: fixed` + `inset` driven by `--bs-size`. Use
|
|
232
|
+
* this for monitoring-style "dim everything except the active panel"
|
|
233
|
+
* UX where the sheet stays interactive but the rest of the page dims
|
|
234
|
+
* without being clickable.
|
|
235
|
+
* - `"off"`: engine forces the scrim invisible (opacity 0 + display none)
|
|
236
|
+
* and clears its inline `position` / `inset` / `pointer-events` writes.
|
|
237
|
+
* Use this when a parent toggle hides the scrim without re-mounting.
|
|
238
|
+
* Switchable at runtime via `setScrimMode`.
|
|
239
|
+
*/
|
|
240
|
+
scrimMode?: "full" | "above-sheet" | "off";
|
|
241
|
+
/**
|
|
242
|
+
* Quick-win background color for `screenComponent`, written once at
|
|
243
|
+
* construction. Skipped entirely when absent so consumers who style the
|
|
244
|
+
* scrim via CSS pay no inline-style cost. Any valid CSS `background`
|
|
245
|
+
* value (e.g. `"rgba(0,0,0,0.6)"`, `"#000a"`).
|
|
246
|
+
*
|
|
247
|
+
* **Security**: this string is written to inline `style.background`. The
|
|
248
|
+
* value is NOT escaped — pass only trusted CSS, never user input. Any
|
|
249
|
+
* `url(...)` token inside the value will trigger a network request from
|
|
250
|
+
* the page (potential referer/IP leak via attribute selectors). Reject
|
|
251
|
+
* or sanitise consumer input upstream.
|
|
252
|
+
*/
|
|
253
|
+
scrimColor?: string;
|
|
254
|
+
/**
|
|
255
|
+
* Backdrop blur for `screenComponent`. Engine wraps the supplied length in
|
|
256
|
+
* `blur(...)` and writes both `backdropFilter` and the WebKit-prefixed
|
|
257
|
+
* variant once at construction. Pass a CSS length like `"8px"`. Skipped
|
|
258
|
+
* entirely when absent.
|
|
259
|
+
*
|
|
260
|
+
* **Security**: same caveat as `scrimColor` — value is written to inline
|
|
261
|
+
* `style.backdropFilter` without sanitisation. Pass trusted CSS only.
|
|
262
|
+
*/
|
|
263
|
+
scrimBlur?: string;
|
|
264
|
+
/**
|
|
265
|
+
* Whether the scrim accepts pointer events. Default `false` — engine
|
|
266
|
+
* writes `pointer-events: none` so clicks/taps pass through to underlying
|
|
267
|
+
* UI (matches `scrimMode: "above-sheet"`'s existing behaviour). Set
|
|
268
|
+
* `true` when the scrim itself must be interactive (e.g. consumer wires
|
|
269
|
+
* its own click handler). Applied once at construction.
|
|
270
|
+
*/
|
|
271
|
+
scrimInteractive?: boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Click on the scrim snaps to the first non-zero allowed snap (same target
|
|
274
|
+
* as `autoCollapseAfter`). Default `false`. When enabled together with
|
|
275
|
+
* `scrimInteractive: false`, engine auto-promotes pointer-events to `auto`
|
|
276
|
+
* — otherwise the listener would never fire. Listener installs in
|
|
277
|
+
* `attach()` and tears down in `destroy()`.
|
|
278
|
+
*/
|
|
279
|
+
scrimTapToClose?: boolean;
|
|
280
|
+
/**
|
|
281
|
+
* Curated scrim look-and-feel preset — applied at construction BEFORE the
|
|
282
|
+
* individual `scrimColor` / `scrimBlur` / `screenRange` / `scrimInteractive`
|
|
283
|
+
* options, so a preset gives baseline values and any explicit option still
|
|
284
|
+
* wins. Use this to opt into a named visual style without remembering each
|
|
285
|
+
* underlying field. See `SCRIM_PRESETS` for the exact values per preset.
|
|
286
|
+
*/
|
|
287
|
+
scrimPreset?: ScrimPreset;
|
|
288
|
+
/**
|
|
289
|
+
* Animation strategy / preset. The two **base kinds** are physics-driven
|
|
290
|
+
* `"spring"` (default — carries drag velocity into the settle, feels
|
|
291
|
+
* native) and time-driven `"tween"` (fixed-duration easing curve). Four
|
|
292
|
+
* named **presets** layer on top, each prefilling `spring` / `duration`
|
|
293
|
+
* / `easing` with a curated config — consumer-supplied `spring`,
|
|
294
|
+
* `duration`, or `easing` still override the preset's defaults.
|
|
295
|
+
*
|
|
296
|
+
* Subjective feel:
|
|
297
|
+
* - `"spring"` — default critical-damped spring. Native feel.
|
|
298
|
+
* - `"tween"` — fixed-duration ease-out. Predictable timing.
|
|
299
|
+
* - `"ios-spring"` — quick + slightly underdamped, like iOS modals
|
|
300
|
+
* (`{ stiffness: 300, damping: 30, mass: 1 }`). Best for iOS-native
|
|
301
|
+
* apps where users expect Apple's exact bounce signature.
|
|
302
|
+
* - `"material-bounce"` — slower, more bounce, Material You feel
|
|
303
|
+
* (`{ stiffness: 200, damping: 22, mass: 1 }`). Best for Android-
|
|
304
|
+
* native apps and playful, brand-led UIs.
|
|
305
|
+
* - `"linear"` — `tween` with `easeLinear` (no acceleration). Use when
|
|
306
|
+
* the consumer wants explicit reduced-motion-friendly behaviour
|
|
307
|
+
* without trading off the deterministic timing of a tween.
|
|
308
|
+
* - `"snappy"` — fast tween (`duration: 180`, `easeOutQuint`). Best
|
|
309
|
+
* for UI-heavy / dense apps that want IMMEDIATE response — file
|
|
310
|
+
* pickers, chat composers, command palettes.
|
|
311
|
+
*/
|
|
312
|
+
animation?: "spring" | "tween" | "ios-spring" | "material-bounce" | "linear" | "snappy";
|
|
313
|
+
/** Spring config when `animation: "spring"`. Defaults: critical damping. */
|
|
314
|
+
spring?: {
|
|
315
|
+
stiffness?: number;
|
|
316
|
+
damping?: number;
|
|
317
|
+
mass?: number;
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Trap Tab focus inside the sheet when expanded. Default: false. When
|
|
321
|
+
* true, focus is moved into the sheet on open (or to `initialFocus`),
|
|
322
|
+
* Tab cycles within, and original focus is restored on close.
|
|
323
|
+
*/
|
|
324
|
+
focusTrap?: boolean;
|
|
325
|
+
/** Selector or HTMLElement to focus when the sheet opens (focusTrap only). */
|
|
326
|
+
initialFocus?: string | HTMLElement;
|
|
327
|
+
/** Listen for Escape key and close the sheet. Default: true. */
|
|
328
|
+
closeOnEscape?: boolean;
|
|
329
|
+
/** Lock body scroll when the sheet is non-closed. Default: true. */
|
|
330
|
+
lockBodyScroll?: boolean;
|
|
331
|
+
/**
|
|
332
|
+
* Intercept the Android hardware back button (and browser back) — close the
|
|
333
|
+
* sheet instead of navigating away. Pushes a no-op history entry on open.
|
|
334
|
+
* Default: false (opt-in to avoid surprising consumers with their own
|
|
335
|
+
* routing logic).
|
|
336
|
+
*/
|
|
337
|
+
closeOnBack?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Sync the sheet's open/close state with the URL. When set, opening the
|
|
340
|
+
* sheet pushes `routedTo` onto `history` (`history.pushState({__bsRouted:
|
|
341
|
+
* routedTo}, "", routedTo)`); closing the sheet calls `history.back()` if
|
|
342
|
+
* the current entry was the one we pushed. Browser back (`popstate`) on
|
|
343
|
+
* the routed entry programmatically calls `engine.close()`.
|
|
344
|
+
*
|
|
345
|
+
* Coexists with `closeOnBack` — if both are set, `routedTo` wins (the
|
|
346
|
+
* structured version of the same idea). Skipped on SSR (no `window`).
|
|
347
|
+
*
|
|
348
|
+
* **Security:** the value is forwarded directly to `history.pushState` and
|
|
349
|
+
* the URL bar reflects it. Do NOT pass user-controlled input here (e.g.
|
|
350
|
+
* `searchParams.get('next')`) without a same-origin allowlist — although
|
|
351
|
+
* `pushState` itself does not navigate to `javascript:` URLs, downstream
|
|
352
|
+
* code that reads `location.href` (e.g. a "share this URL" button) would
|
|
353
|
+
* become an XSS sink at the consumer site. Cross-origin URLs throw
|
|
354
|
+
* `SecurityError`, which is silently caught.
|
|
355
|
+
*
|
|
356
|
+
* Default: undefined (off).
|
|
357
|
+
*/
|
|
358
|
+
routedTo?: string;
|
|
359
|
+
/**
|
|
360
|
+
* Mark the sheet's siblings as `inert` while open — full-modal a11y. AT
|
|
361
|
+
* and pointer events skip the rest of the page. Default: false. Enable
|
|
362
|
+
* only when the sheet must hijack the entire surface (e.g. confirmation
|
|
363
|
+
* dialog); leave false when the sheet is meant to coexist with chrome
|
|
364
|
+
* outside it (search bar, tab controls, etc.).
|
|
365
|
+
*/
|
|
366
|
+
inertSiblings?: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Persist active snap id to `localStorage[persistKey]` so the sheet returns
|
|
369
|
+
* to its last state after a page reload. Skipped on SSR / private-browsing
|
|
370
|
+
* / disabled-storage. The engine never deletes the entry — that's the
|
|
371
|
+
* consumer's lifecycle decision (e.g. on logout).
|
|
372
|
+
*/
|
|
373
|
+
persistKey?: string;
|
|
374
|
+
/**
|
|
375
|
+
* Auto-collapse to the first non-zero allowed snap (typically `minimized`)
|
|
376
|
+
* after this many milliseconds of no drag/snap activity. Suppressed mid-
|
|
377
|
+
* drag (finger still down) and when already at the target snap. Pass
|
|
378
|
+
* `undefined` to disable.
|
|
379
|
+
*/
|
|
380
|
+
autoCollapseAfter?: number;
|
|
381
|
+
/**
|
|
382
|
+
* Sister sheets that should shrink to their first non-zero allowed snap
|
|
383
|
+
* when THIS sheet opens (closed → open transition). Use case: navigation
|
|
384
|
+
* rail expands → bottom panel auto-minimizes. Set later via
|
|
385
|
+
* `setLinkedSheets` if peers don't exist at construction time.
|
|
386
|
+
*
|
|
387
|
+
* The reaction is one-directional and fires only on the `open` event —
|
|
388
|
+
* the linked sheet's resulting `snap` does not feed back into THIS sheet
|
|
389
|
+
* because `open` is gated to closed→open transitions. Wiring sheets in a
|
|
390
|
+
* cycle (A links B, B links A) is supported by this gate but only safe
|
|
391
|
+
* as long as neither sheet is constructed already-open.
|
|
392
|
+
*/
|
|
393
|
+
linkedSheets?: BottomSheetEngineLike[];
|
|
394
|
+
/**
|
|
395
|
+
* @experimental
|
|
396
|
+
*
|
|
397
|
+
* Wrap **programmatic** `snapTo()` calls in `document.startViewTransition`
|
|
398
|
+
* so Chrome/Safari can run a smooth shared-element morph between snap
|
|
399
|
+
* states. Default: `false`.
|
|
400
|
+
*
|
|
401
|
+
* Important constraints:
|
|
402
|
+
* - **Programmatic snaps only.** Gesture-driven settles (drag → flick)
|
|
403
|
+
* are NOT wrapped — view transitions are designed for discrete
|
|
404
|
+
* state changes and would conflict with a 60fps spring update loop.
|
|
405
|
+
* - **Final settle only.** The engine wraps the single discrete
|
|
406
|
+
* "apply target size" call, not each frame of `animateTo`. When
|
|
407
|
+
* view transitions are enabled, the spring/tween between frames
|
|
408
|
+
* is bypassed and the browser drives the cross-fade instead.
|
|
409
|
+
* - **No-op without browser support.** Falls back to the normal
|
|
410
|
+
* animation path on browsers that don't ship `startViewTransition`
|
|
411
|
+
* (Firefox at the time of writing) — opt-in is safe.
|
|
412
|
+
*
|
|
413
|
+
* Treat this as a **bleeding-edge** opt-in: visually verify on each
|
|
414
|
+
* supported browser before relying on it in production.
|
|
415
|
+
*/
|
|
416
|
+
viewTransitions?: boolean;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Alias surfaced for `linkedSheets` and `Plugin.install`. Keeping this
|
|
421
|
+
* separate from the class lets adapters fake the engine in tests and lets
|
|
422
|
+
* future internal-only methods stay private without breaking plugin typing.
|
|
423
|
+
*/
|
|
424
|
+
type BottomSheetEngineLike = BottomSheetEngine;
|
|
425
|
+
/**
|
|
426
|
+
* Transactional teardown scope passed to `Plugin.install` as the second
|
|
427
|
+
* argument. A plugin that registers DOM listeners / timers BEFORE doing
|
|
428
|
+
* something that might throw should push their cleanup callbacks into the
|
|
429
|
+
* scope — engine drains the scope on install failure so partial side effects
|
|
430
|
+
* don't leak. On successful install the scope is merged into the engine's
|
|
431
|
+
* destroy-time TeardownStack, so plugins that ONLY use scope.add (and don't
|
|
432
|
+
* return a teardown function) still get correct destroy() cleanup.
|
|
433
|
+
*
|
|
434
|
+
* Optional — plugins that don't accept the second argument keep working
|
|
435
|
+
* unchanged. Use when install does multi-step setup that can fail mid-way.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```ts
|
|
439
|
+
* const myPlugin: Plugin = {
|
|
440
|
+
* name: "analytics",
|
|
441
|
+
* install: (engine, scope) => {
|
|
442
|
+
* // Step 1 — listener that must be cleaned up if step 2 throws.
|
|
443
|
+
* const off = engine.on("snap", payload => track("snap", payload));
|
|
444
|
+
* scope.add(off);
|
|
445
|
+
*
|
|
446
|
+
* // Step 2 — risky work that might throw (network probe, feature
|
|
447
|
+
* // detection, etc.). If it throws, engine drains `scope` and unsubs
|
|
448
|
+
* // the listener registered above.
|
|
449
|
+
* const observer = new ResizeObserver(() => {});
|
|
450
|
+
* observer.observe(document.body);
|
|
451
|
+
* scope.add(() => observer.disconnect());
|
|
452
|
+
*
|
|
453
|
+
* // Optional: return ONE additional cleanup. Both `scope.add` callbacks
|
|
454
|
+
* // AND this returned teardown run on `engine.destroy()` (LIFO).
|
|
455
|
+
* return () => track("plugin-destroyed");
|
|
456
|
+
* },
|
|
457
|
+
* };
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
type TeardownScope = {
|
|
461
|
+
/**
|
|
462
|
+
* Register a cleanup callback. Non-functions (`null`/`undefined`/strings/
|
|
463
|
+
* numbers/objects) are silently rejected — useful when a plugin's helper
|
|
464
|
+
* returns `void | (() => void)` and you want to forward the result without
|
|
465
|
+
* a runtime check.
|
|
466
|
+
*/
|
|
467
|
+
add: (fn: (() => void) | null | undefined) => void;
|
|
468
|
+
};
|
|
469
|
+
/**
|
|
470
|
+
* A plugin extends the engine via `engine.use(plugin)`. `install` runs once
|
|
471
|
+
* at registration; the optional teardown function (returned from install)
|
|
472
|
+
* is captured by the engine and invoked on `destroy()`. Plugins are free
|
|
473
|
+
* to subscribe to events, monkey-patch methods, or hold their own state.
|
|
474
|
+
*
|
|
475
|
+
* **Transactional contract**: `install` should be transactional — if it
|
|
476
|
+
* throws midway, any DOM listeners / timers / external state it set up
|
|
477
|
+
* BEFORE the throw will leak (engine can't introspect arbitrary side
|
|
478
|
+
* effects). Use the optional `scope` parameter to push partial cleanup
|
|
479
|
+
* callbacks; the engine will drain `scope` if `install` throws, and merge
|
|
480
|
+
* it into destroy-time teardowns on success.
|
|
481
|
+
*/
|
|
482
|
+
type Plugin = {
|
|
483
|
+
/** Human-readable identifier — surfaced in warnings and debug logs. */
|
|
484
|
+
name: string;
|
|
485
|
+
/**
|
|
486
|
+
* Called once at `engine.use(plugin)` time. Return a function to run
|
|
487
|
+
* during `destroy()` for cleanup (timers, listeners, observers).
|
|
488
|
+
*
|
|
489
|
+
* The optional `scope` argument lets the plugin register partial cleanups
|
|
490
|
+
* before each side effect — engine rolls them back on install failure.
|
|
491
|
+
*/
|
|
492
|
+
install: (engine: BottomSheetEngine, scope: TeardownScope) => void | (() => void);
|
|
493
|
+
};
|
|
494
|
+
type EngineState = {
|
|
495
|
+
size: number;
|
|
496
|
+
activeId: string;
|
|
497
|
+
isDragging: boolean;
|
|
498
|
+
isAnimating: boolean;
|
|
499
|
+
progress: number;
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
type Listener<K extends keyof SheetEventMap> = (payload: SheetEventMap[K]) => void;
|
|
503
|
+
/**
|
|
504
|
+
* Framework-agnostic bottom-sheet engine.
|
|
505
|
+
*
|
|
506
|
+
* Owns the size value, snap-point math, gesture handling, and animation.
|
|
507
|
+
* Renders nothing on its own — applies size to a host element via the chosen
|
|
508
|
+
* `mode` axis. Adapters subscribe to `on(...)` events to mirror the state
|
|
509
|
+
* back into their reactive system.
|
|
510
|
+
*/
|
|
511
|
+
declare class BottomSheetEngine {
|
|
512
|
+
private id;
|
|
513
|
+
private element;
|
|
514
|
+
private handle;
|
|
515
|
+
private scrollContainer?;
|
|
516
|
+
private backdrop?;
|
|
517
|
+
private screenComponent?;
|
|
518
|
+
private mode;
|
|
519
|
+
private flickVelocity;
|
|
520
|
+
private dragThreshold;
|
|
521
|
+
private rubberBandEnabled;
|
|
522
|
+
private scrim;
|
|
523
|
+
private aria;
|
|
524
|
+
private animation;
|
|
525
|
+
private lifecycle;
|
|
526
|
+
private closeOnBack;
|
|
527
|
+
private routedTo;
|
|
528
|
+
private snapPointsRaw;
|
|
529
|
+
private snaps;
|
|
530
|
+
private activeId;
|
|
531
|
+
private size;
|
|
532
|
+
private gesture;
|
|
533
|
+
private rootEl;
|
|
534
|
+
private destroyed;
|
|
535
|
+
private currentViewTransition;
|
|
536
|
+
private currentAbort;
|
|
537
|
+
private sizeWriteSentinel;
|
|
538
|
+
private progressWriteSentinel;
|
|
539
|
+
private isTopSheet;
|
|
540
|
+
private progressPayload;
|
|
541
|
+
private dragContextBuf;
|
|
542
|
+
private bus;
|
|
543
|
+
private persistKey;
|
|
544
|
+
private linkedSheets;
|
|
545
|
+
private scrollCache;
|
|
546
|
+
private teardowns;
|
|
547
|
+
private transformTemplate;
|
|
548
|
+
constructor(opts: EngineOptions);
|
|
549
|
+
get state(): EngineState;
|
|
550
|
+
on<K extends keyof SheetEventMap>(event: K, fn: Listener<K>): () => void;
|
|
551
|
+
/**
|
|
552
|
+
* Register a plugin. `install` runs synchronously; if it returns a function,
|
|
553
|
+
* it's captured for teardown during `destroy()`. No-op if already destroyed
|
|
554
|
+
* — installing into a torn-down engine would leak listeners.
|
|
555
|
+
*
|
|
556
|
+
* Throws from `plugin.install` are caught and rethrown via `queueMicrotask`
|
|
557
|
+
* so a buggy plugin can't take the engine down with it: the engine stays
|
|
558
|
+
* usable, sibling plugins still install, and the error surfaces in dev tools
|
|
559
|
+
* on the next tick instead of being swallowed. Mirrors the per-fn isolation
|
|
560
|
+
* model used for teardowns.
|
|
561
|
+
*
|
|
562
|
+
* Plugins that accept the second `scope` argument get transactional cleanup:
|
|
563
|
+
* push partial-install cleanups via `scope.add(fn)` and the engine drains
|
|
564
|
+
* them on throw, OR merges them into destroy-time teardowns on success.
|
|
565
|
+
*/
|
|
566
|
+
use(plugin: Plugin): this;
|
|
567
|
+
/**
|
|
568
|
+
* Replace the linked-sheets list at runtime. Use when peer engines aren't
|
|
569
|
+
* available at construction time (mounted later in the tree). The wiring
|
|
570
|
+
* to the `open` event is independent of this list — it reads `linkedSheets`
|
|
571
|
+
* lazily on emit, so this setter is a simple field swap.
|
|
572
|
+
*/
|
|
573
|
+
setLinkedSheets(sheets: BottomSheetEngine[]): void;
|
|
574
|
+
/** Read-only view of the current allow-list, for cross-sheet target negotiation. */
|
|
575
|
+
getAllowedIds(): string[];
|
|
576
|
+
/**
|
|
577
|
+
* Animate to a snap point by id. Resolves when the animation completes.
|
|
578
|
+
*
|
|
579
|
+
* Two call shapes:
|
|
580
|
+
* ```ts
|
|
581
|
+
* engine.snapTo("full"); // default velocity
|
|
582
|
+
* engine.snapTo("full", 0.5); // px/ms initial velocity
|
|
583
|
+
* engine.snapTo("full", { velocity: 0.5, signal }); // abortable
|
|
584
|
+
* ```
|
|
585
|
+
*
|
|
586
|
+
* When `opts.signal` is provided, calling `controller.abort()` cancels the
|
|
587
|
+
* in-flight tween/spring and the returned promise resolves without firing
|
|
588
|
+
* `snap`. The captured `AbortSignal` is checked after each await so external
|
|
589
|
+
* aborts ride the same cycle invariant as internal ones (resize, destroy,
|
|
590
|
+
* concurrent snapTo).
|
|
591
|
+
*/
|
|
592
|
+
snapTo(id: string, velocityOrOpts?: number | {
|
|
593
|
+
velocity?: number;
|
|
594
|
+
signal?: AbortSignal;
|
|
595
|
+
},
|
|
596
|
+
/** Internal: skip the before-snap user gate. Used by setAllowed/setSnapPoints
|
|
597
|
+
* for self-repair so a listener can't cancel our state-consistency moves. */
|
|
598
|
+
_skipBeforeSnap?: boolean): Promise<void>;
|
|
599
|
+
/**
|
|
600
|
+
* Drive the sheet to an arbitrary px size, bypassing snap matching.
|
|
601
|
+
* Useful for preview scrubs, animation chaining, or programmatic gestures.
|
|
602
|
+
* Animates with the configured spring/tween. Resolves when the animation
|
|
603
|
+
* completes. Does NOT update `activeId` — call `snapTo(id)` after to commit.
|
|
604
|
+
*
|
|
605
|
+
* Unlike `snapTo`, this does not fire `before-snap` (it isn't a snap commit)
|
|
606
|
+
* nor `snap` (the sheet isn't settling to a known snap). `progress` events
|
|
607
|
+
* still fire via applySize. Target is clamped to `[0, maxAxisSize]`.
|
|
608
|
+
*/
|
|
609
|
+
dragTo(targetSize: number, velocityOrOpts?: number | {
|
|
610
|
+
velocity?: number;
|
|
611
|
+
signal?: AbortSignal;
|
|
612
|
+
}): Promise<void>;
|
|
613
|
+
open(id?: string): Promise<void>;
|
|
614
|
+
close(): Promise<void>;
|
|
615
|
+
setAllowed(ids: string[], snap?: string): void;
|
|
616
|
+
setBackdropRange(range: [number, number]): void;
|
|
617
|
+
setScreenRange(range: [number, number]): void;
|
|
618
|
+
setScrimColor(color: string | undefined | null): void;
|
|
619
|
+
setScrimBlur(blur: string | undefined | null): void;
|
|
620
|
+
setScrimInteractive(interactive: boolean): void;
|
|
621
|
+
/**
|
|
622
|
+
* Batch-update multiple scrim fields in one call. Apply order is:
|
|
623
|
+
* 1. `preset` — fills baseline color/blur/range/interactive.
|
|
624
|
+
* 2. `mode` — re-positions the scrim (fixed inset / clear / hide).
|
|
625
|
+
* 3. `enabled` — flips the opacity contribution on/off.
|
|
626
|
+
* 4. `tapToClose` — installs/tears the click listener.
|
|
627
|
+
* 5. `color` / `blur` / `interactive` / `range` — explicit overrides.
|
|
628
|
+
* 6. ONE `applySize` so the per-frame opacity stays consistent.
|
|
629
|
+
*
|
|
630
|
+
* Performance: collecting changes here (vs calling individual setters)
|
|
631
|
+
* avoids repeated `applySize` calls during a multi-field swap. Setters
|
|
632
|
+
* called here suppress their own internal `applySize` either by passing
|
|
633
|
+
* through fast-paths (mode/enabled no-op when value unchanged) or by
|
|
634
|
+
* being visual-only (color/blur/interactive don't influence opacity).
|
|
635
|
+
*/
|
|
636
|
+
setScrim(opts: ScrimUpdate): void;
|
|
637
|
+
setScrimMode(mode: "full" | "above-sheet" | "off"): void;
|
|
638
|
+
setScrimTapToClose(enabled: boolean): void;
|
|
639
|
+
setScrimEnabled(enabled: boolean): void;
|
|
640
|
+
setScrimOverlay(opts: ScrimOverlayOptions): () => void;
|
|
641
|
+
/**
|
|
642
|
+
* Read-only snapshot of scrim state. Use the `setScrim*` setters to mutate.
|
|
643
|
+
* Stable public API — internals (e.g. cached opacity sentinels) are NOT
|
|
644
|
+
* exposed so consumers can't accidentally couple to ScrimController shape.
|
|
645
|
+
*/
|
|
646
|
+
getScrimState(): {
|
|
647
|
+
mode: "full" | "above-sheet" | "off";
|
|
648
|
+
enabled: boolean;
|
|
649
|
+
};
|
|
650
|
+
setSnapPoints(points: EngineOptions["snapPoints"], allowed?: string[]): void;
|
|
651
|
+
destroy(): void;
|
|
652
|
+
private emit;
|
|
653
|
+
/**
|
|
654
|
+
* Abort the current cycle's signal and replace the controller. Returns the
|
|
655
|
+
* fresh signal so the caller can capture it locally before any await — when
|
|
656
|
+
* a future newCycle() / destroy aborts our controller, the captured signal
|
|
657
|
+
* flips to aborted and post-await guards short-circuit.
|
|
658
|
+
*/
|
|
659
|
+
private newCycle;
|
|
660
|
+
/**
|
|
661
|
+
* Fire `before-snap` and report whether a listener cancelled it. The
|
|
662
|
+
* `cancel()` callback exposed to listeners is frozen after the synchronous
|
|
663
|
+
* emit phase so async callers can't silently miss — they get a console
|
|
664
|
+
* warning. Returns `true` when cancelled so callers can early-return /
|
|
665
|
+
* spring-back without re-implementing the cancel/frozen pattern.
|
|
666
|
+
*/
|
|
667
|
+
private emitBeforeSnap;
|
|
668
|
+
private recomputeSnaps;
|
|
669
|
+
/**
|
|
670
|
+
* Memoised `allowedRange()` — delegates to SnapResolver which owns the cache.
|
|
671
|
+
* Engine wrapper kept so existing call sites don't all need updating;
|
|
672
|
+
* resolver-level memoization survives across method invocations.
|
|
673
|
+
*/
|
|
674
|
+
private getAllowedRange;
|
|
675
|
+
/**
|
|
676
|
+
* Write ARIA slider value attributes onto the handle so the engine itself
|
|
677
|
+
* is the source of truth for screen-reader announcements. Called from the
|
|
678
|
+
* constructor and from every code path that mutates `activeId` /
|
|
679
|
+
* `allowedIds`. WAI-ARIA 1.2 requires `aria-valuenow` for `role="slider"` —
|
|
680
|
+
* without it screen readers can't announce drag/snap progress to keyboard
|
|
681
|
+
* + AT users (WCAG 4.1.2 Name, Role, Value).
|
|
682
|
+
*
|
|
683
|
+
* Engine does NOT set `role="slider"` itself — that's the adapter's or
|
|
684
|
+
* consumer markup's responsibility. We only decorate with dynamic values.
|
|
685
|
+
*
|
|
686
|
+
* Empty allowed-list is malformed input (settable via `setAllowed([])`);
|
|
687
|
+
* short-circuit to remove ARIA value attributes so screen readers see a
|
|
688
|
+
* no-value slider rather than a degenerate `min=max=now=0` (WAI-ARIA 1.2
|
|
689
|
+
* requires `valuemax > valuemin` for `role="slider"`).
|
|
690
|
+
*/
|
|
691
|
+
private updateAriaSlider;
|
|
692
|
+
private registerInStack;
|
|
693
|
+
private attach;
|
|
694
|
+
private attachFeatures;
|
|
695
|
+
private settleAfterDrag;
|
|
696
|
+
/** Trigger a brief haptic tick (iOS Safari + Android Chrome support). */
|
|
697
|
+
private haptic;
|
|
698
|
+
private applySize;
|
|
699
|
+
private computeProgress;
|
|
700
|
+
private handleOpen;
|
|
701
|
+
private handleClose;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export { BottomSheetEngine as B, type EngineState as E, type Plugin as P, type SnapPointDef as S, type TeardownScope as T, type SheetMode as a, type EngineOptions as b, type SnapPoint as c, type SheetEventMap as d, SCRIM_PRESETS as e, type ScrimOverlayOptions as f, type ScrimOverlayPosition as g, type ScrimPreset as h, type ScrimPresetConfig as i, type ScrimUpdate as j, type SnapId as k };
|