Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 3x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 76x 76x 2x 2x 2x 2x 74x 76x 7x 7x 7x 7x 7x 7x | import { h } from '../h';
import { For } from './For';
import { createVirtualizer } from './virtualizer';
import type { SignalGetter, Attribute } from '../types';
/**
* Props for configuring the VirtualFor scroll container element.
* Allows customization of the container's appearance and attributes.
*/
interface VirtualForContainerProps {
/** CSS class name(s) for the scroll container */
className?: string;
/** Inline styles for the scroll container */
style?: Record<string, string | SignalGetter<string>>;
/** Custom attributes to apply to the container (e.g., data-testid, aria-label) */
attributes?: Attribute[];
}
/**
* Props for the VirtualFor component that renders large lists efficiently.
*/
interface VirtualForProps<T> {
/** Signal containing the array of items to virtualize */
each: SignalGetter<T[]>;
/** Fixed height of each item in pixels */
itemHeight: number;
/** Function that renders each item, receiving the item data and its index */
children: (item: T, index: number) => HTMLElement;
/** Optional placeholder element to show while scrolling or for empty slots */
placeholder?: HTMLElement;
/** Number of extra items to render outside visible area (default: 5) */
overscan?: number;
/** Optional configuration for the scroll container element */
containerProps?: VirtualForContainerProps;
}
/**
* Renders large lists efficiently by only rendering visible items and a small buffer.
*
* VirtualFor uses a virtual scroller that calculates which items should be visible
* based on scroll position and container height. Only visible items plus an overscan
* buffer are rendered, making it suitable for lists with thousands of items.
*
* @param props Configuration object with list data, item height, and render function
* @returns An HTMLElement containing the virtualized list with scroll container
*
* @example
* ```typescript
* const [items] = createSignal(Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` })));
*
* // Simple usage
* const virtualList = VirtualFor({
* each: items,
* itemHeight: 50,
* children: (item, index) => h.div({}, `${index}: ${item.text}`)
* });
*
* // With placeholder for loading states
* const loadingPlaceholder = h.div({
* style: { height: '50px', background: '#f0f0f0' }
* }, 'Loading...');
*
* const listWithPlaceholder = VirtualFor({
* each: items,
* itemHeight: 50,
* placeholder: loadingPlaceholder,
* children: (item, index) => h.div({}, `${index}: ${item.text}`)
* });
*
* // With container customization
* const customList = VirtualFor({
* each: items,
* itemHeight: 50,
* overscan: 3,
* containerProps: {
* className: 'my-scroller',
* style: { height: '400px' },
* attributes: [
* { name: 'data-testid', value: 'virtual-list' },
* { name: 'aria-label', value: 'Virtualized item list' }
* ]
* },
* children: (item, index) => h.div({}, `${index}: ${item.text}`)
* });
* ```
*/
export function VirtualFor<T>(props: VirtualForProps<T>): HTMLElement {
const { each, children, itemHeight, overscan, containerProps, placeholder } = props;
const virtualizer = createVirtualizer({
items: each,
itemHeight,
overscan,
});
// Extract container props with defaults
const className = containerProps?.className;
const customStyle = containerProps?.style || {};
const customAttributes = containerProps?.attributes || [];
// Filter out dangerous attributes that could break functionality
const safeAttributes = customAttributes.filter(
(attr) => !['ref', 'role', 'style', 'class', 'className'].includes(attr.name)
);
// Build container attributes object
const containerAttrs: Record<string, unknown> = {
ref: virtualizer.setContainer,
class: className,
role: 'list',
style: {
overflow: 'auto',
height: '100%',
...customStyle,
},
};
// Apply safe custom attributes
safeAttributes.forEach((attr) => {
containerAttrs[attr.name] = attr.value;
});
const container = h.div(containerAttrs);
const sizer = h.div({
style: {
position: 'relative',
height: () => `${virtualizer.totalHeight()}px`,
},
});
const content = h.div({
style: {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
willChange: 'transform',
contain: 'layout',
transform: () => `translate3d(0, ${virtualizer.visibleState().scrollOffset}px, 0)`,
},
});
const renderedItems = For({
each: virtualizer.visibleItems,
key: (item) => item.index,
children: (itemSignal) => {
const it = itemSignal();
// If placeholder is provided and data is not yet available, show placeholder
if (placeholder && !it.data) {
const placeholderClone = placeholder.cloneNode(true) as HTMLElement;
placeholderClone.style.height = `${itemHeight}px`;
return placeholderClone;
}
return children(it.data as T, it.index);
},
});
content.appendChild(renderedItems);
sizer.appendChild(content);
container.appendChild(sizer);
return container;
}
|