/ Examples
core

Basic List

Pure vanilla JavaScript — the core of @floor/vlist. Use the control panel to explore item count, overscan, scroll-to, and data operations in real time.

Source
// Basic List — Interactive control panel
// Demonstrates core vlist API: item count, overscan, scrollToIndex,
// and data operations (append, prepend, remove).

import { vlist, withSelection } from 'vlist';
import {
  DEFAULT_COUNT,
  ITEM_HEIGHT,
  makeUsers,
  itemTemplate,
} from '../shared.js';
import { createStats } from '../../stats.js';
import './controls.js';

// =============================================================================
// State — exported so controls.js can read/write
// =============================================================================

export let users = makeUsers(DEFAULT_COUNT);
export let nextId = DEFAULT_COUNT + 1;
export let currentOverscan = 3;
export let list = null;
export let selectedIndex = -1;

export function setUsers(u) {
  users = u;
}
export function setNextId(n) {
  nextId = n;
}
export function setCurrentOverscan(n) {
  currentOverscan = n;
}
export function setSelectedIndex(i) {
  selectedIndex = i;
}

// =============================================================================
// Stats — shared footer (progress, velocity, visible/total)
// =============================================================================

export const stats = createStats({
  getList: () => list,
  getTotal: () => users.length,
  getItemHeight: () => ITEM_HEIGHT,
  container: '#list-container',
});

// =============================================================================
// Create / Recreate list
// =============================================================================

export function createList() {
  if (list) {
    list.destroy();
    list = null;
  }
  selectedIndex = -1;

  list = vlist({
    container: '#list-container',
    ariaLabel: 'User list',
    overscan: currentOverscan,
    items: users,
    item: {
      height: ITEM_HEIGHT,
      template: itemTemplate,
    },
  })
    .use(withSelection({ mode: 'single' }))
    .build();

  list.on('range:change', stats.scheduleUpdate);
  list.on('scroll', stats.scheduleUpdate);
  list.on('velocity:change', ({ velocity }) => stats.onVelocity(velocity));
  list.on('selection:change', ({ selected }) => {
    selectedIndex = selected.length > 0 ? selected[0] : -1;
  });

  stats.update();
}

// =============================================================================
// Footer — right side (contextual, specific to this example)
// =============================================================================

const ftHeight = document.getElementById('ft-height');
const ftOverscan = document.getElementById('ft-overscan');

export function updateContext() {
  ftHeight.textContent = ITEM_HEIGHT;
  ftOverscan.textContent = currentOverscan;
}

// =============================================================================
// Re-export constants for controls.js
// =============================================================================

export { DEFAULT_COUNT };

// =============================================================================
// Initialise
// =============================================================================

