/ Examples
scalescrollbar

Large List

Builder pattern with vlist/builder + withCompression + withScrollbar plugins. Handles 100K–5M items with automatic scroll compression when total height exceeds the browser's 16.7M pixel limit.

Loading…

Compression activates automatically when the virtual height exceeds ~16.7 million pixels. The builder's plugin system keeps the core small — compression logic is only loaded when you .use(withCompression()). ⚡

Source
// Shared data and utilities for large-list example variants
// This file is imported by all framework implementations to avoid duplication

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

export const ITEM_HEIGHT = 48;

export const SIZES = {
  "100k": 100_000,
  "500k": 500_000,
  "1m": 1_000_000,
  "2m": 2_000_000,
  "5m": 5_000_000,
};

export const COLORS = [
  "#667eea",
  "#764ba2",
  "#f093fb",
  "#f5576c",
  "#4facfe",
  "#43e97b",
  "#fa709a",
  "#fee140",
];

// =============================================================================
// Utilities
// =============================================================================

// Simple hash for consistent per-item values
export function hash(n) {
  let h = (n + 1) * 2654435761;
  h ^= h >>> 16;
  return Math.abs(h);
}

// Generate items on the fly
export function generateItems(count) {
  return Array.from({ length: count }, (_, i) => ({
    id: i + 1,
    value: hash(i) % 100,
    hash: hash(i).toString(16).slice(0, 8).toUpperCase(),
    color: COLORS[i % COLORS.length],
  }));
}

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

// Item template
export const itemTemplate = (item, index) => `
  <div class="item-row">
    <div class="item-color" style="background:${item.color}"></div>
    <div class="item-info">
      <span class="item-label">#${(index + 1).toLocaleString()}</span>
      <span class="item-hash">${item.hash}</span>
    </div>
    <div class="item-bar-wrap">
      <div class="item-bar" style="width:${item.value}%;background:${item.color}"></div>
    </div>
    <span class="item-value">${item.value}%</span>
  </div>
`;

// =============================================================================
// Compression Info
// =============================================================================

export function getCompressionInfo(count, itemHeight = ITEM_HEIGHT) {
  const totalHeight = count * itemHeight;
  const maxHeight = 16_777_216; // browser limit ~16.7M px
  const isCompressed = totalHeight > maxHeight;
  const ratio = isCompressed ? (totalHeight / maxHeight).toFixed(1) : "1.0";

  return {
    isCompressed,
    virtualHeight: totalHeight,
    ratio,
  };
}

// Format virtualization percentage
export function calculateVirtualization(domNodes, total) {
  if (total > 0 && domNodes > 0) {
    return ((1 - domNodes / total) * 100).toFixed(4);
  }
  return "0.0000";
}
/* Builder Million Items — example-specific styles only
   Common styles (.container, h1, .description, .stats, footer)
   are provided by example/example.css using shell.css design tokens.
   Panel system (.split-layout, .split-panel, .panel-*)
   is also provided by example/example.css. */

/* List container */
#list-container {
    height: 600px;
    margin: 0 auto;
}

/* ============================================================================
   Size Selector
   ============================================================================ */

.size-selector {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
}

.size-btn {
    padding: 6px 16px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-card);
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    transition: all 0.15s ease;
    flex: 1;
}

.size-btn:hover {
    border-color: var(--accent);
    color: var(--accent-text);
}

.size-btn--active {
    background: var(--accent);
    color: white;
    border-color: var(--accent);
}

/* ============================================================================
   Compression Bar
   ============================================================================ */

.compression-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 14px;
    margin-bottom: 16px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--bg-card);
    font-size: 13px;
    color: var(--text-muted);
}

.compression-badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 12px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    flex-shrink: 0;
}

.compression-badge--active {
    background: #ff6b6b;
    color: white;
}

.compression-badge--off {
    background: #51cf66;
    color: white;
}

.compression-detail {
    font-size: 12px;
    color: var(--text-muted);
}

.compression-detail strong {
    color: var(--text);
}

/* ============================================================================
   Item styles (inside list)
   ============================================================================ */

.item-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 0 16px;
    height: 100%;
}

.item-color {
    width: 8px;
    height: 28px;
    border-radius: 4px;
    flex-shrink: 0;
}

.item-info {
    display: flex;
    flex-direction: column;
    min-width: 80px;
    flex-shrink: 0;
}

.item-label {
    font-weight: 600;
    font-size: 13px;
    white-space: nowrap;
}

.item-hash {
    font-size: 11px;
    font-family: "SF Mono", Monaco, Menlo, monospace;
    color: var(--text-muted);
}

.item-bar-wrap {
    flex: 1;
    height: 6px;
    background: var(--border);
    border-radius: 3px;
    overflow: hidden;
    min-width: 0;
}

.item-bar {
    height: 100%;
    border-radius: 3px;
    transition: width 0.2s ease;
}

.item-value {
    font-size: 12px;
    font-weight: 600;
    min-width: 36px;
    text-align: right;
    flex-shrink: 0;
    color: var(--text-muted);
}

/* ============================================================================
   Responsive
   ============================================================================ */

@media (max-width: 820px) {
    #list-container {
        height: 400px;
    }

    .size-selector {
        flex-wrap: wrap;
    }

    .size-btn {
        flex: 0 0 auto;
        padding: 6px 12px;
    }

    .compression-bar {
        flex-wrap: wrap;
        gap: 6px;
    }
}