PageSourceSearch

https://cairnjs.vercel.app/src/virtual-list.js

js cairnjs.vercel.app collected 2026-10-03 06:20:41 UTC 3,065 bytes, 95 lines download raw bytes

1/**
2 * @cairn-js/core - Virtual List Component
3 * High-performance virtualized list rendering (100k+ items at 60fps) with fine-grained signal bindings.
4 */
5
6import { div, ul } from './dom.js';
7import { state } from './state.js';
8
9function computeVirtualLayout({ totalItems, itemHeight, containerHeight, scrollTop, bufferSize = 5 }) {
10    const totalHeight = Math.max(0, totalItems * itemHeight);
11    const rawStart = Math.floor(scrollTop / itemHeight) - bufferSize;
12    const startIndex = Math.max(0, Math.min(totalItems - 1, rawStart >= 0 ? rawStart : 0));
13    const visibleCount = Math.ceil(containerHeight / itemHeight) + bufferSize * 2;
14    const endIndex = Math.max(0, Math.min(totalItems - 1, startIndex + visibleCount));
15    const offsetY = startIndex * itemHeight;
16    return { totalHeight, startIndex, endIndex, offsetY };
17}
18
19export function VirtualList(props = {}) {
20    const {
21        data = [],
22        renderItem = (item) => div(String(item)),
23        itemHeight = 40,
24        containerHeight = 400,
25        bufferSize = 5
26    } = props;
27
28    const items = data._isCairnState ? data : state(data);
29    const scrollTop = state(0);
30
31    const layout = state(() => {
32        const listData = items.value || [];
33        return computeVirtualLayout({
34            totalItems: listData.length,
35            itemHeight,
36            containerHeight,
37            scrollTop: scrollTop.value,
38            bufferSize
39        });
40    });
41
42    const visibleItems = state(() => {
43        const listData = items.value || [];
44        const { startIndex, endIndex } = layout.value;
45        const visible = [];
46        for (let i = startIndex; i <= endIndex && i < listData.length; i++) {
47            visible.push({ index: i, item: listData[i] });
48        }
49        return visible;
50    });
51
52    let scrollRafId = null;
53
54    return div({
55        style: () => ({
56            height: `${containerHeight}px`,
57            overflowY: 'auto',
58            position: 'relative'
59        }),
60        onscroll: (e) => {
61            const targetTop = e.target ? e.target.scrollTop : 0;
62            if (typeof requestAnimationFrame !== 'undefined') {
63                if (!scrollRafId) {
64                    scrollRafId = requestAnimationFrame(() => {
65                        scrollTop.value = targetTop;
66                        scrollRafId = null;
67                    });
68                }
69            } else {
70                scrollTop.value = targetTop;
71            }
72        }
73    },
74        div({
75            style: () => ({
76                height: `${layout.value.totalHeight}px`,
77                position: 'relative'
78            })
79        },
80            div({
81                style: () => ({
82                    transform: `translateY(${layout.value.offsetY}px)`,
83                    position: 'absolute',
84                    top: 0,
85                    left: 0,
86                    right: 0
87                })
88            },
89                () => visibleItems.value.map(({ item, index }) => renderItem(item, index))
90            )
91        )
92    );
93}
94
95export default VirtualList;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.