@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
package/dist/styles.css
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/* =====================================================================
|
|
2
|
+
* @surdeddd/bottom-sheet — base styles
|
|
3
|
+
* Override any token below to retheme. All sizes use logical defaults
|
|
4
|
+
* so the same stylesheet works for bottom / top / left / right modes.
|
|
5
|
+
* ===================================================================== */
|
|
6
|
+
|
|
7
|
+
:where(.bs-root) {
|
|
8
|
+
/* surface */
|
|
9
|
+
--bs-surface: #ffffff;
|
|
10
|
+
--bs-surface-border: rgba(225, 227, 237, 1);
|
|
11
|
+
--bs-shadow: 0 -3px 12px rgba(0, 0, 0, 0.12);
|
|
12
|
+
--bs-radius: 20px;
|
|
13
|
+
|
|
14
|
+
/* handle — WCAG 1.4.11 contrast against light surface ≥ 3:1 */
|
|
15
|
+
--bs-handle-color: #b5b3a8;
|
|
16
|
+
--bs-handle-width: 40px;
|
|
17
|
+
--bs-handle-height: 5px;
|
|
18
|
+
|
|
19
|
+
/* layout */
|
|
20
|
+
--bs-z-backdrop: 90;
|
|
21
|
+
--bs-z-sheet: 100;
|
|
22
|
+
--bs-header-min-height: 80px;
|
|
23
|
+
|
|
24
|
+
/* backdrop */
|
|
25
|
+
--bs-backdrop-color: rgba(0, 0, 0, 0.5);
|
|
26
|
+
|
|
27
|
+
/* scrim — dimming layer for the area NOT occupied by the sheet
|
|
28
|
+
(used with scrimMode: "above-sheet" or the .bs-scrim* recipes below).
|
|
29
|
+
Override these to retheme without rewriting the recipe. */
|
|
30
|
+
--bs-scrim-color: rgba(0, 0, 0, 0.4);
|
|
31
|
+
--bs-scrim-blur: 0px;
|
|
32
|
+
--bs-z-scrim: calc(var(--bs-z-sheet) - 1);
|
|
33
|
+
|
|
34
|
+
/* engine writes these — read them in your own CSS to react to drag */
|
|
35
|
+
--bs-size: 0px;
|
|
36
|
+
--bs-progress: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.bs-backdrop {
|
|
40
|
+
position: fixed;
|
|
41
|
+
inset: 0;
|
|
42
|
+
background: var(--bs-backdrop-color);
|
|
43
|
+
opacity: 0;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
z-index: var(--bs-z-backdrop);
|
|
46
|
+
/* Transition only when NOT actively dragging/animating — engine writes
|
|
47
|
+
opacity per-frame and the transition would delay it by 150ms. */
|
|
48
|
+
transition: opacity 0.15s linear;
|
|
49
|
+
will-change: opacity;
|
|
50
|
+
}
|
|
51
|
+
.bs-root[data-dragging="true"] .bs-backdrop,
|
|
52
|
+
.bs-root[data-animating="true"] .bs-backdrop {
|
|
53
|
+
transition: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ----- sheet ----- */
|
|
57
|
+
.bs-sheet {
|
|
58
|
+
position: fixed;
|
|
59
|
+
background: var(--bs-surface);
|
|
60
|
+
box-shadow: var(--bs-shadow);
|
|
61
|
+
z-index: var(--bs-z-sheet);
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
/* engine sets touch-action on the handle directly; this is for clicks. */
|
|
66
|
+
-webkit-tap-highlight-color: transparent;
|
|
67
|
+
user-select: none;
|
|
68
|
+
overscroll-behavior: contain;
|
|
69
|
+
/* engine drives the size via transform translate3d. */
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.bs-sheet[data-mode="bottom"] {
|
|
73
|
+
left: 0;
|
|
74
|
+
right: 0;
|
|
75
|
+
bottom: 0;
|
|
76
|
+
width: 100%;
|
|
77
|
+
border-top-left-radius: var(--bs-radius);
|
|
78
|
+
border-top-right-radius: var(--bs-radius);
|
|
79
|
+
border-top: 1px solid var(--bs-surface-border);
|
|
80
|
+
/* iPhone home-indicator safe area. */
|
|
81
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
82
|
+
/* Progress-driven shadow depth — deeper as the sheet expands. */
|
|
83
|
+
box-shadow: 0
|
|
84
|
+
calc(var(--bs-progress, 0) * -2px - 3px)
|
|
85
|
+
calc(var(--bs-progress, 0) * 18px + 12px)
|
|
86
|
+
rgba(0, 0, 0, calc(var(--bs-progress, 0) * 0.15 + 0.12));
|
|
87
|
+
}
|
|
88
|
+
.bs-sheet[data-mode="top"] {
|
|
89
|
+
left: 0;
|
|
90
|
+
right: 0;
|
|
91
|
+
top: 0;
|
|
92
|
+
width: 100%;
|
|
93
|
+
border-bottom-left-radius: var(--bs-radius);
|
|
94
|
+
border-bottom-right-radius: var(--bs-radius);
|
|
95
|
+
border-bottom: 1px solid var(--bs-surface-border);
|
|
96
|
+
flex-direction: column-reverse;
|
|
97
|
+
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
|
|
98
|
+
padding-top: env(safe-area-inset-top);
|
|
99
|
+
}
|
|
100
|
+
.bs-sheet[data-mode="left"] {
|
|
101
|
+
top: 0;
|
|
102
|
+
bottom: 0;
|
|
103
|
+
left: 0;
|
|
104
|
+
height: 100%;
|
|
105
|
+
border-top-right-radius: var(--bs-radius);
|
|
106
|
+
border-bottom-right-radius: var(--bs-radius);
|
|
107
|
+
border-right: 1px solid var(--bs-surface-border);
|
|
108
|
+
/* default flex layout (column) — handle is positioned-absolute at the
|
|
109
|
+
open edge (right) so column flow doesn't matter for it */
|
|
110
|
+
box-shadow: 3px 0 12px rgba(0, 0, 0, 0.12);
|
|
111
|
+
}
|
|
112
|
+
.bs-sheet[data-mode="right"] {
|
|
113
|
+
top: 0;
|
|
114
|
+
bottom: 0;
|
|
115
|
+
right: 0;
|
|
116
|
+
height: 100%;
|
|
117
|
+
border-top-left-radius: var(--bs-radius);
|
|
118
|
+
border-bottom-left-radius: var(--bs-radius);
|
|
119
|
+
border-left: 1px solid var(--bs-surface-border);
|
|
120
|
+
box-shadow: -3px 0 12px rgba(0, 0, 0, 0.12);
|
|
121
|
+
}
|
|
122
|
+
/* In side modes, content sits inside the sheet but with padding to clear
|
|
123
|
+
the absolutely-positioned handle strip on the open edge. */
|
|
124
|
+
.bs-sheet[data-mode="left"] .bs-content {
|
|
125
|
+
padding-right: 36px;
|
|
126
|
+
}
|
|
127
|
+
.bs-sheet[data-mode="right"] .bs-content {
|
|
128
|
+
padding-left: 36px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ----- handle ----- */
|
|
132
|
+
.bs-handle {
|
|
133
|
+
flex-shrink: 0;
|
|
134
|
+
/* WCAG 2.5.5 / Apple HIG / Material — 44px minimum hit target. */
|
|
135
|
+
min-height: 44px;
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
padding: 8px 16px 4px;
|
|
140
|
+
cursor: grab;
|
|
141
|
+
touch-action: none;
|
|
142
|
+
user-select: none;
|
|
143
|
+
background: var(--bs-surface);
|
|
144
|
+
-webkit-tap-highlight-color: transparent;
|
|
145
|
+
outline: none;
|
|
146
|
+
/* anchor point for the optional .bs-handle--ticks ::after dots */
|
|
147
|
+
position: relative;
|
|
148
|
+
}
|
|
149
|
+
.bs-handle:active {
|
|
150
|
+
cursor: grabbing;
|
|
151
|
+
}
|
|
152
|
+
.bs-handle:focus-visible {
|
|
153
|
+
outline: 2px solid currentColor;
|
|
154
|
+
outline-offset: -2px;
|
|
155
|
+
border-radius: var(--bs-radius);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.bs-handle::before {
|
|
159
|
+
content: "";
|
|
160
|
+
display: block;
|
|
161
|
+
width: var(--bs-handle-width);
|
|
162
|
+
height: var(--bs-handle-height);
|
|
163
|
+
background: var(--bs-handle-color);
|
|
164
|
+
border-radius: calc(var(--bs-handle-height) / 2);
|
|
165
|
+
margin: 0 auto 8px;
|
|
166
|
+
transition: width 0.15s ease, background 0.15s ease, transform 0.15s ease;
|
|
167
|
+
pointer-events: none; /* always hit the padding-zoned parent */
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Side-mode handle: a thin grab-strip docked to the sheet's open edge,
|
|
171
|
+
absolutely positioned so it sits on the visible boundary regardless of
|
|
172
|
+
the sheet's flex-direction. The header content inside it is hidden in
|
|
173
|
+
side modes — for vertical sheets the handle is just a grip. The hidden
|
|
174
|
+
header content stays in DOM (and out of the a11y tree thanks to
|
|
175
|
+
`display: none`) so screen readers don't read a stale header here —
|
|
176
|
+
consumers should put descriptive content inside the scrollable area
|
|
177
|
+
instead in side modes. */
|
|
178
|
+
.bs-sheet[data-mode="left"] .bs-handle,
|
|
179
|
+
.bs-sheet[data-mode="right"] .bs-handle {
|
|
180
|
+
position: absolute;
|
|
181
|
+
top: 0;
|
|
182
|
+
bottom: 0;
|
|
183
|
+
width: 28px;
|
|
184
|
+
min-width: 0;
|
|
185
|
+
min-height: 0;
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
padding: 0;
|
|
191
|
+
background: var(--bs-surface);
|
|
192
|
+
z-index: 1;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
}
|
|
195
|
+
.bs-sheet[data-mode="left"] .bs-handle > *,
|
|
196
|
+
.bs-sheet[data-mode="right"] .bs-handle > * {
|
|
197
|
+
display: none;
|
|
198
|
+
}
|
|
199
|
+
.bs-sheet[data-mode="left"] .bs-handle {
|
|
200
|
+
right: 0;
|
|
201
|
+
border-left: 1px solid var(--bs-surface-border);
|
|
202
|
+
}
|
|
203
|
+
.bs-sheet[data-mode="right"] .bs-handle {
|
|
204
|
+
left: 0;
|
|
205
|
+
border-right: 1px solid var(--bs-surface-border);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.bs-sheet[data-mode="left"] .bs-handle::before,
|
|
209
|
+
.bs-sheet[data-mode="right"] .bs-handle::before {
|
|
210
|
+
width: var(--bs-handle-height);
|
|
211
|
+
height: var(--bs-handle-width);
|
|
212
|
+
margin: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.bs-sheet:hover .bs-handle::before {
|
|
216
|
+
background: color-mix(in oklab, var(--bs-handle-color), #000 15%);
|
|
217
|
+
}
|
|
218
|
+
.bs-handle:active::before {
|
|
219
|
+
background: color-mix(in oklab, var(--bs-handle-color), #000 35%);
|
|
220
|
+
transform: scaleX(1.15);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* ----- snap-target ticks (opt-in via .bs-handle--ticks) -----
|
|
224
|
+
Material You / Apple Music inspired: while dragging, render a faint row
|
|
225
|
+
of dots near the grip to suggest "this thing has multiple stops".
|
|
226
|
+
Decorative only — positions are NOT computed from real snap points.
|
|
227
|
+
Hidden in idle state, fades in during drag. Opt-in via the modifier
|
|
228
|
+
class so consumers who don't want the hint don't get it. */
|
|
229
|
+
.bs-handle.bs-handle--ticks::after {
|
|
230
|
+
content: "";
|
|
231
|
+
display: block;
|
|
232
|
+
position: absolute;
|
|
233
|
+
left: 50%;
|
|
234
|
+
/* Sit just under the grip in vertical (bottom/top) modes. Translate -50%
|
|
235
|
+
so the dot row is centered horizontally regardless of width. */
|
|
236
|
+
bottom: 6px;
|
|
237
|
+
transform: translateX(-50%);
|
|
238
|
+
width: calc(var(--bs-handle-width) * 2);
|
|
239
|
+
height: 3px;
|
|
240
|
+
/* 3px dots spaced every 8px — purely visual hint. */
|
|
241
|
+
background-image: radial-gradient(
|
|
242
|
+
circle,
|
|
243
|
+
var(--bs-handle-color) 0 1.5px,
|
|
244
|
+
transparent 1.6px
|
|
245
|
+
);
|
|
246
|
+
background-size: 8px 3px;
|
|
247
|
+
background-repeat: repeat-x;
|
|
248
|
+
background-position: center;
|
|
249
|
+
opacity: 0;
|
|
250
|
+
transition: opacity 0.18s ease-out;
|
|
251
|
+
pointer-events: none;
|
|
252
|
+
}
|
|
253
|
+
/* Only visible while the user is actively dragging the sheet. */
|
|
254
|
+
.bs-root[data-dragging="true"] .bs-handle.bs-handle--ticks::after {
|
|
255
|
+
opacity: 0.4;
|
|
256
|
+
}
|
|
257
|
+
/* Top mode: dots above the grip instead of below. */
|
|
258
|
+
.bs-sheet[data-mode="top"] .bs-handle.bs-handle--ticks::after {
|
|
259
|
+
bottom: auto;
|
|
260
|
+
top: 6px;
|
|
261
|
+
}
|
|
262
|
+
/* Side modes: dots run vertically alongside the (vertical) grip. */
|
|
263
|
+
.bs-sheet[data-mode="left"] .bs-handle.bs-handle--ticks::after,
|
|
264
|
+
.bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after {
|
|
265
|
+
left: 50%;
|
|
266
|
+
top: 50%;
|
|
267
|
+
bottom: auto;
|
|
268
|
+
transform: translate(-50%, -50%);
|
|
269
|
+
width: 3px;
|
|
270
|
+
height: calc(var(--bs-handle-width) * 2);
|
|
271
|
+
background-size: 3px 8px;
|
|
272
|
+
background-repeat: repeat-y;
|
|
273
|
+
/* Offset the column so it sits beside the grip, not on top of it. */
|
|
274
|
+
margin-left: calc(var(--bs-handle-height) + 6px);
|
|
275
|
+
}
|
|
276
|
+
.bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after {
|
|
277
|
+
margin-left: calc(-1 * (var(--bs-handle-height) + 6px));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* ----- content ----- */
|
|
281
|
+
.bs-content {
|
|
282
|
+
flex: 1;
|
|
283
|
+
min-height: 0;
|
|
284
|
+
overflow-y: auto;
|
|
285
|
+
-webkit-overflow-scrolling: touch;
|
|
286
|
+
overscroll-behavior: contain;
|
|
287
|
+
padding: 0 16px 16px;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* ----- screen-component (fades in behind sheet, opacity = --bs-progress) -----
|
|
291
|
+
*
|
|
292
|
+
* Two ready-made scrim recipes for the engine's `screenComponent`:
|
|
293
|
+
*
|
|
294
|
+
* 1. `.bs-screen` — base. Engine writes `opacity` and `display`; consumer
|
|
295
|
+
* controls layout. Use directly when scrimMode: "full" (default).
|
|
296
|
+
*
|
|
297
|
+
* 2. `.bs-scrim` — opinionated scrim with token-driven color + optional
|
|
298
|
+
* blur. Drop on the same element as `.bs-screen` (or alone) for an
|
|
299
|
+
* instant "monitoring-style" dim with no extra CSS.
|
|
300
|
+
*
|
|
301
|
+
* 3. `.bs-scrim--above` — pairs with `scrimMode: "above-sheet"`. The
|
|
302
|
+
* engine writes the `inset` so the scrim covers only the area NOT
|
|
303
|
+
* occupied by the sheet; this class only adds the visual treatment.
|
|
304
|
+
*
|
|
305
|
+
* Tokens (override at any cascade level):
|
|
306
|
+
* --bs-scrim-color default rgba(0, 0, 0, 0.4)
|
|
307
|
+
* --bs-scrim-blur default 0px (set to e.g. 8px for backdrop blur)
|
|
308
|
+
* --bs-z-scrim default calc(--bs-z-sheet - 1)
|
|
309
|
+
*/
|
|
310
|
+
.bs-screen {
|
|
311
|
+
position: absolute;
|
|
312
|
+
inset: 0;
|
|
313
|
+
pointer-events: none;
|
|
314
|
+
display: none;
|
|
315
|
+
z-index: var(--bs-z-scrim);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.bs-scrim {
|
|
319
|
+
background: var(--bs-scrim-color);
|
|
320
|
+
/* Engine writes opacity per-frame; transition only when not actively
|
|
321
|
+
dragging/animating to avoid the 150ms lag on fast snaps. */
|
|
322
|
+
transition: opacity 0.15s linear;
|
|
323
|
+
will-change: opacity;
|
|
324
|
+
}
|
|
325
|
+
.bs-root[data-dragging="true"] .bs-scrim,
|
|
326
|
+
.bs-root[data-animating="true"] .bs-scrim {
|
|
327
|
+
transition: none;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* Optional blur — opt-in by setting --bs-scrim-blur to a non-zero length.
|
|
331
|
+
`backdrop-filter: blur(0)` is a no-op visually but still triggers a
|
|
332
|
+
compositing layer; the calc() guards against accidental layer creation
|
|
333
|
+
when the consumer hasn't customised the token. */
|
|
334
|
+
.bs-scrim {
|
|
335
|
+
backdrop-filter: blur(var(--bs-scrim-blur));
|
|
336
|
+
-webkit-backdrop-filter: blur(var(--bs-scrim-blur));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* `.bs-scrim--above` is a documented marker class for `scrimMode:
|
|
340
|
+
"above-sheet"` — the engine writes the `inset` + `position: fixed`
|
|
341
|
+
inline. Add your own declarations under `.bs-scrim--above { ... }` to
|
|
342
|
+
override the visual treatment per-context. */
|
|
343
|
+
|
|
344
|
+
/* ----- buttons docked above the sheet ----- */
|
|
345
|
+
.bs-button-slot {
|
|
346
|
+
position: absolute;
|
|
347
|
+
z-index: calc(var(--bs-z-sheet) + 1);
|
|
348
|
+
pointer-events: auto;
|
|
349
|
+
}
|
|
350
|
+
.bs-button-slot[data-side="left"] {
|
|
351
|
+
left: 16px;
|
|
352
|
+
bottom: calc(var(--bs-size, 0px) + 12px);
|
|
353
|
+
transition: bottom 0.15s ease;
|
|
354
|
+
}
|
|
355
|
+
.bs-button-slot[data-side="right"] {
|
|
356
|
+
right: 16px;
|
|
357
|
+
bottom: calc(var(--bs-size, 0px) + 12px);
|
|
358
|
+
transition: bottom 0.15s ease;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* ----- accessibility ----- */
|
|
362
|
+
@media (prefers-reduced-motion: reduce) {
|
|
363
|
+
.bs-backdrop,
|
|
364
|
+
.bs-handle::before,
|
|
365
|
+
.bs-button-slot[data-side="left"],
|
|
366
|
+
.bs-button-slot[data-side="right"] {
|
|
367
|
+
transition: none !important;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ----- dark mode token defaults ----- */
|
|
372
|
+
@media (prefers-color-scheme: dark) {
|
|
373
|
+
:where(.bs-root) {
|
|
374
|
+
--bs-surface: #1a1a1d;
|
|
375
|
+
--bs-surface-border: rgba(255, 255, 255, 0.08);
|
|
376
|
+
/* WCAG 1.4.11 — non-text contrast ≥ 3:1 against the surface. */
|
|
377
|
+
--bs-handle-color: rgba(255, 255, 255, 0.45);
|
|
378
|
+
--bs-shadow: 0 -3px 16px rgba(0, 0, 0, 0.5);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* visually-hidden helper for aria-live announcements */
|
|
383
|
+
.bs-sr-only {
|
|
384
|
+
position: absolute;
|
|
385
|
+
width: 1px;
|
|
386
|
+
height: 1px;
|
|
387
|
+
padding: 0;
|
|
388
|
+
margin: -1px;
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
clip: rect(0, 0, 0, 0);
|
|
391
|
+
white-space: nowrap;
|
|
392
|
+
border: 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* =====================================================================
|
|
396
|
+
* OVERLAY — bottom-anchored slide-up panel
|
|
397
|
+
* ===================================================================== */
|
|
398
|
+
|
|
399
|
+
:where(.ovl-root) {
|
|
400
|
+
--ovl-surface: var(--bs-surface, #ffffff);
|
|
401
|
+
--ovl-surface-border: var(--bs-surface-border, rgba(0, 0, 0, 0.08));
|
|
402
|
+
--ovl-shadow: 0 -8px 40px rgba(0, 0, 0, 0.35);
|
|
403
|
+
--ovl-radius: var(--bs-radius, 20px);
|
|
404
|
+
--ovl-backdrop-color: rgba(0, 0, 0, 0.5);
|
|
405
|
+
--ovl-z-backdrop: 200;
|
|
406
|
+
--ovl-z-panel: 201;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.ovl-backdrop {
|
|
410
|
+
position: fixed;
|
|
411
|
+
inset: 0;
|
|
412
|
+
background: var(--ovl-backdrop-color);
|
|
413
|
+
opacity: 0;
|
|
414
|
+
pointer-events: none;
|
|
415
|
+
z-index: var(--ovl-z-backdrop);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.ovl-panel {
|
|
419
|
+
position: fixed;
|
|
420
|
+
background: var(--ovl-surface);
|
|
421
|
+
box-shadow: var(--ovl-shadow);
|
|
422
|
+
z-index: var(--ovl-z-panel);
|
|
423
|
+
-webkit-tap-highlight-color: transparent;
|
|
424
|
+
outline: none;
|
|
425
|
+
/* Modern open transition — works alongside the JS double-rAF fallback
|
|
426
|
+
in core/overlay.ts. In browsers that support @starting-style
|
|
427
|
+
(Chrome 117+, Safari 17.5+, Firefox 129+), the engine just flips
|
|
428
|
+
[data-state] from "entering" to "open" and the browser interpolates
|
|
429
|
+
from the @starting-style block below. The JS still writes inline
|
|
430
|
+
transition/transform for older browsers; setting the same transition
|
|
431
|
+
here is harmless because the inline style wins via specificity. */
|
|
432
|
+
transition: transform 0.32s cubic-bezier(0.29, 1.24, 0.84, 0.99),
|
|
433
|
+
opacity 0.32s ease-out;
|
|
434
|
+
}
|
|
435
|
+
.ovl-panel[data-state="open"] {
|
|
436
|
+
transform: translate3d(0, 0, 0);
|
|
437
|
+
opacity: 1;
|
|
438
|
+
}
|
|
439
|
+
/* @starting-style: define the "from" frame for the entry interpolation
|
|
440
|
+
per edge. Browsers without support fall back gracefully — the
|
|
441
|
+
double-rAF in overlay.ts still drives the same animation. */
|
|
442
|
+
@starting-style {
|
|
443
|
+
.ovl-panel[data-edge="bottom"][data-state="open"] {
|
|
444
|
+
transform: translate3d(0, 100%, 0);
|
|
445
|
+
opacity: 0;
|
|
446
|
+
}
|
|
447
|
+
.ovl-panel[data-edge="top"][data-state="open"] {
|
|
448
|
+
transform: translate3d(0, -100%, 0);
|
|
449
|
+
opacity: 0;
|
|
450
|
+
}
|
|
451
|
+
.ovl-panel[data-edge="left"][data-state="open"] {
|
|
452
|
+
transform: translate3d(-100%, 0, 0);
|
|
453
|
+
opacity: 0;
|
|
454
|
+
}
|
|
455
|
+
.ovl-panel[data-edge="right"][data-state="open"] {
|
|
456
|
+
transform: translate3d(100%, 0, 0);
|
|
457
|
+
opacity: 0;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.ovl-panel[data-edge="bottom"] {
|
|
462
|
+
left: 0;
|
|
463
|
+
right: 0;
|
|
464
|
+
bottom: 0;
|
|
465
|
+
width: 100%;
|
|
466
|
+
border-top-left-radius: var(--ovl-radius);
|
|
467
|
+
border-top-right-radius: var(--ovl-radius);
|
|
468
|
+
border-top: 1px solid var(--ovl-surface-border);
|
|
469
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.ovl-panel[data-edge="top"] {
|
|
473
|
+
left: 0;
|
|
474
|
+
right: 0;
|
|
475
|
+
top: 0;
|
|
476
|
+
width: 100%;
|
|
477
|
+
border-bottom-left-radius: var(--ovl-radius);
|
|
478
|
+
border-bottom-right-radius: var(--ovl-radius);
|
|
479
|
+
border-bottom: 1px solid var(--ovl-surface-border);
|
|
480
|
+
padding-top: env(safe-area-inset-top);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.ovl-panel[data-edge="left"] {
|
|
484
|
+
top: 0;
|
|
485
|
+
bottom: 0;
|
|
486
|
+
left: 0;
|
|
487
|
+
height: 100%;
|
|
488
|
+
border-top-right-radius: var(--ovl-radius);
|
|
489
|
+
border-bottom-right-radius: var(--ovl-radius);
|
|
490
|
+
border-right: 1px solid var(--ovl-surface-border);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.ovl-panel[data-edge="right"] {
|
|
494
|
+
top: 0;
|
|
495
|
+
bottom: 0;
|
|
496
|
+
right: 0;
|
|
497
|
+
height: 100%;
|
|
498
|
+
border-top-left-radius: var(--ovl-radius);
|
|
499
|
+
border-bottom-left-radius: var(--ovl-radius);
|
|
500
|
+
border-left: 1px solid var(--ovl-surface-border);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@media (prefers-reduced-motion: reduce) {
|
|
504
|
+
.ovl-panel,
|
|
505
|
+
.ovl-backdrop {
|
|
506
|
+
transition: none !important;
|
|
507
|
+
}
|
|
508
|
+
}
|