@rcarls/rc-virtual-scroller 0.0.0-next-20260925180130

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/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # @rcarls/rc-virtual-scroller
2
+
3
+ ## 0.0.0-next-20260925180130
4
+
5
+ ### Minor Changes
6
+
7
+ - aed2034: Add `rc-virtual-scroller`, a headless virtualization wrapper.
8
+
9
+ Mounting a large collection as custom elements costs real time regardless of
10
+ `content-visibility: auto`: upgrade happens at parse and insertion, before
11
+ style resolves, so containment cannot prevent element construction. This
12
+ package stops the count of mounted elements from scaling with collection
13
+ size, without taking over rendering.
14
+
15
+ The element measures geometry against a slotted container and dispatches
16
+ `rc-virtual-scroller-range` with the visible `[start, end)` index range; the
17
+ consumer renders that slice into its own container with whatever it already
18
+ uses, and two spacer parts reserve the off-range scroll space. Items per line
19
+ and line pitch are measured from the container's own grid tracks rather than
20
+ configured, so `auto-fill` and container queries stay authoritative. The
21
+ focused item is never dropped from the reported range, so scrolling never
22
+ silently resets keyboard focus to `<body>`.
23
+
24
+ `axis="block|inline"` is logical: `block` windows stacked lines, `inline`
25
+ windows a row of items such as a horizontal shelf. Direction and writing mode
26
+ are read from computed style, so RTL pages and vertical text work without
27
+ configuration.
28
+
29
+ ### Patch Changes
30
+
31
+ - 0c9d563: Re-resolve the writing direction when a `dir` attribute changes, and at the
32
+ start of every `scrollToIndex` call, so an inline-axis scroller keeps the
33
+ right range and scrolls to the right item after a page switches direction.
34
+ - Updated dependencies [50d5610]
35
+ - Updated dependencies [9ee769c]
36
+ - Updated dependencies [4341311]
37
+ - Updated dependencies [51f6403]
38
+ - Updated dependencies [9caca6b]
39
+ - Updated dependencies [b9175e9]
40
+ - Updated dependencies [f7f0270]
41
+ - Updated dependencies [32ebead]
42
+ - Updated dependencies [11680b6]
43
+ - Updated dependencies [223d36c]
44
+ - Updated dependencies [7b91a44]
45
+ - @rcarls/rc-common@0.0.0-next-20260925180130
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @rcarls/rc-virtual-scroller
2
+
3
+ Headless virtualization wrapper that reports the visible index range for a
4
+ consumer-owned list or grid and reserves the off-range scroll space.
5
+
6
+ ```html
7
+ <rc-scroller>
8
+ <rc-virtual-scroller count="4000" item-size="288">
9
+ <ul class="cards">
10
+ <!-- only the items in the current range -->
11
+ </ul>
12
+ </rc-virtual-scroller>
13
+ </rc-scroller>
14
+ ```
15
+
16
+ ```js
17
+ const scroller = document.querySelector('rc-virtual-scroller');
18
+
19
+ const render = ({ start, end }) => renderItems(recipes.slice(start, end), start);
20
+
21
+ // The element may have measured before this listener existed.
22
+ if (scroller.range) render(scroller.range);
23
+
24
+ scroller.addEventListener('rc-virtual-scroller-range', (event) => render(event.detail));
25
+ ```
26
+
27
+ The element measures on its own schedule, which can be before your listener is
28
+ attached, and it never re-sends an unchanged range. Render once from the
29
+ read-only `range` property when you subscribe, then follow the event.
30
+
31
+ The element never creates, recycles, or positions item DOM. It measures
32
+ geometry, dispatches `rc-virtual-scroller-range`, and sizes two spacer parts
33
+ around the default slot. You slot one container element and render the reported
34
+ slice into it with whatever you already use.
35
+
36
+ That split is the whole point. The markup, the CSS layout, and the
37
+ accessibility semantics stay with the code that owns them, so a native
38
+ `grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))` keeps working
39
+ untouched, `<ul>` still contains `<li>`, and a framework's keyed list still
40
+ reuses the nodes that stayed in range.
41
+
42
+ ## Why not `content-visibility: auto`
43
+
44
+ CSS containment skips layout and paint for offscreen content, but a custom
45
+ element upgrades when it is parsed or inserted, before style is resolved.
46
+ Containment cannot prevent element construction, so a collection of a few
47
+ thousand custom elements still pays for all of them up front. Virtualization is
48
+ the only thing that avoids that cost.
49
+
50
+ ## Axis, direction, and writing mode
51
+
52
+ `axis` is logical. `block`, the default, windows lines that stack the way
53
+ paragraphs do: top to bottom in horizontal text, right to left in
54
+ `vertical-rl`. `inline` windows items running the way text runs, such as a
55
+ horizontal shelf. For the inline axis, lay the container's items along it with
56
+ `display: flex` or a grid with `grid-auto-flow: column`.
57
+
58
+ ```html
59
+ <rc-virtual-scroller axis="inline" count="500" item-size="160">
60
+ <ul class="shelf">
61
+ <!-- display: flex -->
62
+ </ul>
63
+ </rc-virtual-scroller>
64
+ ```
65
+
66
+ Direction and writing mode come from the element's computed style, so RTL
67
+ pages and vertical text need no configuration. The visible range is measured
68
+ from where the scroll container's visible box sits relative to the element, in
69
+ the element's own flow, so it never depends on the sign conventions of
70
+ `scrollLeft` in RTL.
71
+
72
+ ## Geometry
73
+
74
+ `count` is the true total. `item-size` is the estimated line pitch in pixels;
75
+ it is used for the first frame and whenever nothing can be measured yet, so a
76
+ reasonable value matters for the first paint and for restoring a saved scroll
77
+ position.
78
+
79
+ Items per line and line pitch are measured from the slotted container rather
80
+ than configured. On the block axis a line holds one item per grid column
81
+ (`grid-template-columns`); on the inline axis, one item per grid row
82
+ (`grid-template-rows`). Grid columns and rows are logical, so this holds in
83
+ vertical writing modes too. The pitch is the offset between two items one line
84
+ apart, which keeps `auto-fill`, container queries, and the gap authoritative in
85
+ CSS instead of duplicated in JavaScript. `rc-virtual-scroller-range` reports
86
+ `itemsPerLine`, `lineSize`, and a `measured` flag that is `false` while the
87
+ geometry is still the `item-size` estimate.
88
+
89
+ `overscan` (default `2`) is how many extra lines render past each edge.
90
+ `disabled` reports the whole collection as the range and stops measuring, which
91
+ makes an A/B against the non-virtualized cost a one-attribute change.
92
+
93
+ The element measures against the nearest scrolling ancestor. Set the
94
+ `scrollTarget` property when that ancestor only becomes scrollable after its own
95
+ upgrade, or when the real scrollport is further up the tree.
96
+
97
+ `scrollToIndex(index, { align, behavior })` scrolls an arbitrary index into
98
+ view along `axis`, including one that is not currently rendered. `align` is
99
+ `start` (the default), `center`, `end`, or `nearest`. It is accurate to the
100
+ current line pitch, so it is only as accurate as `item-size` until at least one
101
+ line has rendered.
102
+
103
+ ## Accessibility
104
+
105
+ A virtualized set must still tell assistive technology how big it really is,
106
+ because the DOM no longer says so. The element does not write these for you:
107
+ the roles belong to your markup, and guessing at them would fight a container
108
+ that manages its own rows.
109
+
110
+ - For a list, put `aria-setsize` (the true `count`) and `aria-posinset` (the
111
+ item's real index, not its position in the rendered slice) on each item.
112
+ - For a grid, use `aria-rowcount` / `aria-rowindex`, and
113
+ `aria-colcount` / `aria-colindex` where columns are also virtualized.
114
+ - Set `aria-busy="true"` on the container while a batch is being swapped in, as
115
+ the [WAI-ARIA APG feed pattern](https://www.w3.org/WAI/ARIA/apg/patterns/feed/)
116
+ describes.
117
+
118
+ The one thing the element does handle is focus. Unmounting the focused element
119
+ moves focus to `<body>` with no error and no visible cause, which is how a
120
+ keyboard user silently loses their place. The reported range is always widened
121
+ to include the focused item, contiguously, so your render stays a plain slice;
122
+ it collapses again as soon as focus moves. Scrolling a long way while holding
123
+ focus therefore renders a larger range on purpose.
124
+
125
+ The element sets `overflow-anchor: none` on itself. Browser scroll anchoring
126
+ exists to hold the reading position when content above the viewport changes
127
+ size, which is what this element does every frame, so leaving it on makes the
128
+ two fight and the scroll position drift.
129
+
130
+ Import `@rcarls/rc-virtual-scroller` to use the class without registering it, or
131
+ import `@rcarls/rc-virtual-scroller/define` to register
132
+ `<rc-virtual-scroller>`.