createList();
updateContext();
<div class="container">
    <header>
        <h1>Basic List</h1>
        <p class="description">
            Pure vanilla JavaScript — the core of <code>@floor/vlist</code>. Use
            the control panel to explore item count, overscan, scroll-to, and
            data operations in real time.
        </p>
    </header>

    <div class="split-layout">
        <div class="split-main">
            <div id="list-container"></div>
        </div>

        <aside class="split-panel">
            <!-- Items -->
            <section class="panel-section">
                <h3 class="panel-title">Items</h3>

                <div class="panel-row">
                    <label class="panel-label">Count</label>
                    <span class="panel-value" id="count-value">10,000</span>
                </div>
                <div class="panel-row slider">
                    <input
                        type="range"
                        id="count-slider"
                        class="panel-slider"
                        min="100"
                        max="100000"
                        step="100"
                        value="10000"
                    />
                </div>

                <div class="panel-row">
                    <label class="panel-label">Overscan</label>
                    <span class="panel-value" id="overscan-value">3</span>
                </div>
                <div class="panel-row slider">
                    <input
                        type="range"
                        id="overscan-slider"
                        class="panel-slider"
                        min="0"
                        max="10"
                        step="1"
                        value="3"
                    />
                </div>
            </section>

            <!-- Scroll To -->
            <section class="panel-section">
                <h3 class="panel-title">Scroll To</h3>

                <div class="panel-row">
                    <div class="panel-input-group">
                        <input
                            type="number"
                            id="scroll-index"
                            class="panel-input"
                            placeholder="Index"
                            min="0"
                            value="0"
                        />
                        <select id="scroll-align" class="panel-select">
                            <option value="start">start</option>
                            <option value="center">center</option>
                            <option value="end">end</option>
                        </select>
                        <button id="scroll-go" class="panel-btn">Go</button>
                    </div>
                </div>

                <div class="panel-row">
                    <div class="panel-btn-group">
                        <button
                            id="btn-first"
                            class="panel-btn panel-btn--icon"
                            title="First"
                        >
                            <i class="icon icon--up"></i>
                        </button>
                        <button
                            id="btn-middle"
                            class="panel-btn panel-btn--icon"
                            title="Middle"
                        >
                            <i class="icon icon--center"></i>
                        </button>
                        <button
                            id="btn-last"
                            class="panel-btn panel-btn--icon"
                            title="Last"
                        >
                            <i class="icon icon--down"></i>
                        </button>
                    </div>
                </div>
            </section>

            <!-- Data Operations -->
            <section class="panel-section">
                <h3 class="panel-title">Data</h3>

                <div class="panel-row">
                    <div class="panel-btn-group">
                        <button
                            id="btn-prepend"
                            class="panel-btn"
                            title="Prepend 1 item"
                        >
                            <i class="icon icon--add"></i> Prepend
                        </button>
                        <button
                            id="btn-append"
                            class="panel-btn"
                            title="Append 1 item"
                        >
                            <i class="icon icon--add"></i> Append
                        </button>
                        <button
                            id="btn-append-100"
                            class="panel-btn"
                            title="Append 100 items"
                        >
                            <i class="icon icon--add"></i> +100
                        </button>
                    </div>
                </div>

                <div class="panel-row">
                    <div class="panel-btn-group">
                        <button
                            id="btn-remove"
                            class="panel-btn"
                            title="Remove last item"
                        >
                            <i class="icon icon--remove"></i> Remove
                        </button>
                        <button
                            id="btn-clear"
                            class="panel-btn"
                            title="Clear all items"
                        >
                            <i class="icon icon--trash"></i> Clear
                        </button>
                        <button
                            id="btn-reset"
                            class="panel-btn"
                            title="Reset to 10,000 items"
                        >
                            <i class="icon icon--shuffle"></i> Reset
                        </button>
                    </div>
                </div>
            </section>
        </aside>
    </div>

    <footer class="example-footer" id="example-footer">
        <div class="example-footer__left">
            <span class="example-footer__stat">
                <strong id="ft-progress">0%</strong>
            </span>
            <span class="example-footer__stat">
                <span id="ft-velocity">0.00</span> /
                <strong id="ft-velocity-avg">0.00</strong>
                <span class="example-footer__unit">px/ms</span>
            </span>
            <span class="example-footer__stat">
                <span id="ft-dom">0</span> /
                <strong id="ft-total">0</strong>
                <span class="example-footer__unit">items</span>
            </span>
        </div>
        <div class="example-footer__right">
            <span class="example-footer__stat">
                height <strong id="ft-height">56</strong
                ><span class="example-footer__unit">px</span>
            </span>
            <span class="example-footer__stat">
                overscan <strong id="ft-overscan">3</strong>
            </span>
        </div>
    </footer>
</div>
// Shared data and utilities for basic list example variants
// This file is imported by all framework implementations to avoid duplication

import { makeUser, makeUsers } from '../../src/data/people.js';

// =============================================================================
// Constants
// =============================================================================

export const DEFAULT_COUNT = 10_000;
export const ITEM_HEIGHT = 56;

// =============================================================================
// Data Generation (re-export for convenience)
// =============================================================================

export { makeUser, makeUsers };

// =============================================================================
// Templates
// =============================================================================

export const itemTemplate = (user, i) => `
  <div class="item__avatar" style="background:${user.color}">${user.initials}</div>
  <div class="item__text">
    <div class="item__name">${user.name}</div>
    <div class="item__email">${user.email}</div>
  </div>
  <span class="item__index">#${i + 1}</span>
`;
/* Basic List — example styles */

/* ============================================================================
   Item  (styles live on .vlist-item — no wrapper div needed)
   ============================================================================ */

.vlist-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 0 16px;
}

.item__avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 600;
    font-size: 15px;
    flex-shrink: 0;
}

.item__text {
    flex: 1;
    min-width: 0;
}

.item__name {
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.item__email {
    font-size: 13px;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.item__index {
    font-size: 12px;
    color: var(--text-muted);
    min-width: 48px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* ============================================================================
   Selected state
   ============================================================================ */