@botiverse/raft-ui 0.59.2 → 0.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/components/composer/composer-attachment.recipe.mjs +2 -3
  3. package/dist/components/composer/composer-attachments-scroll-area.d.mts +6 -0
  4. package/dist/components/composer/composer-attachments-scroll-area.mjs +20 -31
  5. package/dist/components/composer/composer.d.mts +8 -2
  6. package/dist/components/composer/composer.mjs +7 -9
  7. package/dist/components/composer/composer.recipe.mjs +1 -0
  8. package/dist/components/context-menu/context-menu.d.mts +1 -1
  9. package/dist/components/context-menu/context-menu.mjs +1 -1
  10. package/dist/components/dropdown-menu/dropdown-menu.d.mts +1 -1
  11. package/dist/components/dropdown-menu/dropdown-menu.mjs +2 -2
  12. package/dist/components/kbd/index.d.mts +5 -2
  13. package/dist/components/kbd/kbd-keys.d.mts +12 -0
  14. package/dist/components/kbd/kbd-keys.mjs +297 -0
  15. package/dist/components/kbd/kbd-shortcut.d.mts +34 -0
  16. package/dist/components/kbd/kbd-shortcut.mjs +79 -0
  17. package/dist/components/kbd/kbd.d.mts +55 -2
  18. package/dist/components/kbd/kbd.mjs +39 -15
  19. package/dist/components/markdown/markdown-parts.d.mts +7 -2
  20. package/dist/components/markdown/markdown-parts.mjs +8 -3
  21. package/dist/components/markdown/markdown.recipe.mjs +1 -5
  22. package/dist/components/message-list/message-list.mjs +9 -15
  23. package/dist/components/message-list/message-list.recipe.mjs +1 -1
  24. package/dist/components/scroll-area/class-names.mjs +19 -0
  25. package/dist/components/scroll-area/gutters-binding.mjs +4 -0
  26. package/dist/components/scroll-area/index.d.mts +6 -2
  27. package/dist/components/scroll-area/scroll-area-gutters.d.mts +15 -0
  28. package/dist/components/scroll-area/scroll-area-gutters.mjs +60 -0
  29. package/dist/components/scroll-area/scroll-area-textarea.d.mts +14 -0
  30. package/dist/components/scroll-area/scroll-area-textarea.mjs +50 -0
  31. package/dist/components/scroll-area/scroll-area.css +720 -0
  32. package/dist/components/scroll-area/scroll-area.d.mts +24 -15
  33. package/dist/components/scroll-area/scroll-area.mjs +67 -45
  34. package/dist/components/scroll-area/scroll-geometry.mjs +58 -0
  35. package/dist/components/scroll-area/scroll-to-element.mjs +67 -0
  36. package/dist/components/scroll-area/types.d.mts +224 -0
  37. package/dist/components/scroll-area/use-rail-pointer.mjs +93 -0
  38. package/dist/components/scroll-area/use-scroll-area-container.d.mts +26 -0
  39. package/dist/components/scroll-area/use-scroll-area-container.mjs +150 -0
  40. package/dist/components/scroll-area/use-scrollbars.mjs +480 -0
  41. package/dist/components/search/search-trigger.d.mts +1 -1
  42. package/dist/components/sidebar/sidebar.d.mts +1 -1
  43. package/dist/components/sidebar/sidebar.mjs +24 -20
  44. package/dist/components/sidebar/sidebar.recipe.mjs +1 -6
  45. package/dist/components/tooltip/tooltip.mjs +8 -5
  46. package/dist/components/virtual-list/VirtualList.mjs +1 -1
  47. package/dist/index.d.mts +7 -3
  48. package/dist/index.mjs +5 -2
  49. package/dist/lib/inline-part.mjs +13 -0
  50. package/dist/lib/use-merged-ref.mjs +73 -47
  51. package/dist/lib/use-platform.d.mts +13 -0
  52. package/dist/lib/use-platform.mjs +29 -0
  53. package/dist/styles.css +1 -0
  54. package/dist/wip.d.mts +5 -1
  55. package/dist/wip.mjs +3 -1
  56. package/package.json +1 -1
  57. package/dist/components/scroll-area/scroll-area.recipe.mjs +0 -23
