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.