@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 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,6 @@
1
+ import { R as e } from "./rc-scroller-BvfVYwVD.js";
2
+ customElements.get("rc-scroller") || customElements.define("rc-scroller", e);
3
+ export {
4
+ e as RCScroller
5
+ };
6
+ //# sourceMappingURL=rc-scroller-define.js.map
@@ -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,5 @@
1
+ import { R as e } from "./rc-scroller-BvfVYwVD.js";
2
+ export {
3
+ e as RCScroller
4
+ };
5
+ //# sourceMappingURL=rc-scroller.js.map
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export declare const scrollerStyles: import('lit').CSSResult;
2
+ export default scrollerStyles;
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
+ }