@@ -0,0 +1,720 @@
1
+ /*
2
+ * ScrollArea, modelled on 07akioni/react-scroll-area-demo. The container is
3
+ * the element that scrolls (native scrolling, its native scrollbars hidden);
4
+ * the gutters are a layer that draws thin overlay scrollbars over it. Where
5
+ * the gutters go (`<ScrollArea>` is always sticky; `useScrollAreaContainer`
6
+ * takes either):
7
+ *
8
+ * sticky the root is the container; the gutters are its first child, a
9
+ * zero-height layer stuck to the top of the viewport and sized
10
+ * by --r-scroll-area-viewport-{height,width}.
11
+ * absolute the root is a positioned frame holding the container and,
12
+ * after it, the gutters, laid over the frame (`inset: 0`). For a
13
+ * container that can't hold children: a textarea.
14
+ *
15
+ * Plain CSS: every selector is written out, and every rule that reaches
16
+ * past the root names the position (`--sticky-gutters`,
17
+ * `--absolute-gutters`), so an area nested in another's container never
18
+ * matches the outer one's rules.
19
+ *
20
+ * Classes: `.r-scroll-area` (the root) with the modifiers
21
+ * `--small` / `--medium`, `--{hover,scroll,none}-trigger`,
22
+ * `--{vertical,horizontal,both}-orientation`, `--{sticky,absolute}-gutters`,
23
+ * `--{none,contain}-overscroll-{y,x}`, `--fade`, `--rtl`,
24
+ * `--overflow-{y,x}-{start,end}` (more content past that edge; `x` is
25
+ * inline; these and `--rtl` are set by the script with `classList`, never
26
+ * rendered) and `--textarea`; `__container` (with absolute gutters, the
27
+ * container), `__viewport` (ScrollArea's own `<div>` container: its
28
+ * overflow), `__content`, `__textarea`, `__gutters` (with `--scrolling`,
29
+ * from the first scroll until `hideDelay` after the last, and `--dragging`,
30
+ * both set by the script: on the layer, so toggling them restyles only the
31
+ * gutters, not the content under the root), `__gutter` (`--vertical`,
32
+ * `--horizontal`, `--shortened`, `--dragging`) and `__thumb`.
33
+ *
34
+ * In the `components` layer, so a utility in the caller's `className`
35
+ * (`h-*`, `max-h-*`, `absolute`) overrides these rules.
36
+ *
37
+ * Knobs, set on the root (or by a utility on it):
38
+ * --r-scroll-area-thumb-size-{small,medium} thumb thickness at rest
39
+ * --r-scroll-area-thumb-size-hover-{small,medium} while pointed at or dragged
40
+ * --r-scroll-area-thumb-min-length
41
+ * --r-scroll-area-thumb-color / -color-hover
42
+ * --r-scroll-area-rail-gap thumb to the root's edge
43
+ * --r-scroll-area-rail-inset-{top,bottom,left,right} room kept clear at the
44
+ * ends (`railInset`); they
45
+ * don't inherit
46
+ * --r-scroll-area-fade-size how far `fade` fades
47
+ *
48
+ * Written by React on the root:
49
+ * --r-scroll-area-timeline-{y,x} the names of the container's scroll
50
+ * timelines, unique to the ScrollArea
51
+ * What the script writes as inline styles (React never renders them):
52
+ * --r-scroll-area-viewport-{height,width} on the root: the container's
53
+ * client size (layout path only)
54
+ * --r-scroll-area-padding-{top,left,right} on a sticky root: its padding,
55
+ * which the gutters layer
56
+ * reaches over (layout path only)
57
+ * --r-scroll-area-thumb-length on each thumb: its length, px
58
+ * --r-scroll-area-thumb-travel on each thumb, with scroll-driven
59
+ * animations: gutter minus length
60
+ * (negative inline in RTL)
61
+ * transform on each thumb, without them:
62
+ * where it is along its gutter
63
+ * --r-scroll-area-overflow-{y,x}-{start,end} with `fade` only, where the
64
+ * mask is: px left past each edge
65
+ * Where scroll-driven animations work, the thumb is moved by one, on the
66
+ * container's named scroll timeline: the gutters are inside the container
67
+ * (sticky) and see it, or the root hoists it (`timeline-scope`, absolute).
68
+ * The script then writes only when a size changes. Elsewhere a scrolled
69
+ * frame changes the thumbs' transforms, and the length with them when the
70
+ * content's size changed. Never the root while scrolling, whose variables
71
+ * everything inside would inherit.
72
+ *
73
+ * The thumb is the label colour (`foreground`) made translucent, dark on
74
+ * light pages and light on dark ones, so it sits on any content; there is no
75
+ * visible track: pointing at the scrollbar only thickens the thumb. One look
76
+ * in every theme.
77
+ */
78
+
79
+ /*
80
+ * The px left past each edge change on every scrolled frame. Only `fade`
81
+ * reads them, on the element its mask is on (the content, or the container
82
+ * with absolute gutters), and they don't inherit: a change restyles that
83
+ * element, not everything inside it.
84
+ */
85
+ @property --r-scroll-area-overflow-y-start {
86
+ syntax: "<length>";
87
+ inherits: false;
88
+ initial-value: 0px;
89
+ }
90
+ @property --r-scroll-area-overflow-y-end {
91
+ syntax: "<length>";
92
+ inherits: false;
93
+ initial-value: 0px;
94
+ }
95
+ @property --r-scroll-area-overflow-x-start {
96
+ syntax: "<length>";
97
+ inherits: false;
98
+ initial-value: 0px;
99
+ }
100
+ @property --r-scroll-area-overflow-x-end {
101
+ syntax: "<length>";
102
+ inherits: false;
103
+ initial-value: 0px;
104
+ }
105
+
106
+ /*
107
+ * Room kept clear at each end of the scrollbars, such as for a sticky header
108
+ * or footer inside the area (`railInset`). Set on the root itself; they don't
109
+ * inherit, so a scroll area nested inside doesn't take them.
110
+ */
111
+ @property --r-scroll-area-rail-inset-top {
112
+ syntax: "<length>";
113
+ inherits: false;
114
+ initial-value: 0px;
115
+ }
116
+ @property --r-scroll-area-rail-inset-bottom {
117
+ syntax: "<length>";
118
+ inherits: false;
119
+ initial-value: 0px;
120
+ }
121
+ @property --r-scroll-area-rail-inset-left {
122
+ syntax: "<length>";
123
+ inherits: false;
124
+ initial-value: 0px;
125
+ }
126
+ @property --r-scroll-area-rail-inset-right {
127
+ syntax: "<length>";
128
+ inherits: false;
129
+ initial-value: 0px;
130
+ }
131
+
132
+ /* Along the gutter: from its start to `travel` px down (or along) it, driven by the scroll. */
133
+ @keyframes r-scroll-area-thumb-y {
134
+ from {
135
+ transform: translate3d(0, 0, 0);
136
+ }
137
+ to {
138
+ transform: translate3d(0, var(--r-scroll-area-thumb-travel), 0);
139
+ }
140
+ }
141
+ @keyframes r-scroll-area-thumb-x {
142
+ from {
143
+ transform: translate3d(0, 0, 0);
144
+ }
145
+ to {
146
+ transform: translate3d(var(--r-scroll-area-thumb-travel), 0, 0);
147
+ }
148
+ }
149
+
150
+ @layer components {
151
+ .r-scroll-area {
152
+ --r-scroll-area-thumb-size-small: 5px;
153
+ --r-scroll-area-thumb-size-hover-small: 8px;
154
+ --r-scroll-area-thumb-size-medium: 7px;
155
+ --r-scroll-area-thumb-size-hover-medium: 10px;
156
+ --r-scroll-area-thumb-min-length: 24px;
157
+ --r-scroll-area-thumb-color: color-mix(in oklab, var(--foreground) 42%, transparent);
158
+ --r-scroll-area-thumb-color-hover: color-mix(in oklab, var(--foreground) 58%, transparent);
159
+ --r-scroll-area-rail-gap: 3px;
160
+ /* Room left for a textarea's resize grip in the bottom corner. */
161
+ --r-scroll-area-resize-grip-size: 16px;
162
+ --r-scroll-area-fade-size: 24px;
163
+ --r-scroll-area-duration-fade-in: 120ms;
164
+ --r-scroll-area-duration-fade-out: 400ms;
165
+ --r-scroll-area-duration: 150ms;
166
+ --r-scroll-area-easing: ease-out;
167
+ /* Unique per ScrollArea from its inline style; these only stand in for a root rendered without it. */
168
+ --r-scroll-area-timeline-y: --r-scroll-area-y;
169
+ --r-scroll-area-timeline-x: --r-scroll-area-x;
170
+
171
+ --r-scroll-area-thumb-size: var(--r-scroll-area-thumb-size-medium);
172
+ --r-scroll-area-thumb-size-hover: var(--r-scroll-area-thumb-size-hover-medium);
173
+ /*
174
+ * Until the first measurement, and again for a nested scroll area, which
175
+ * would otherwise inherit its parent's.
176
+ */
177
+ --r-scroll-area-viewport-height: 100%;
178
+ --r-scroll-area-viewport-width: 100%;
179
+ --r-scroll-area-padding-top: 0px;
180
+ --r-scroll-area-padding-left: 0px;
181
+ --r-scroll-area-padding-right: 0px;
182
+ /* The gutter is as thick as the hovered thumb plus the gap on both sides. */
183
+ --r-scroll-area-rail-size: calc(
184
+ var(--r-scroll-area-thumb-size-hover) + 2 * var(--r-scroll-area-rail-gap)
185
+ );
186
+ /*
187
+ * Where each gutter starts and ends: the gap plus the room kept clear at
188
+ * that end. Read on the root, where the room is set (it doesn't inherit).
189
+ */
190
+ --r-scroll-area-rail-top: calc(
191
+ var(--r-scroll-area-rail-gap) + var(--r-scroll-area-rail-inset-top)
192
+ );
193
+ --r-scroll-area-rail-bottom: calc(
194
+ var(--r-scroll-area-rail-gap) + var(--r-scroll-area-rail-inset-bottom)
195
+ );
196
+ --r-scroll-area-rail-left: calc(
197
+ var(--r-scroll-area-rail-gap) + var(--r-scroll-area-rail-inset-left)
198
+ );
199
+ --r-scroll-area-rail-right: calc(
200
+ var(--r-scroll-area-rail-gap) + var(--r-scroll-area-rail-inset-right)
201
+ );
202
+ /* With both gutters, the end at the corner: the other gutter plus the room kept clear there. */
203
+ --r-scroll-area-rail-bottom-shortened: calc(
204
+ var(--r-scroll-area-rail-size) + var(--r-scroll-area-rail-inset-bottom)
205
+ );
206
+ --r-scroll-area-rail-left-shortened: calc(
207
+ var(--r-scroll-area-rail-size) + var(--r-scroll-area-rail-inset-left)
208
+ );
209
+ --r-scroll-area-rail-right-shortened: calc(
210
+ var(--r-scroll-area-rail-size) + var(--r-scroll-area-rail-inset-right)
211
+ );
212
+ }
213
+ .r-scroll-area.r-scroll-area--small {
214
+ --r-scroll-area-thumb-size: var(--r-scroll-area-thumb-size-small);
215
+ --r-scroll-area-thumb-size-hover: var(--r-scroll-area-thumb-size-hover-small);
216
+ }
217
+
218
+ /*
219
+ * The container. Its own overflow, layout and focus are its own (a list,
220
+ * a textarea); the overlay scrollbars replace its native ones. With sticky
221
+ * gutters it is the root.
222
+ */
223
+ .r-scroll-area.r-scroll-area--sticky-gutters,
224
+ .r-scroll-area.r-scroll-area--absolute-gutters > .r-scroll-area__container {
225
+ scrollbar-width: none;
226
+ }
227
+ .r-scroll-area.r-scroll-area--sticky-gutters::-webkit-scrollbar,
228
+ .r-scroll-area.r-scroll-area--absolute-gutters > .r-scroll-area__container::-webkit-scrollbar {
229
+ display: none;
230
+ }
231
+ /*
232
+ * Absolute gutters: the root is the frame the gutters are positioned in,
233
+ * and a column the container fills, so a height or a `max-height` on the
234
+ * root bounds the container, which then scrolls. The frame is what callers
235
+ * border and size, so its size includes its border whatever the page's
236
+ * preflight says (Tailwind's already does this).
237
+ */
238
+ .r-scroll-area.r-scroll-area--absolute-gutters {
239
+ box-sizing: border-box;
240
+ display: flex;
241
+ flex-direction: column;
242
+ position: relative;
243
+ }
244
+
245
+ /* ScrollArea's own `<div>` container (always sticky) scrolls in the orientation. */
246
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area__viewport {
247
+ overflow: hidden auto;
248
+ position: relative;
249
+ }
250
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--horizontal-orientation.r-scroll-area__viewport {
251
+ overflow: auto hidden;
252
+ }
253
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--both-orientation.r-scroll-area__viewport {
254
+ overflow: auto;
255
+ }
256
+
257
+ /*
258
+ * A scroll that reaches the end stops there instead of scrolling the page:
259
+ * `none` also keeps Mac trackpads and iOS from bouncing the area (and its
260
+ * sticky parts) at the edge; `contain` lets them bounce it. Only on the axes
261
+ * that really scroll right now (the script adds each modifier while that
262
+ * axis has a gutter), so a strip that only overflows sideways never holds
263
+ * back the page's vertical scroll. Each sets one axis only and never `auto`,
264
+ * so it doesn't override a caller's own `overscroll-behavior` on the other
265
+ * axis. The modifiers are on the root; the container takes them.
266
+ */
267
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--contain-overscroll-y,
268
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--contain-overscroll-y
269
+ > .r-scroll-area__container {
270
+ overscroll-behavior-y: contain;
271
+ }
272
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--contain-overscroll-x,
273
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--contain-overscroll-x
274
+ > .r-scroll-area__container {
275
+ overscroll-behavior-x: contain;
276
+ }
277
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--none-overscroll-y,
278
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--none-overscroll-y
279
+ > .r-scroll-area__container {
280
+ overscroll-behavior-y: none;
281
+ }
282
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--none-overscroll-x,
283
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--none-overscroll-x
284
+ > .r-scroll-area__container {
285
+ overscroll-behavior-x: none;
286
+ }
287
+
288
+ /*
289
+ * Keyboard focus only (--focus-ring is empty after a click or a tap,
290
+ * styles.css), on ScrollArea's own container. A frame with absolute
291
+ * gutters is the caller's, and so is its focus ring (`has-focus-visible:`).
292
+ */
293
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area__viewport:focus-visible {
294
+ box-shadow: var(--focus-ring);
295
+ outline: none;
296
+ }
297
+
298
+ /* Wide content widens the content box instead of wrapping to it. */
299
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--horizontal-orientation
300
+ > .r-scroll-area__content,
301
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--both-orientation
302
+ > .r-scroll-area__content,
303
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--horizontal-orientation
304
+ > .r-scroll-area__container
305
+ > .r-scroll-area__content,
306
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--both-orientation
307
+ > .r-scroll-area__container
308
+ > .r-scroll-area__content {
309
+ inline-size: fit-content;
310
+ min-inline-size: 100%;
311
+ }
312
+
313
+ /*
314
+ * `fade`: the content fades out toward each edge with more past it, over
315
+ * the px left (at most --r-scroll-area-fade-size), so it fades in as it
316
+ * leaves an edge. A mask reads only alpha: `black` is opaque here, not a
317
+ * colour. The gutters never fade:
318
+ * sticky the mask is on the content, not the container that holds the
319
+ * gutters. The content scrolls, so the mask is one viewport
320
+ * large and moved to where the viewport is.
321
+ * absolute the mask is on the container, which doesn't move it when it
322
+ * scrolls; the gutters are its siblings.
323
+ *
324
+ * The mask repaints on every scrolled frame, so `fade` is meant for short
325
+ * areas, not long lists.
326
+ */
327
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--sticky-gutters > .r-scroll-area__content,
328
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--absolute-gutters > .r-scroll-area__container {
329
+ --r-scroll-area-overflow-left: var(--r-scroll-area-overflow-x-start);
330
+ --r-scroll-area-overflow-right: var(--r-scroll-area-overflow-x-end);
331
+ --r-scroll-area-fade-top: min(
332
+ var(--r-scroll-area-overflow-y-start),
333
+ var(--r-scroll-area-fade-size)
334
+ );
335
+ --r-scroll-area-fade-bottom: min(
336
+ var(--r-scroll-area-overflow-y-end),
337
+ var(--r-scroll-area-fade-size)
338
+ );
339
+ --r-scroll-area-fade-left: min(
340
+ var(--r-scroll-area-overflow-left),
341
+ var(--r-scroll-area-fade-size)
342
+ );
343
+ --r-scroll-area-fade-right: min(
344
+ var(--r-scroll-area-overflow-right),
345
+ var(--r-scroll-area-fade-size)
346
+ );
347
+ --r-scroll-area-fade-y: linear-gradient(
348
+ to bottom,
349
+ transparent,
350
+ black var(--r-scroll-area-fade-top),
351
+ black calc(100% - var(--r-scroll-area-fade-bottom)),
352
+ transparent
353
+ );
354
+ --r-scroll-area-fade-x: linear-gradient(
355
+ to right,
356
+ transparent,
357
+ black var(--r-scroll-area-fade-left),
358
+ black calc(100% - var(--r-scroll-area-fade-right)),
359
+ transparent
360
+ );
361
+ }
362
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--sticky-gutters > .r-scroll-area__content {
363
+ mask-position: var(--r-scroll-area-overflow-left) var(--r-scroll-area-overflow-y-start);
364
+ mask-repeat: no-repeat;
365
+ mask-size: var(--r-scroll-area-viewport-width) var(--r-scroll-area-viewport-height);
366
+ }
367
+ /* In right-to-left layouts the inline start is the right edge. */
368
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--rtl.r-scroll-area--sticky-gutters
369
+ > .r-scroll-area__content,
370
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--rtl.r-scroll-area--absolute-gutters
371
+ > .r-scroll-area__container {
372
+ --r-scroll-area-overflow-left: var(--r-scroll-area-overflow-x-end);
373
+ --r-scroll-area-overflow-right: var(--r-scroll-area-overflow-x-start);
374
+ }
375
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--vertical-orientation.r-scroll-area--sticky-gutters
376
+ > .r-scroll-area__content,
377
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--vertical-orientation.r-scroll-area--absolute-gutters
378
+ > .r-scroll-area__container {
379
+ mask-image: var(--r-scroll-area-fade-y);
380
+ }
381
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--horizontal-orientation.r-scroll-area--sticky-gutters
382
+ > .r-scroll-area__content,
383
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--horizontal-orientation.r-scroll-area--absolute-gutters
384
+ > .r-scroll-area__container {
385
+ mask-image: var(--r-scroll-area-fade-x);
386
+ }
387
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--both-orientation.r-scroll-area--sticky-gutters
388
+ > .r-scroll-area__content,
389
+ .r-scroll-area.r-scroll-area--fade.r-scroll-area--both-orientation.r-scroll-area--absolute-gutters
390
+ > .r-scroll-area__container {
391
+ mask-composite: intersect;
392
+ mask-image: var(--r-scroll-area-fade-y), var(--r-scroll-area-fade-x);
393
+ }
394
+
395
+ /* ScrollAreaTextarea: absolute gutters, the textarea is the container and fills the frame. */
396
+ .r-scroll-area.r-scroll-area--textarea {
397
+ display: grid;
398
+ }
399
+ .r-scroll-area.r-scroll-area--textarea > .r-scroll-area__textarea {
400
+ box-sizing: border-box;
401
+ min-block-size: 0;
402
+ /* Drag the corner to make it taller; widening would outgrow the frame. */
403
+ resize: vertical;
404
+ }
405
+
406
+ /* The gutters layer: out of the way of the content, but for each gutter itself. */
407
+ .r-scroll-area > .r-scroll-area__gutters {
408
+ pointer-events: none;
409
+ user-select: none;
410
+ z-index: 1;
411
+ }
412
+ /*
413
+ * Sticky: a zero-height layer at the top of the viewport, the container's
414
+ * first child. In a container that lays its children out as a block or a
415
+ * flex column it stays a zero-height row of the full width, and nothing
416
+ * after it moves. It is in flow (a sticky box always is), so a grid, a flex
417
+ * row, `gap` and sibling selectors (`stack-y-*`, `rule-y`, `first:`) count
418
+ * it: lay the content out in a wrapper inside the container instead.
419
+ *
420
+ * It reaches over the container's padding, so the gutters are at the
421
+ * padding box's edges, not the content's: the negative inline margins
422
+ * widen it to the padding box. A sticky box sticks inside the scrollport
423
+ * less its padding, so `top: 0` holds it `padding-top` below the top of the
424
+ * viewport, where a translate lifts it back to the top. The translate moves
425
+ * nothing in flow, so the content stays where its padding puts it.
426
+ */
427
+ .r-scroll-area.r-scroll-area--sticky-gutters > .r-scroll-area__gutters {
428
+ align-self: stretch;
429
+ block-size: 0;
430
+ flex-shrink: 0;
431
+ inset-block-start: 0;
432
+ inset-inline-start: 0;
433
+ margin-left: calc(-1 * var(--r-scroll-area-padding-left));
434
+ margin-right: calc(-1 * var(--r-scroll-area-padding-right));
435
+ position: sticky;
436
+ translate: 0 calc(-1 * var(--r-scroll-area-padding-top));
437
+ }
438
+ /*
439
+ * Scrolling sideways, it also sticks the inline-start padding in from that
440
+ * edge, and the translate lifts it back across (the other way right to left).
441
+ */
442
+ .r-scroll-area.r-scroll-area--sticky-gutters:is(
443
+ .r-scroll-area--horizontal-orientation,
444
+ .r-scroll-area--both-orientation
445
+ )
446
+ > .r-scroll-area__gutters {
447
+ translate: calc(-1 * var(--r-scroll-area-padding-left))
448
+ calc(-1 * var(--r-scroll-area-padding-top));
449
+ }
450
+ .r-scroll-area.r-scroll-area--sticky-gutters.r-scroll-area--rtl:is(
451
+ .r-scroll-area--horizontal-orientation,
452
+ .r-scroll-area--both-orientation
453
+ )
454
+ > .r-scroll-area__gutters {
455
+ translate: var(--r-scroll-area-padding-right) calc(-1 * var(--r-scroll-area-padding-top));
456
+ }
457
+ /* Absolute: a layer over the frame's padding box, after the container. */
458
+ .r-scroll-area.r-scroll-area--absolute-gutters > .r-scroll-area__gutters {
459
+ border-radius: inherit;
460
+ inset: 0;
461
+ position: absolute;
462
+ }
463
+
464
+ /*
465
+ * A gutter is the thumb's travel, and the invisible target for hover and
466
+ * paging: it stops the gap short of both ends, plus any room kept clear
467
+ * there. Top and bottom room shortens the vertical gutter, left and right
468
+ * the horizontal one. Sticky gutters hang from the zero-height layer, so
469
+ * their length comes from the viewport's size; absolute ones from the
470
+ * frame's.
471
+ */
472
+ .r-scroll-area > .r-scroll-area__gutters > .r-scroll-area__gutter {
473
+ opacity: 0;
474
+ pointer-events: auto;
475
+ position: absolute;
476
+ transition: opacity var(--r-scroll-area-duration-fade-out) var(--r-scroll-area-easing);
477
+ }
478
+ .r-scroll-area > .r-scroll-area__gutters > .r-scroll-area__gutter--vertical {
479
+ inline-size: var(--r-scroll-area-rail-size);
480
+ inset-block-start: var(--r-scroll-area-rail-top);
481
+ inset-inline-end: 0;
482
+ }
483
+ .r-scroll-area.r-scroll-area--sticky-gutters
484
+ > .r-scroll-area__gutters
485
+ > .r-scroll-area__gutter--vertical {
486
+ block-size: calc(
487
+ var(--r-scroll-area-viewport-height) - var(--r-scroll-area-rail-top) -
488
+ var(--r-scroll-area-rail-bottom)
489
+ );
490
+ }
491
+ .r-scroll-area.r-scroll-area--absolute-gutters
492
+ > .r-scroll-area__gutters
493
+ > .r-scroll-area__gutter--vertical {
494
+ inset-block-end: var(--r-scroll-area-rail-bottom);
495
+ }
496
+ .r-scroll-area > .r-scroll-area__gutters > .r-scroll-area__gutter--horizontal {
497
+ block-size: var(--r-scroll-area-rail-size);
498
+ left: var(--r-scroll-area-rail-left);
499
+ right: var(--r-scroll-area-rail-right);
500
+ }
501
+ .r-scroll-area.r-scroll-area--sticky-gutters
502
+ > .r-scroll-area__gutters
503
+ > .r-scroll-area__gutter--horizontal {
504
+ inset-block-start: calc(var(--r-scroll-area-viewport-height) - var(--r-scroll-area-rail-size));
505
+ }
506
+ .r-scroll-area.r-scroll-area--absolute-gutters
507
+ > .r-scroll-area__gutters
508
+ > .r-scroll-area__gutter--horizontal {
509
+ inset-block-end: 0;
510
+ }
511
+
512
+ /*
513
+ * With both gutters, each stops short of the other, like native scrollbars;
514
+ * room kept clear past the corner adds to it. The corner is at the inline
515
+ * end: the right, or the left in right-to-left layouts.
516
+ */
517
+ .r-scroll-area.r-scroll-area--sticky-gutters
518
+ > .r-scroll-area__gutters
519
+ > .r-scroll-area__gutter--vertical.r-scroll-area__gutter--shortened {
520
+ block-size: calc(
521
+ var(--r-scroll-area-viewport-height) - var(--r-scroll-area-rail-top) -
522
+ var(--r-scroll-area-rail-bottom-shortened)
523
+ );
524
+ }
525
+ .r-scroll-area.r-scroll-area--absolute-gutters
526
+ > .r-scroll-area__gutters
527
+ > .r-scroll-area__gutter--vertical.r-scroll-area__gutter--shortened {
528
+ inset-block-end: var(--r-scroll-area-rail-bottom-shortened);
529
+ }
530
+ .r-scroll-area
531
+ > .r-scroll-area__gutters
532
+ > .r-scroll-area__gutter--horizontal.r-scroll-area__gutter--shortened {
533
+ right: var(--r-scroll-area-rail-right-shortened);
534
+ }
535
+ .r-scroll-area.r-scroll-area--rtl
536
+ > .r-scroll-area__gutters
537
+ > .r-scroll-area__gutter--horizontal.r-scroll-area__gutter--shortened {
538
+ left: var(--r-scroll-area-rail-left-shortened);
539
+ right: var(--r-scroll-area-rail-right);
540
+ }
541
+ /* The vertical gutter stops above the textarea's resize grip so the grip can be dragged. */
542
+ .r-scroll-area.r-scroll-area--absolute-gutters.r-scroll-area--textarea
543
+ > .r-scroll-area__gutters
544
+ > .r-scroll-area__gutter--vertical {
545
+ inset-block-end: var(--r-scroll-area-resize-grip-size);
546
+ }
547
+
548
+ /*
549
+ * The thumb sits at the start of its gutter and is moved along it by a
550
+ * transform (`translate3d`, its own compositor layer), so a scrolled frame
551
+ * moves it without a layout: a scroll-driven animation where the browser
552
+ * has them (below), the script's transform elsewhere. Its length, in px, is
553
+ * written by the script (at least --r-scroll-area-thumb-min-length). Only
554
+ * the thickness and the colour transition, on hover; the length and the
555
+ * place never do, so like a native scrollbar it always shows the current
556
+ * size.
557
+ */
558
+ .r-scroll-area > .r-scroll-area__gutters > .r-scroll-area__gutter > .r-scroll-area__thumb {
559
+ background: var(--r-scroll-area-thumb-color);
560
+ border-radius: calc(var(--r-scroll-area-thumb-size-hover) / 2);
561
+ position: absolute;
562
+ touch-action: none;
563
+ will-change: transform;
564
+ }
565
+ .r-scroll-area
566
+ > .r-scroll-area__gutters
567
+ > .r-scroll-area__gutter--vertical
568
+ > .r-scroll-area__thumb {
569
+ block-size: var(--r-scroll-area-thumb-length, 0px);
570
+ inline-size: var(--r-scroll-area-thumb-size);
571
+ inset-block-start: 0;
572
+ inset-inline-end: var(--r-scroll-area-rail-gap);
573
+ transition:
574
+ background-color var(--r-scroll-area-duration) var(--r-scroll-area-easing),
575
+ inline-size var(--r-scroll-area-duration) var(--r-scroll-area-easing);
576
+ }
577
+ .r-scroll-area
578
+ > .r-scroll-area__gutters
579
+ > .r-scroll-area__gutter--horizontal
580
+ > .r-scroll-area__thumb {
581
+ block-size: var(--r-scroll-area-thumb-size);
582
+ inline-size: var(--r-scroll-area-thumb-length, 0px);
583
+ inset-block-end: var(--r-scroll-area-rail-gap);
584
+ inset-inline-start: 0;
585
+ transition:
586
+ background-color var(--r-scroll-area-duration) var(--r-scroll-area-easing),
587
+ block-size var(--r-scroll-area-duration) var(--r-scroll-area-easing);
588
+ }
589
+
590
+ /* Shown while hovered (trigger="hover"), always (trigger="none"), and while scrolling or dragging (every trigger). */
591
+ .r-scroll-area.r-scroll-area--hover-trigger:hover
592
+ > .r-scroll-area__gutters
593
+ > .r-scroll-area__gutter,
594
+ .r-scroll-area.r-scroll-area--none-trigger > .r-scroll-area__gutters > .r-scroll-area__gutter,
595
+ .r-scroll-area
596
+ > .r-scroll-area__gutters.r-scroll-area__gutters--scrolling
597
+ > .r-scroll-area__gutter,
598
+ .r-scroll-area
599
+ > .r-scroll-area__gutters.r-scroll-area__gutters--dragging
600
+ > .r-scroll-area__gutter {
601
+ opacity: 1;
602
+ transition-duration: var(--r-scroll-area-duration-fade-in);
603
+ }
604
+
605
+ /*
606
+ * trigger="scroll": a hidden gutter doesn't catch presses meant for the
607
+ * content under it. A gutter the pointer is already on (it was shown by
608
+ * scrolling) stays visible and usable after `hideDelay`, until the pointer
609
+ * leaves it; a hidden gutter can't become hovered, so it never shows on hover.
610
+ */
611
+ .r-scroll-area.r-scroll-area--scroll-trigger
612
+ > .r-scroll-area__gutters
613
+ > .r-scroll-area__gutter:hover {
614
+ opacity: 1;
615
+ transition-duration: var(--r-scroll-area-duration-fade-in);
616
+ }
617
+ .r-scroll-area.r-scroll-area--scroll-trigger
618
+ > .r-scroll-area__gutters:not(.r-scroll-area__gutters--scrolling):not(
619
+ .r-scroll-area__gutters--dragging
620
+ )
621
+ > .r-scroll-area__gutter:not(:hover) {
622
+ pointer-events: none;
623
+ }
624
+
625
+ /* Pointed at, or while dragged: the thumb thickens and darkens. */
626
+ .r-scroll-area
627
+ > .r-scroll-area__gutters
628
+ > .r-scroll-area__gutter--vertical:hover
629
+ > .r-scroll-area__thumb,
630
+ .r-scroll-area
631
+ > .r-scroll-area__gutters
632
+ > .r-scroll-area__gutter--vertical.r-scroll-area__gutter--dragging
633
+ > .r-scroll-area__thumb {
634
+ background: var(--r-scroll-area-thumb-color-hover);
635
+ inline-size: var(--r-scroll-area-thumb-size-hover);
636
+ }
637
+ .r-scroll-area
638
+ > .r-scroll-area__gutters
639
+ > .r-scroll-area__gutter--horizontal:hover
640
+ > .r-scroll-area__thumb,
641
+ .r-scroll-area
642
+ > .r-scroll-area__gutters
643
+ > .r-scroll-area__gutter--horizontal.r-scroll-area__gutter--dragging
644
+ > .r-scroll-area__thumb {
645
+ background: var(--r-scroll-area-thumb-color-hover);
646
+ block-size: var(--r-scroll-area-thumb-size-hover);
647
+ }
648
+
649
+ /*
650
+ * Scroll-driven thumbs: the browser moves each thumb from the scroll itself,
651
+ * on the compositor, with no script per scroll. The container names its
652
+ * timelines with the ScrollArea's unique names. Sticky gutters are inside
653
+ * the container and see them, so they need only `animation-timeline`;
654
+ * absolute gutters are its siblings, so the frame hoists the names to
655
+ * itself (`timeline-scope`), and only gutters after a container are
656
+ * animated (`~`). `use-scrollbars.ts` checks the same conditions
657
+ * (`stickyScrollTimelineCondition`, `scrollTimelineCondition`) and the
658
+ * same arrangement; for any other the script moves the thumbs.
659
+ */
660
+ @supports (animation-timeline: scroll()) {
661
+ .r-scroll-area.r-scroll-area--sticky-gutters {
662
+ scroll-timeline:
663
+ var(--r-scroll-area-timeline-y) block,
664
+ var(--r-scroll-area-timeline-x) inline;
665
+ }
666
+ .r-scroll-area.r-scroll-area--sticky-gutters
667
+ > .r-scroll-area__gutters
668
+ > .r-scroll-area__gutter--vertical
669
+ > .r-scroll-area__thumb {
670
+ animation: r-scroll-area-thumb-y linear both;
671
+ animation-timeline: var(--r-scroll-area-timeline-y);
672
+ }
673
+ .r-scroll-area.r-scroll-area--sticky-gutters
674
+ > .r-scroll-area__gutters
675
+ > .r-scroll-area__gutter--horizontal
676
+ > .r-scroll-area__thumb {
677
+ animation: r-scroll-area-thumb-x linear both;
678
+ animation-timeline: var(--r-scroll-area-timeline-x);
679
+ }
680
+ }
681
+ @supports (animation-timeline: scroll()) and (timeline-scope: none) {
682
+ .r-scroll-area.r-scroll-area--absolute-gutters > .r-scroll-area__container {
683
+ scroll-timeline:
684
+ var(--r-scroll-area-timeline-y) block,
685
+ var(--r-scroll-area-timeline-x) inline;
686
+ }
687
+ .r-scroll-area.r-scroll-area--absolute-gutters {
688
+ timeline-scope: var(--r-scroll-area-timeline-y), var(--r-scroll-area-timeline-x);
689
+ }
690
+ .r-scroll-area.r-scroll-area--absolute-gutters
691
+ > .r-scroll-area__container
692
+ ~ .r-scroll-area__gutters
693
+ > .r-scroll-area__gutter--vertical
694
+ > .r-scroll-area__thumb {
695
+ animation: r-scroll-area-thumb-y linear both;
696
+ animation-timeline: var(--r-scroll-area-timeline-y);
697
+ }
698
+ .r-scroll-area.r-scroll-area--absolute-gutters
699
+ > .r-scroll-area__container
700
+ ~ .r-scroll-area__gutters
701
+ > .r-scroll-area__gutter--horizontal
702
+ > .r-scroll-area__thumb {
703
+ animation: r-scroll-area-thumb-x linear both;
704
+ animation-timeline: var(--r-scroll-area-timeline-x);
705
+ }
706
+ }
707
+
708
+ @media print {
709
+ .r-scroll-area > .r-scroll-area__gutters {
710
+ display: none;
711
+ }
712
+ }
713
+
714
+ @media (prefers-reduced-motion: reduce) {
715
+ .r-scroll-area > .r-scroll-area__gutters > .r-scroll-area__gutter,
716
+ .r-scroll-area > .r-scroll-area__gutters > .r-scroll-area__gutter > .r-scroll-area__thumb {
717
+ transition: none;
718
+ }
719
+ }
720
+ }