/ Examples
core
Source
// Basic List — SolidJS implementation using vlist-solidjs adapter
// Demonstrates core vlist with 100,000 items.

import { render } from "solid-js/web";
import { createVList } from "vlist-solidjs";
import { COUNT, ITEM_HEIGHT, makeItems, itemTemplate } from "../shared.js";

// =============================================================================
// App Component
// =============================================================================

const items = makeItems(COUNT);

function App() {
  const { setRef } = createVList(() => ({
    ariaLabel: "Orders",
    items,
    item: {
      height: ITEM_HEIGHT,
      striped: true,
      template: itemTemplate,
    },
  }));

  return (
    <div class="container">
      <header>
        <h1>Basic List</h1>
        <p class="description">
          SolidJS implementation — the core of <code>vlist</code>. Scroll
          through 100,000 items to see virtualization in action.
        </p>
      </header>

      <div class="split-layout">
        <div class="split-main">
          <h2 class="sr-only">Orders</h2>
          <div ref={setRef} id="list-container" />
        </div>

        <aside class="split-panel">
          <section class="ui-section">
            <h3 class="ui-title">About</h3>
            <p class="ui-text">
              This list renders <strong>100,000 items</strong> but only creates
              DOM nodes for the visible rows. Scroll at any speed — the frame
              rate stays constant.
            </p>
            <p class="ui-text">
              Built with <strong>vlist-solidjs</strong>, the SolidJS adapter for
              vlist. One primitive, zero boilerplate.
            </p>
          </section>

          <section class="ui-section">
            <h3 class="ui-title">How it works</h3>
            <p class="ui-text">
              Each item has a fixed height of <strong>64 px</strong>. vlist
              calculates which rows are visible and renders only those,
              recycling DOM elements as you scroll.
            </p>
          </section>

          <section class="ui-section">
            <h3 class="ui-title">Accessibility</h3>
            <p class="ui-text">
              vlist implements the <strong>WAI-ARIA Listbox</strong> pattern to
              provide a fully accessible virtual list experience — including{" "}
              <code>role</code>, <code>aria-setsize</code>,{" "}
              <code>aria-posinset</code>, and keyboard navigation out of the
              box.
            </p>
          </section>
        </aside>
      </div>
    </div>
  );
}

// =============================================================================
// Mount
// =============================================================================

render(() => <App />, document.getElementById("solidjs-root"));
<div id="solidjs-root"></div>
// Shared data and utilities for basic list example variants
// This file is imported by all framework implementations to avoid duplication

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

export const COUNT = 100_000;
export const ITEM_HEIGHT = 64;

// =============================================================================
// Data generator
// =============================================================================

// Deterministic hash (murmurhash-inspired)
const hash = (i, seed = 0) => {
  let h = Math.imul(i ^ seed ^ 0x5bd1e995, 0x5bd1e995);
  h ^= h >>> 13;
  h = Math.imul(h, 0x5bd1e995);
  return (h ^ (h >>> 15)) >>> 0;
};

const pick = (arr, i, seed) => arr[hash(i, seed) % arr.length];

const STATUSES = [
  "shipped",
  "delivered",
  "pending",
  "processing",
  "cancelled",
  "returned",
];

// Date clustering — each day gets a random batch of orders (50–350)
const TODAY = new Date();
TODAY.setHours(0, 0, 0, 0);
const DAY = 24 * 60 * 60 * 1000;
const formatDate = new Intl.DateTimeFormat(undefined, {
  month: "short",
  day: "numeric",
  year: "numeric",
  hour: "numeric",
  minute: "2-digit",
}).format;

// Pre-build a date for each order index (0 = most recent)
const buildDates = (count) => {
  const dates = new Array(count);
  let idx = 0;
  let day = 0;
  while (idx < count) {
    const batch = (hash(day, 99) % 301) + 50; // 50–350 orders per day
    const base = TODAY.getTime() - day * DAY;
    const end = Math.min(idx + batch, count);
    for (let j = idx; j < end; j++) dates[j] = base + (hash(j, 88) % DAY);
    idx = end;
    day++;
  }
  return dates;
};

let ORDER_DATES = buildDates(COUNT);

const ensureDates = (count) => {
  if (count > ORDER_DATES.length) ORDER_DATES = buildDates(count);
};

const formatAmount = new Intl.NumberFormat(undefined, {
  style: "currency",
  currency: "USD",
}).format;

/**
 * Generate a single order item by index.
 * Deterministic — same index always produces the same item.
 * @param {number} i - 0-based item index (order id = i + 1)
 * @returns {{ id: number, customer: number, amount: string, date: string, status: string }}
 */
export const makeItem = (i) => {
  const status = pick(STATUSES, i, 1);
  const amount = ((hash(i, 2) % 999900) + 100) / 100; // 1.00 – 9,999.99
  const customer = (hash(i, 4) % 90000) + 10000; // 10000–99999

  ensureDates(i + 1);

  return {
    id: i + 1,
    customer,
    amount: formatAmount(amount),
    date: formatDate(ORDER_DATES[i] ?? Date.now()),
    status,
  };
};

/**
 * Generate a batch of order items, highest id first (most recent at top).
 * @param {number} count - number of items
 * @param {number} [startIndex=0] - base index for id generation
 * @returns {Array}
 */
export const makeItems = (count, startIndex = 0) => {
  ensureDates(count);
  return Array.from({ length: count }, (_, i) => {
    const itemIndex = startIndex + count - 1 - i;
    return {
      ...makeItem(itemIndex),
      date: formatDate(ORDER_DATES[i]),
    };
  });
};

// =============================================================================
// Template
// =============================================================================

/** Map order status → ui-badge semantic color */
const STATUS_BADGE = {
  shipped: "info",
  delivered: "success",
  pending: "warning",
  processing: "purple",
  cancelled: "error",
  returned: "muted",
};

export const itemTemplate = (item, i) => `
  <div class="item__row">
    <span class="item__label">Order #${item.id} — C-${item.customer}</span>
    <span class="ui-badge ui-badge--sm ui-badge--${STATUS_BADGE[item.status] || "muted"}">${item.status}</span>
  </div>
  <div class="item__row">
    <span class="item__date">${item.date}</span>
    <span class="item__amount">${item.amount}</span>
  </div>
`;
/* Basic List — order list item styles (shared across all variants) */

.vlist-item {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: stretch;
    gap: 4px;
    padding: 0 16px;
    font-variant-numeric: tabular-nums;
}

.item__row {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.item__label {
    font-size: 16px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
}

.item__date {
    font-size: 13px;
    color: var(--text-muted);
    white-space: nowrap;
    flex: 1;
    min-width: 0;
}

.item__amount {
    font-size: 15px;
    white-space: nowrap;
    flex-shrink: 0;
    text-align: right;
}

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