@rcarls/rc-scroller 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/dist/custom-elements.json +274 -0
- package/dist/rc-scroller-BvfVYwVD.js +125 -0
- package/dist/rc-scroller-BvfVYwVD.js.map +1 -0
- package/dist/rc-scroller-define.js +6 -0
- package/dist/rc-scroller-define.js.map +1 -0
- package/dist/rc-scroller.js +5 -0
- package/dist/rc-scroller.js.map +1 -0
- package/dist/types/packages/rc-scroller/src/define.d.ts +1 -0
- package/dist/types/packages/rc-scroller/src/index.d.ts +1 -0
- package/dist/types/packages/rc-scroller/src/rc-scroller.d.ts +67 -0
- package/dist/types/packages/rc-scroller/src/rc-scroller.styles.d.ts +2 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @rcarls/rc-scroller
|
|
2
|
+
|
|
3
|
+
Design-system-neutral native scroll region with an optional centered content
|
|
4
|
+
layout and fullbleed track.
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<rc-scroller layout="content" role="region" aria-label="Recipes">
|
|
8
|
+
<header data-rc-scroller-span="fullbleed">Recipes</header>
|
|
9
|
+
<article>Apple pie</article>
|
|
10
|
+
</rc-scroller>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The custom element host is the native scrollport, so `scrollTop`, `scrollTo()`,
|
|
14
|
+
the `scroll` event, scroll restoration, and scroll observers work directly on
|
|
15
|
+
`<rc-scroller>`. Give the host a definite block size when it should scroll.
|
|
16
|
+
|
|
17
|
+
`axis` accepts `block` (the default), `inline`, or `both`. `layout` accepts
|
|
18
|
+
`none` (the default) or `content`. In content layout, direct children use the
|
|
19
|
+
centered content track; a direct child with
|
|
20
|
+
`data-rc-scroller-span="fullbleed"` spans the available inline size.
|
|
21
|
+
|
|
22
|
+
The component intentionally adds no landmark role, accessible name, or
|
|
23
|
+
`tabindex`. Add native semantics to the host when the surrounding document
|
|
24
|
+
requires them.
|
|
25
|
+
|
|
26
|
+
## Scroll-boundary state
|
|
27
|
+
|
|
28
|
+
`at-block-start`, `at-block-end`, `at-inline-start`, and `at-inline-end` are
|
|
29
|
+
reflected, read-only attributes: present when the host is scrolled to (within
|
|
30
|
+
a few pixels of) that edge on an enabled axis, or when that axis isn't
|
|
31
|
+
scrollable at all (nothing more to reveal counts as already at both of its
|
|
32
|
+
edges). Style an edge affordance (a fade, a shadow, a border) from outside
|
|
33
|
+
the shadow root by targeting the attribute directly:
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
rc-scroller[axis='inline'] {
|
|
37
|
+
mask-image: linear-gradient(
|
|
38
|
+
to right,
|
|
39
|
+
transparent,
|
|
40
|
+
black 24px,
|
|
41
|
+
black calc(100% - 24px),
|
|
42
|
+
transparent
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
rc-scroller[axis='inline'][at-inline-start] {
|
|
47
|
+
mask-image: linear-gradient(to right, black calc(100% - 24px), transparent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
rc-scroller[axis='inline'][at-inline-end] {
|
|
51
|
+
mask-image: linear-gradient(to right, transparent, black 24px);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The component only tracks and reflects this state: it makes no visual
|
|
56
|
+
decision of its own about how (or whether) to show it.
|
|
57
|
+
|
|
58
|
+
Import `@rcarls/rc-scroller` to use the class without registering it, or import
|
|
59
|
+
`@rcarls/rc-scroller/define` to register `<rc-scroller>`.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"readme": "",
|
|
4
|
+
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"kind": "javascript-module",
|
|
7
|
+
"path": "src/define.ts",
|
|
8
|
+
"declarations": [],
|
|
9
|
+
"exports": [
|
|
10
|
+
{
|
|
11
|
+
"kind": "custom-element-definition",
|
|
12
|
+
"name": "rc-scroller",
|
|
13
|
+
"declaration": {
|
|
14
|
+
"name": "RCScroller",
|
|
15
|
+
"module": "/src/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"kind": "js",
|
|
20
|
+
"name": "*",
|
|
21
|
+
"declaration": {
|
|
22
|
+
"name": "*",
|
|
23
|
+
"module": "src/index.js"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"kind": "javascript-module",
|
|
30
|
+
"path": "src/index.ts",
|
|
31
|
+
"declarations": [],
|
|
32
|
+
"exports": [
|
|
33
|
+
{
|
|
34
|
+
"kind": "js",
|
|
35
|
+
"name": "*",
|
|
36
|
+
"declaration": {
|
|
37
|
+
"name": "*",
|
|
38
|
+
"module": "src/rc-scroller.js"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"kind": "javascript-module",
|
|
45
|
+
"path": "src/rc-scroller.ts",
|
|
46
|
+
"declarations": [
|
|
47
|
+
{
|
|
48
|
+
"kind": "class",
|
|
49
|
+
"description": "Design-system-neutral native scroll region with optional content and\nfullbleed layout tracks. The custom element host is the scroll container,\nso native scrolling APIs and events work directly on `<rc-scroller>`.\n\nWith `layout=\"content\"`, direct children use the centered content track.\nAdd `data-rc-scroller-span=\"fullbleed\"` to a direct child to span the full\navailable inline size. This child attribute is a layout convention rather\nthan component state.\n\nThe author owns the accessible name and landmark semantics when the region\nneeds them. The component does not add `role` or `tabindex` automatically.",
|
|
50
|
+
"name": "RCScroller",
|
|
51
|
+
"cssProperties": [
|
|
52
|
+
{
|
|
53
|
+
"description": "Overscroll behavior on enabled axes.",
|
|
54
|
+
"name": "--rc-scroller-overscroll-behavior",
|
|
55
|
+
"default": "contain"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"description": "Browser scroll anchoring behavior.",
|
|
59
|
+
"name": "--rc-scroller-overflow-anchor",
|
|
60
|
+
"default": "auto"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"description": "Scrollbar width.",
|
|
64
|
+
"name": "--rc-scroller-scrollbar-width",
|
|
65
|
+
"default": "auto"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"description": "Scrollbar thumb and track colors.",
|
|
69
|
+
"name": "--rc-scroller-scrollbar-color",
|
|
70
|
+
"default": "auto"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"description": "Minimum inline gutter in content layout.",
|
|
74
|
+
"name": "--rc-scroller-content-padding-inline",
|
|
75
|
+
"default": "1rem"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"description": "Maximum width of the content track.",
|
|
79
|
+
"name": "--rc-scroller-content-max-inline-size",
|
|
80
|
+
"default": "100%"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"description": "Gap between rows in content layout.",
|
|
84
|
+
"name": "--rc-scroller-content-row-gap",
|
|
85
|
+
"default": "0"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"cssParts": [
|
|
89
|
+
{
|
|
90
|
+
"description": "Wrapper for the default slot and optional layout grid.",
|
|
91
|
+
"name": "content"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"slots": [
|
|
95
|
+
{
|
|
96
|
+
"description": "Scrollable content.",
|
|
97
|
+
"name": ""
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"members": [
|
|
101
|
+
{
|
|
102
|
+
"kind": "field",
|
|
103
|
+
"name": "enabledWarnings",
|
|
104
|
+
"static": true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"kind": "field",
|
|
108
|
+
"name": "axis",
|
|
109
|
+
"type": {
|
|
110
|
+
"text": "RCScrollerAxis"
|
|
111
|
+
},
|
|
112
|
+
"default": "'block'",
|
|
113
|
+
"description": "Scroll axis.",
|
|
114
|
+
"attribute": "axis",
|
|
115
|
+
"reflects": true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"kind": "field",
|
|
119
|
+
"name": "layout",
|
|
120
|
+
"type": {
|
|
121
|
+
"text": "RCScrollerLayout"
|
|
122
|
+
},
|
|
123
|
+
"default": "'none'",
|
|
124
|
+
"description": "Optional child layout.",
|
|
125
|
+
"attribute": "layout",
|
|
126
|
+
"reflects": true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"kind": "field",
|
|
130
|
+
"name": "atBlockStart",
|
|
131
|
+
"type": {
|
|
132
|
+
"text": "boolean"
|
|
133
|
+
},
|
|
134
|
+
"default": "true",
|
|
135
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
136
|
+
"attribute": "at-block-start",
|
|
137
|
+
"reflects": true
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"kind": "field",
|
|
141
|
+
"name": "atBlockEnd",
|
|
142
|
+
"type": {
|
|
143
|
+
"text": "boolean"
|
|
144
|
+
},
|
|
145
|
+
"default": "true",
|
|
146
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
147
|
+
"attribute": "at-block-end",
|
|
148
|
+
"reflects": true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"kind": "field",
|
|
152
|
+
"name": "atInlineStart",
|
|
153
|
+
"type": {
|
|
154
|
+
"text": "boolean"
|
|
155
|
+
},
|
|
156
|
+
"default": "true",
|
|
157
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
158
|
+
"attribute": "at-inline-start",
|
|
159
|
+
"reflects": true
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"kind": "field",
|
|
163
|
+
"name": "atInlineEnd",
|
|
164
|
+
"type": {
|
|
165
|
+
"text": "boolean"
|
|
166
|
+
},
|
|
167
|
+
"default": "true",
|
|
168
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
169
|
+
"attribute": "at-inline-end",
|
|
170
|
+
"reflects": true
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"kind": "field",
|
|
174
|
+
"name": "_resizeObserver",
|
|
175
|
+
"type": {
|
|
176
|
+
"text": "ResizeObserver | null"
|
|
177
|
+
},
|
|
178
|
+
"privacy": "private",
|
|
179
|
+
"default": "null"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"kind": "field",
|
|
183
|
+
"name": "_onScroll",
|
|
184
|
+
"privacy": "private",
|
|
185
|
+
"readonly": true
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"kind": "method",
|
|
189
|
+
"name": "_evaluateBoundaries",
|
|
190
|
+
"privacy": "private",
|
|
191
|
+
"return": {
|
|
192
|
+
"type": {
|
|
193
|
+
"text": "void"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"attributes": [
|
|
199
|
+
{
|
|
200
|
+
"description": "Scroll axis.",
|
|
201
|
+
"name": "axis",
|
|
202
|
+
"type": {
|
|
203
|
+
"text": "RCScrollerAxis"
|
|
204
|
+
},
|
|
205
|
+
"default": "'block'",
|
|
206
|
+
"fieldName": "axis"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"description": "Optional child layout.",
|
|
210
|
+
"name": "layout",
|
|
211
|
+
"type": {
|
|
212
|
+
"text": "RCScrollerLayout"
|
|
213
|
+
},
|
|
214
|
+
"default": "'none'",
|
|
215
|
+
"fieldName": "layout"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
219
|
+
"name": "at-block-start",
|
|
220
|
+
"type": {
|
|
221
|
+
"text": "boolean"
|
|
222
|
+
},
|
|
223
|
+
"default": "true",
|
|
224
|
+
"fieldName": "atBlockStart"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
228
|
+
"name": "at-block-end",
|
|
229
|
+
"type": {
|
|
230
|
+
"text": "boolean"
|
|
231
|
+
},
|
|
232
|
+
"default": "true",
|
|
233
|
+
"fieldName": "atBlockEnd"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
237
|
+
"name": "at-inline-start",
|
|
238
|
+
"type": {
|
|
239
|
+
"text": "boolean"
|
|
240
|
+
},
|
|
241
|
+
"default": "true",
|
|
242
|
+
"fieldName": "atInlineStart"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"description": "Reflected, computed. See the class doc comment.",
|
|
246
|
+
"name": "at-inline-end",
|
|
247
|
+
"type": {
|
|
248
|
+
"text": "boolean"
|
|
249
|
+
},
|
|
250
|
+
"default": "true",
|
|
251
|
+
"fieldName": "atInlineEnd"
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"superclass": {
|
|
255
|
+
"name": "LitElement",
|
|
256
|
+
"package": "lit"
|
|
257
|
+
},
|
|
258
|
+
"tagName": "rc-scroller",
|
|
259
|
+
"customElement": true
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
"exports": [
|
|
263
|
+
{
|
|
264
|
+
"kind": "js",
|
|
265
|
+
"name": "RCScroller",
|
|
266
|
+
"declaration": {
|
|
267
|
+
"name": "RCScroller",
|
|
268
|
+
"module": "src/rc-scroller.ts"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { css as b, LitElement as u, html as v } from "lit";
|
|
2
|
+
import { property as o } from "lit/decorators.js";
|
|
3
|
+
const p = b`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
min-inline-size: 0;
|
|
7
|
+
min-block-size: 0;
|
|
8
|
+
overflow-inline: hidden;
|
|
9
|
+
overflow-block: auto;
|
|
10
|
+
overscroll-behavior-block: var(--rc-scroller-overscroll-behavior, contain);
|
|
11
|
+
overflow-anchor: var(--rc-scroller-overflow-anchor, auto);
|
|
12
|
+
scrollbar-width: var(--rc-scroller-scrollbar-width, auto);
|
|
13
|
+
scrollbar-color: var(--rc-scroller-scrollbar-color, auto);
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:host([hidden]) {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:host([axis='inline']) {
|
|
22
|
+
overflow-inline: auto;
|
|
23
|
+
overflow-block: hidden;
|
|
24
|
+
overscroll-behavior-inline: var(--rc-scroller-overscroll-behavior, contain);
|
|
25
|
+
overscroll-behavior-block: auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:host([axis='both']) {
|
|
29
|
+
overflow-inline: auto;
|
|
30
|
+
overflow-block: auto;
|
|
31
|
+
overscroll-behavior: var(--rc-scroller-overscroll-behavior, contain);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
[part='content'] {
|
|
35
|
+
display: block;
|
|
36
|
+
min-inline-size: 0;
|
|
37
|
+
min-block-size: 0;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:host([layout='content']) [part='content'] {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns:
|
|
44
|
+
[fullbleed-start] minmax(var(--rc-scroller-content-padding-inline, 1rem), 1fr)
|
|
45
|
+
[content-start] minmax(0, var(--rc-scroller-content-max-inline-size, 100%))
|
|
46
|
+
[content-end] minmax(var(--rc-scroller-content-padding-inline, 1rem), 1fr)
|
|
47
|
+
[fullbleed-end];
|
|
48
|
+
grid-auto-rows: min-content;
|
|
49
|
+
row-gap: var(--rc-scroller-content-row-gap, 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:host([layout='content']) ::slotted(*) {
|
|
53
|
+
grid-column: content;
|
|
54
|
+
min-inline-size: 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:host([layout='content']) ::slotted([data-rc-scroller-span='fullbleed']) {
|
|
58
|
+
grid-column: fullbleed;
|
|
59
|
+
inline-size: 100%;
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
var f = Object.defineProperty, l = (h, t, i, n) => {
|
|
63
|
+
for (var e = void 0, c = h.length - 1, d; c >= 0; c--)
|
|
64
|
+
(d = h[c]) && (e = d(t, i, e) || e);
|
|
65
|
+
return e && f(t, i, e), e;
|
|
66
|
+
};
|
|
67
|
+
const s = 4, a = class a extends u {
|
|
68
|
+
constructor() {
|
|
69
|
+
super(...arguments), this.axis = "block", this.layout = "none", this.atBlockStart = !0, this.atBlockEnd = !0, this.atInlineStart = !0, this.atInlineEnd = !0, this._resizeObserver = null, this._onScroll = () => this._evaluateBoundaries();
|
|
70
|
+
}
|
|
71
|
+
connectedCallback() {
|
|
72
|
+
super.connectedCallback(), this.addEventListener("scroll", this._onScroll, { passive: !0 }), typeof ResizeObserver == "function" && (this._resizeObserver = new ResizeObserver(this._onScroll), this._resizeObserver.observe(this));
|
|
73
|
+
}
|
|
74
|
+
firstUpdated() {
|
|
75
|
+
this._evaluateBoundaries();
|
|
76
|
+
}
|
|
77
|
+
disconnectedCallback() {
|
|
78
|
+
this.removeEventListener("scroll", this._onScroll), this._resizeObserver?.disconnect(), this._resizeObserver = null, super.disconnectedCallback();
|
|
79
|
+
}
|
|
80
|
+
render() {
|
|
81
|
+
return v`<div part="content"><slot></slot></div>`;
|
|
82
|
+
}
|
|
83
|
+
updated(t) {
|
|
84
|
+
(t.has("axis") || t.has("layout")) && this._evaluateBoundaries();
|
|
85
|
+
}
|
|
86
|
+
_evaluateBoundaries() {
|
|
87
|
+
const t = this.axis !== "inline", i = this.axis !== "block";
|
|
88
|
+
if (t) {
|
|
89
|
+
const n = this.scrollHeight - this.clientHeight;
|
|
90
|
+
this.atBlockStart = this.scrollTop <= s, this.atBlockEnd = this.scrollTop >= n - s;
|
|
91
|
+
} else
|
|
92
|
+
this.atBlockStart = !0, this.atBlockEnd = !0;
|
|
93
|
+
if (i) {
|
|
94
|
+
const n = this.scrollWidth - this.clientWidth, e = getComputedStyle(this).direction === "rtl" ? Math.abs(this.scrollLeft) : this.scrollLeft;
|
|
95
|
+
this.atInlineStart = e <= s, this.atInlineEnd = e >= n - s;
|
|
96
|
+
} else
|
|
97
|
+
this.atInlineStart = !0, this.atInlineEnd = !0;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
a.styles = p, a.enabledWarnings = (u.enabledWarnings ?? []).filter(
|
|
101
|
+
(t) => t !== "change-in-update"
|
|
102
|
+
);
|
|
103
|
+
let r = a;
|
|
104
|
+
l([
|
|
105
|
+
o({ reflect: !0 })
|
|
106
|
+
], r.prototype, "axis");
|
|
107
|
+
l([
|
|
108
|
+
o({ reflect: !0 })
|
|
109
|
+
], r.prototype, "layout");
|
|
110
|
+
l([
|
|
111
|
+
o({ type: Boolean, reflect: !0, attribute: "at-block-start" })
|
|
112
|
+
], r.prototype, "atBlockStart");
|
|
113
|
+
l([
|
|
114
|
+
o({ type: Boolean, reflect: !0, attribute: "at-block-end" })
|
|
115
|
+
], r.prototype, "atBlockEnd");
|
|
116
|
+
l([
|
|
117
|
+
o({ type: Boolean, reflect: !0, attribute: "at-inline-start" })
|
|
118
|
+
], r.prototype, "atInlineStart");
|
|
119
|
+
l([
|
|
120
|
+
o({ type: Boolean, reflect: !0, attribute: "at-inline-end" })
|
|
121
|
+
], r.prototype, "atInlineEnd");
|
|
122
|
+
export {
|
|
123
|
+
r as R
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=rc-scroller-BvfVYwVD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-scroller-BvfVYwVD.js","sources":["../src/rc-scroller.styles.ts","../src/rc-scroller.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const scrollerStyles = css`\n :host {\n display: block;\n min-inline-size: 0;\n min-block-size: 0;\n overflow-inline: hidden;\n overflow-block: auto;\n overscroll-behavior-block: var(--rc-scroller-overscroll-behavior, contain);\n overflow-anchor: var(--rc-scroller-overflow-anchor, auto);\n scrollbar-width: var(--rc-scroller-scrollbar-width, auto);\n scrollbar-color: var(--rc-scroller-scrollbar-color, auto);\n box-sizing: border-box;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([axis='inline']) {\n overflow-inline: auto;\n overflow-block: hidden;\n overscroll-behavior-inline: var(--rc-scroller-overscroll-behavior, contain);\n overscroll-behavior-block: auto;\n }\n\n :host([axis='both']) {\n overflow-inline: auto;\n overflow-block: auto;\n overscroll-behavior: var(--rc-scroller-overscroll-behavior, contain);\n }\n\n [part='content'] {\n display: block;\n min-inline-size: 0;\n min-block-size: 0;\n box-sizing: border-box;\n }\n\n :host([layout='content']) [part='content'] {\n display: grid;\n grid-template-columns:\n [fullbleed-start] minmax(var(--rc-scroller-content-padding-inline, 1rem), 1fr)\n [content-start] minmax(0, var(--rc-scroller-content-max-inline-size, 100%))\n [content-end] minmax(var(--rc-scroller-content-padding-inline, 1rem), 1fr)\n [fullbleed-end];\n grid-auto-rows: min-content;\n row-gap: var(--rc-scroller-content-row-gap, 0);\n }\n\n :host([layout='content']) ::slotted(*) {\n grid-column: content;\n min-inline-size: 0;\n }\n\n :host([layout='content']) ::slotted([data-rc-scroller-span='fullbleed']) {\n grid-column: fullbleed;\n inline-size: 100%;\n }\n`;\n\nexport default scrollerStyles;\n","import { LitElement, html } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport scrollerStyles from './rc-scroller.styles.js';\n\nexport type RCScrollerAxis = 'block' | 'inline' | 'both';\nexport type RCScrollerLayout = 'none' | 'content';\n\n/** Pixels of remaining scroll distance below which an edge counts as reached. */\nconst BOUNDARY_THRESHOLD = 4;\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-scroller': RCScroller;\n }\n}\n\n/**\n * Design-system-neutral native scroll region with optional content and\n * fullbleed layout tracks. The custom element host is the scroll container,\n * so native scrolling APIs and events work directly on `<rc-scroller>`.\n *\n * With `layout=\"content\"`, direct children use the centered content track.\n * Add `data-rc-scroller-span=\"fullbleed\"` to a direct child to span the full\n * available inline size. This child attribute is a layout convention rather\n * than component state.\n *\n * The author owns the accessible name and landmark semantics when the region\n * needs them. The component does not add `role` or `tabindex` automatically.\n *\n * @slot - Scrollable content.\n *\n * @csspart content - Wrapper for the default slot and optional layout grid.\n *\n * @attr axis - Scroll axis: `block`, `inline`, or `both`.\n * @attr layout - Layout mode: `none` or `content`.\n * @attr [at-block-start] - Reflected, computed. Present when scrolled to (within a few\n * pixels of) the block-start edge, or when the block axis isn't scrollable\n * (`axis=\"inline\"`) — nothing more to reveal counts as already at both edges.\n * @attr [at-block-end] - Reflected, computed. The block-end equivalent of `at-block-start`.\n * @attr [at-inline-start] - Reflected, computed. The inline-start equivalent, always\n * present when the inline axis isn't scrollable (`axis=\"block\"`).\n * @attr [at-inline-end] - Reflected, computed. The inline-end equivalent of `at-inline-start`.\n *\n * @cssprop [--rc-scroller-overscroll-behavior=contain] - Overscroll behavior on enabled axes.\n * @cssprop [--rc-scroller-overflow-anchor=auto] - Browser scroll anchoring behavior.\n * @cssprop [--rc-scroller-scrollbar-width=auto] - Scrollbar width.\n * @cssprop [--rc-scroller-scrollbar-color=auto] - Scrollbar thumb and track colors.\n * @cssprop [--rc-scroller-content-padding-inline=1rem] - Minimum inline gutter in content layout.\n * @cssprop [--rc-scroller-content-max-inline-size=100%] - Maximum width of the content track.\n * @cssprop [--rc-scroller-content-row-gap=0] - Gap between rows in content layout.\n */\nexport class RCScroller extends LitElement {\n static override styles = scrollerStyles;\n\n // Boundary state can only be measured after render, so the reflected edge\n // properties intentionally schedule one follow-up update.\n static override enabledWarnings = (LitElement.enabledWarnings ?? []).filter(\n (warning) => warning !== 'change-in-update',\n );\n\n /** Scroll axis. */\n @property({ reflect: true })\n axis: RCScrollerAxis = 'block';\n\n /** Optional child layout. */\n @property({ reflect: true })\n layout: RCScrollerLayout = 'none';\n\n /** Reflected, computed. See the class doc comment. */\n @property({ type: Boolean, reflect: true, attribute: 'at-block-start' })\n atBlockStart = true;\n\n /** Reflected, computed. See the class doc comment. */\n @property({ type: Boolean, reflect: true, attribute: 'at-block-end' })\n atBlockEnd = true;\n\n /** Reflected, computed. See the class doc comment. */\n @property({ type: Boolean, reflect: true, attribute: 'at-inline-start' })\n atInlineStart = true;\n\n /** Reflected, computed. See the class doc comment. */\n @property({ type: Boolean, reflect: true, attribute: 'at-inline-end' })\n atInlineEnd = true;\n\n private _resizeObserver: ResizeObserver | null = null;\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n this.addEventListener('scroll', this._onScroll, { passive: true });\n\n if (typeof ResizeObserver === 'function') {\n this._resizeObserver = new ResizeObserver(this._onScroll);\n this._resizeObserver.observe(this);\n }\n }\n\n override firstUpdated(): void {\n // Only accurate once the shadow DOM (and slotted content) has actually\n // rendered — connectedCallback runs before Lit's first render, when\n // scrollWidth/clientHeight etc. don't yet reflect real layout. Also\n // covers connecting already scrolled (bfcache restore, fragment nav)\n // rather than waiting for the first scroll/resize event.\n this._evaluateBoundaries();\n }\n\n override disconnectedCallback(): void {\n this.removeEventListener('scroll', this._onScroll);\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n\n super.disconnectedCallback();\n }\n\n protected override render() {\n return html`<div part=\"content\"><slot></slot></div>`;\n }\n\n protected override updated(changed: Map<PropertyKey, unknown>): void {\n if (changed.has('axis') || changed.has('layout')) {\n this._evaluateBoundaries();\n }\n }\n\n private readonly _onScroll = (): void => this._evaluateBoundaries();\n\n private _evaluateBoundaries(): void {\n const blockScrollable = this.axis !== 'inline';\n const inlineScrollable = this.axis !== 'block';\n\n if (blockScrollable) {\n const maxScrollTop = this.scrollHeight - this.clientHeight;\n\n this.atBlockStart = this.scrollTop <= BOUNDARY_THRESHOLD;\n this.atBlockEnd = this.scrollTop >= maxScrollTop - BOUNDARY_THRESHOLD;\n } else {\n this.atBlockStart = true;\n this.atBlockEnd = true;\n }\n\n if (inlineScrollable) {\n const maxScrollLeft = this.scrollWidth - this.clientWidth;\n const inlineOffset =\n getComputedStyle(this).direction === 'rtl' ? Math.abs(this.scrollLeft) : this.scrollLeft;\n\n this.atInlineStart = inlineOffset <= BOUNDARY_THRESHOLD;\n this.atInlineEnd = inlineOffset >= maxScrollLeft - BOUNDARY_THRESHOLD;\n } else {\n this.atInlineStart = true;\n this.atInlineEnd = true;\n }\n }\n}\n"],"names":["scrollerStyles","css","BOUNDARY_THRESHOLD","_RCScroller","LitElement","html","changed","blockScrollable","inlineScrollable","maxScrollTop","maxScrollLeft","inlineOffset","warning","RCScroller","__decorateClass","property"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-scroller-define.js","sources":["../src/define.ts"],"sourcesContent":["import { RCScroller } from './index.js';\n\ncustomElements.get('rc-scroller') || customElements.define('rc-scroller', RCScroller);\n\nexport * from './index.js';\n"],"names":["RCScroller"],"mappings":";AAEA,eAAe,IAAI,aAAa,KAAK,eAAe,OAAO,eAAeA,CAAU;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-scroller.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rc-scroller.js';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export type RCScrollerAxis = 'block' | 'inline' | 'both';
|
|
3
|
+
export type RCScrollerLayout = 'none' | 'content';
|
|
4
|
+
declare global {
|
|
5
|
+
interface HTMLElementTagNameMap {
|
|
6
|
+
'rc-scroller': RCScroller;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Design-system-neutral native scroll region with optional content and
|
|
11
|
+
* fullbleed layout tracks. The custom element host is the scroll container,
|
|
12
|
+
* so native scrolling APIs and events work directly on `<rc-scroller>`.
|
|
13
|
+
*
|
|
14
|
+
* With `layout="content"`, direct children use the centered content track.
|
|
15
|
+
* Add `data-rc-scroller-span="fullbleed"` to a direct child to span the full
|
|
16
|
+
* available inline size. This child attribute is a layout convention rather
|
|
17
|
+
* than component state.
|
|
18
|
+
*
|
|
19
|
+
* The author owns the accessible name and landmark semantics when the region
|
|
20
|
+
* needs them. The component does not add `role` or `tabindex` automatically.
|
|
21
|
+
*
|
|
22
|
+
* @slot - Scrollable content.
|
|
23
|
+
*
|
|
24
|
+
* @csspart content - Wrapper for the default slot and optional layout grid.
|
|
25
|
+
*
|
|
26
|
+
* @attr axis - Scroll axis: `block`, `inline`, or `both`.
|
|
27
|
+
* @attr layout - Layout mode: `none` or `content`.
|
|
28
|
+
* @attr [at-block-start] - Reflected, computed. Present when scrolled to (within a few
|
|
29
|
+
* pixels of) the block-start edge, or when the block axis isn't scrollable
|
|
30
|
+
* (`axis="inline"`) — nothing more to reveal counts as already at both edges.
|
|
31
|
+
* @attr [at-block-end] - Reflected, computed. The block-end equivalent of `at-block-start`.
|
|
32
|
+
* @attr [at-inline-start] - Reflected, computed. The inline-start equivalent, always
|
|
33
|
+
* present when the inline axis isn't scrollable (`axis="block"`).
|
|
34
|
+
* @attr [at-inline-end] - Reflected, computed. The inline-end equivalent of `at-inline-start`.
|
|
35
|
+
*
|
|
36
|
+
* @cssprop [--rc-scroller-overscroll-behavior=contain] - Overscroll behavior on enabled axes.
|
|
37
|
+
* @cssprop [--rc-scroller-overflow-anchor=auto] - Browser scroll anchoring behavior.
|
|
38
|
+
* @cssprop [--rc-scroller-scrollbar-width=auto] - Scrollbar width.
|
|
39
|
+
* @cssprop [--rc-scroller-scrollbar-color=auto] - Scrollbar thumb and track colors.
|
|
40
|
+
* @cssprop [--rc-scroller-content-padding-inline=1rem] - Minimum inline gutter in content layout.
|
|
41
|
+
* @cssprop [--rc-scroller-content-max-inline-size=100%] - Maximum width of the content track.
|
|
42
|
+
* @cssprop [--rc-scroller-content-row-gap=0] - Gap between rows in content layout.
|
|
43
|
+
*/
|
|
44
|
+
export declare class RCScroller extends LitElement {
|
|
45
|
+
static styles: import('lit').CSSResult;
|
|
46
|
+
static enabledWarnings: ("migration" | "async-perform-update")[];
|
|
47
|
+
/** Scroll axis. */
|
|
48
|
+
axis: RCScrollerAxis;
|
|
49
|
+
/** Optional child layout. */
|
|
50
|
+
layout: RCScrollerLayout;
|
|
51
|
+
/** Reflected, computed. See the class doc comment. */
|
|
52
|
+
atBlockStart: boolean;
|
|
53
|
+
/** Reflected, computed. See the class doc comment. */
|
|
54
|
+
atBlockEnd: boolean;
|
|
55
|
+
/** Reflected, computed. See the class doc comment. */
|
|
56
|
+
atInlineStart: boolean;
|
|
57
|
+
/** Reflected, computed. See the class doc comment. */
|
|
58
|
+
atInlineEnd: boolean;
|
|
59
|
+
private _resizeObserver;
|
|
60
|
+
connectedCallback(): void;
|
|
61
|
+
firstUpdated(): void;
|
|
62
|
+
disconnectedCallback(): void;
|
|
63
|
+
protected render(): import('lit').TemplateResult<1>;
|
|
64
|
+
protected updated(changed: Map<PropertyKey, unknown>): void;
|
|
65
|
+
private readonly _onScroll;
|
|
66
|
+
private _evaluateBoundaries;
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rcarls/rc-scroller",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.5.0",
|
|
7
|
+
"description": "Design-system-neutral native scroll region with optional content and fullbleed layout tracks.",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/richardcarls/rc-webcomponents.git",
|
|
11
|
+
"directory": "packages/rc-scroller"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-scroller",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"module": "./dist/rc-scroller.js",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/types/packages/rc-scroller/src/index.d.ts",
|
|
24
|
+
"default": "./dist/rc-scroller.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"./define": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/types/packages/rc-scroller/src/define.d.ts",
|
|
30
|
+
"default": "./dist/rc-scroller-define.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"types": "./dist/types/packages/rc-scroller/src/index.d.ts",
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"./dist/rc-scroller-define.js"
|
|
37
|
+
],
|
|
38
|
+
"customElements": "dist/custom-elements.json",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc && vite build && cem analyze",
|
|
41
|
+
"cem:analyze": "cem analyze",
|
|
42
|
+
"preview": "vite preview",
|
|
43
|
+
"test:browser": "vitest --run",
|
|
44
|
+
"test:browser:chrome": "vitest --run --project=chromium",
|
|
45
|
+
"test:browser:firefox": "vitest --run --project=firefox"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
49
|
+
"@vitest/browser-playwright": "4.1.5",
|
|
50
|
+
"lit": "^3.0.0",
|
|
51
|
+
"playwright": "^1.56.0",
|
|
52
|
+
"rollup": "^4.60.2",
|
|
53
|
+
"typescript": "~5.9.3",
|
|
54
|
+
"vite": "^7.1.7",
|
|
55
|
+
"vite-plugin-dts": "^4.5.4",
|
|
56
|
+
"vitest": "^4.0.6",
|
|
57
|
+
"vitest-browser-lit": "^1.0.1"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"lit": "^3.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|