Server showcase
100M-cell demo
Profile the full remote-windowed catalog, playground, custom cells, and exports inside Next.
100M cells1M × 100Server windows
Interactive, production component
tsx
// ===== File: apps/web/app/examples/heavy/heavy-demo.tsx =====
"use client";
import { DataTableDemo } from "../../../../../src/demo/Demo";
import type { DataTableTheme } from "../../../../../src/Datatable/types";
import { useTableTheme } from "../_components/example-preview";
export function HeavyDemo() {
const { mounted, theme } = useTableTheme();
const setTheme = (nextTheme: DataTableTheme) => {
document.documentElement.dataset.dtTheme = nextTheme;
document.documentElement.style.colorScheme = nextTheme;
try {
localStorage.setItem("data-table-pro-theme", nextTheme);
} catch {
// Theme still applies for this session when storage is unavailable.
}
};
if (!mounted) return <div className="heavy-mount-placeholder" />;
return (
<DataTableDemo
theme={theme}
onThemeChange={setTheme}
embedded
hideBrandTitle
/>
);
}
// ===== File: src/demo/Demo.tsx =====
/**
* Demo / POC host for the virtual (windowed) Datatable mode.
* Owns fetch, cache, sort/filter against /api/rows/window + makeRow (demo API).
* Real apps supply their own dataSource or getRow + onSortChange host — this file
* is not required to reuse DataTable.
*/
import { useEffect, useMemo, useState, useCallback, useRef } from "react";
import type { ReactNode } from "react";
import type { z } from "zod";
import { DataTable } from "../Datatable/index";
import type {
ColumnFilter,
ColumnLayoutSettings,
ColumnSetting,
DataTableTheme,
} from "../Datatable/types";
import { isFilterActive } from "../Datatable/filtering/columnFilterTypes";
import { parseNumericValue } from "../Datatable/editing/isNumericCellValue";
import { FlagCell } from "./cells/FlagCell";
import { ScoreBar } from "./cells/ScoreBar";
import { RatingStars } from "./cells/RatingStars";
import {
DescriptionEditor,
DescriptionPreview,
} from "./cells/DescriptionEditor";
import {
countryCodeForRow,
demoScoreForRow,
notesForRow,
descriptionForRow,
} from "./cells/demoRowData";
import {
PlaygroundDrawer,
PlaygroundFab,
type PlaygroundFeatures,
} from "./PlaygroundDrawer";
import {
CoachTour,
clearCoachDismissed,
persistCoachDismissed,
shouldShowCoachOnBoot,
} from "./CoachTour";
import "./demo.css";
type Row = Record<string, string | number | boolean | object>;
const COLS = 100;
const SHEET_LIMIT = 300;
const CACHE_CAP = 10_000;
const MAX_CELLS_PER_REQ = 250_000;
const FETCH_OVERSCAN = 120;
const RANGE_DEBOUNCE_MS = 60;
const PERSIST_FLAG_KEY = "dt-demo-persist-cols";
const PERSIST_LAYOUT_KEY = "dt-demo-column-layout";
const XLSX_ADAPTER = {
async download(...args: Parameters<import("../Datatable/types").XlsxAdapter["download"]>) {
const { createXlsxAdapter } = await import("../Datatable/xlsx");
return createXlsxAdapter().download(...args);
},
async parse(file: File) {
const { createXlsxAdapter } = await import("../Datatable/xlsx");
return createXlsxAdapter().parse!(file);
},
} satisfies import("../Datatable/types").XlsxAdapter;
const DATASET_OPTIONS = [
{ label: "1k", value: 1_000 },
{ label: "10k", value: 10_000 },
{ label: "100k", value: 100_000 },
{ label: "1M rows · 100M cells", value: 1_000_000 },
] as const;
function readPersistFlag(): boolean {
try {
return localStorage.getItem(PERSIST_FLAG_KEY) === "1";
} catch {
return false;
}
}
function writePersistFlag(on: boolean) {
try {
if (on) localStorage.setItem(PERSIST_FLAG_KEY, "1");
else localStorage.removeItem(PERSIST_FLAG_KEY);
} catch {
/* ignore */
}
}
function readPersistedLayout(): ColumnLayoutSettings | undefined {
try {
const raw = localStorage.getItem(PERSIST_LAYOUT_KEY);
if (!raw) return undefined;
const parsed = JSON.parse(raw) as ColumnLayoutSettings;
if (!parsed || !Array.isArray(parsed.order)) return undefined;
return parsed;
} catch {
return undefined;
}
}
function writePersistedLayout(settings: ColumnLayoutSettings) {
try {
localStorage.setItem(PERSIST_LAYOUT_KEY, JSON.stringify(settings));
} catch {
/* ignore */
}
}
function clearPersistedLayout() {
try {
localStorage.removeItem(PERSIST_LAYOUT_KEY);
} catch {
/* ignore */
}
}
function makeDemoColumns(): ColumnSetting<Row>[] {
const categories = [
{ value: "audio", label: "Audio" },
{ value: "compute", label: "Compute" },
{ value: "optics", label: "Optics" },
{ value: "wearables", label: "Wearables" },
{ value: "power", label: "Power" },
{ value: "accessories", label: "Accessories" },
];
return [
{
id: "flag",
title: "Origin",
groupTitle: "Catalog",
width: 110,
minWidth: 80,
maxWidth: 160,
pinned: "left",
enableSorting: false,
enableReordering: false,
accessor: (row) => countryCodeForRow(Number(row.__i)),
exportValue: (row) => countryCodeForRow(Number(row.__i)).toUpperCase(),
cell: ({ row }) => (
<FlagCell countryCode={countryCodeForRow(Number(row.__i))} />
),
},
{
id: "sku",
title: "SKU",
groupTitle: "Catalog",
width: 120,
minWidth: 96,
maxWidth: 160,
pinned: "left",
accessor: (row) =>
typeof row.sku === "string" ? row.sku : `SKU-${Number(row.__i)}`,
editor: "text",
validation: (zod: typeof z) => zod.string().min(3, "Min 3 characters"),
deferCellRender: false,
},
{
id: "product",
title: "Product",
groupTitle: "Catalog",
width: 150,
minWidth: 110,
maxWidth: 220,
accessor: (row) =>
typeof row.product === "string"
? row.product
: `Product ${Number(row.__i)}`,
editor: "text",
validation: (zod: typeof z) => zod.string().min(2),
deferCellRender: false,
},
{
id: "stock",
title: "Stock",
groupTitle: "Metrics",
width: 120,
minWidth: 96,
maxWidth: 160,
align: "right",
filterType: "number",
accessor: (row) => {
// Prefer real stock; do not invent a score when stock was imported blank/invalid.
if (!("stock" in row) || row.stock === undefined) {
if (typeof row.score === "number") return row.score;
return demoScoreForRow(Number(row.__i));
}
if (typeof row.stock === "number") return row.stock;
return parseNumericValue(row.stock) ?? null;
},
exportValue: (row) => {
if (typeof row.stock === "number") return row.stock;
return parseNumericValue(row.stock) ?? "";
},
editor: { type: "number", min: 0, max: 100, step: 1 },
validation: (zod: typeof z) =>
zod.coerce
.number({ invalid_type_error: "Must be a number" })
.min(0)
.max(100),
cell: ({ value }) => (
<ScoreBar
value={typeof value === "number" ? value : 0}
max={100}
/>
),
},
{
id: "rating",
title: "Rating",
groupTitle: "Metrics",
width: 108,
minWidth: 88,
maxWidth: 140,
align: "center",
filterType: "number",
accessor: (row) => {
if (typeof row.rating === "number") return row.rating;
return (((Number(row.__i) * 1103515245 + 999) >>> 0) % 5) + 1;
},
exportValue: (row) => {
if (typeof row.rating === "number") return row.rating;
const n = parseNumericValue(row.rating);
if (n !== undefined) return n;
return (((Number(row.__i) * 1103515245 + 999) >>> 0) % 5) + 1;
},
// Live stars — no editor; click to set
deferCellRender: false,
cell: ({ value, commitValue }) => (
<RatingStars
value={typeof value === "number" ? value : Number(value) || 0}
onChange={(n) => commitValue?.(n)}
/>
),
},
{
id: "live",
title: "Live",
groupTitle: "Metrics",
width: 72,
minWidth: 56,
maxWidth: 96,
align: "center",
enableSorting: false,
liveToggle: true,
accessor: (row) => {
const v = row.live;
if (typeof v === "boolean") return v;
if (v === "true" || v === 1 || v === "1") return true;
if (v === "false" || v === 0 || v === "0") return false;
return Number(row.__i) % 3 !== 0;
},
exportValue: (row) => {
const v = row.live;
if (typeof v === "boolean") return v ? "true" : "false";
if (v === "true" || v === 1 || v === "1") return "true";
if (v === "false" || v === 0 || v === "0") return "false";
return Number(row.__i) % 3 !== 0 ? "true" : "false";
},
// No editor — live checkbox in cell (commitValue + Enter/Space)
deferCellRender: false,
cell: ({ value, commitValue }) => {
const checked =
value === true || value === "true" || value === 1 || value === "1";
return (
<label className="dt-demo-live-check">
<input
type="checkbox"
checked={checked}
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onChange={(e) => commitValue?.(e.target.checked)}
aria-label="Listing live"
/>
</label>
);
},
},
{
id: "category",
title: "Category",
groupTitle: "Listing",
width: 120,
minWidth: 96,
maxWidth: 160,
accessor: (row) => {
if (typeof row.category === "string") return row.category;
const legacy = row.status;
if (typeof legacy === "string") return legacy;
return categories[Number(row.__i) % categories.length]!.value;
},
editor: { type: "select", options: categories },
validation: (zod: typeof z) =>
zod.enum([
"audio",
"compute",
"optics",
"wearables",
"power",
"accessories",
]),
exportValue: (row) => {
const raw =
typeof row.category === "string"
? row.category
: typeof row.status === "string"
? row.status
: categories[Number(row.__i) % categories.length]!.value;
return categories.find((c) => c.value === raw)?.label ?? raw;
},
cell: ({ value }) => {
const raw = typeof value === "string" ? value : "";
const label =
categories.find((c) => c.value === raw)?.label ??
(raw ? raw : "—");
return <span className="dt-demo-category">{label}</span>;
},
},
{
id: "price",
title: "Price",
groupTitle: "Listing",
width: 100,
minWidth: 80,
maxWidth: 140,
align: "right",
filterType: "number",
accessor: (row) => {
if (typeof row.price === "number") return row.price;
return Math.round((((Number(row.__i) * 2654435761) >>> 0) % 90000) + 1990) / 100;
},
editor: { type: "number", min: 0, max: 9999, step: 0.01 },
validation: (zod: typeof z) =>
zod.coerce.number({ invalid_type_error: "Must be a number" }).min(0),
exportValue: (row) => {
if (typeof row.price === "number") return row.price;
const n = parseNumericValue(row.price);
if (n !== undefined) return n;
return Math.round((((Number(row.__i) * 2654435761) >>> 0) % 90000) + 1990) / 100;
},
cell: ({ value }) => {
const n = typeof value === "number" ? value : Number(value);
return (
<span className="dt-demo-price">
{Number.isFinite(n) ? `$${n.toFixed(2)}` : "—"}
</span>
);
},
},
{
id: "launch",
title: "Launch",
groupTitle: "Listing",
width: 128,
minWidth: 100,
maxWidth: 160,
accessor: (row) => {
if (typeof row.launch === "string" && row.launch) return row.launch;
if (typeof row.dueDate === "string" && row.dueDate) return row.dueDate;
const i = Number(row.__i);
const month = String((i % 12) + 1).padStart(2, "0");
const day = String((i % 28) + 1).padStart(2, "0");
return `2026-${month}-${day}`;
},
editor: "date",
validation: (zod: typeof z) => zod.string().min(1, "Required"),
cell: ({ value }) => (
<span className="dt-demo-date">
{typeof value === "string" ? value : String(value ?? "")}
</span>
),
},
{
id: "priority",
title: "Priority",
groupTitle: "Listing",
width: 140,
minWidth: 110,
maxWidth: 180,
align: "center",
filterType: "number",
accessor: (row) => {
if (typeof row.priority === "number") return row.priority;
return (Number(row.__i) * 17) % 101;
},
editor: { type: "range", min: 0, max: 100, step: 1 },
validation: (zod: typeof z) => zod.coerce.number().min(0).max(100),
cell: ({ value }) => {
const n = typeof value === "number" ? value : Number(value) || 0;
return <span className="dt-demo-price">{n}</span>;
},
},
{
id: "featured",
title: "Featured",
groupTitle: "Editors",
width: 88,
minWidth: 72,
maxWidth: 120,
align: "center",
accessor: (row) => {
const v = row.featured;
if (typeof v === "boolean") return v;
if (v === "true" || v === 1 || v === "1") return true;
return Number(row.__i) % 5 === 0;
},
editor: "checkbox",
validation: (zod: typeof z) => zod.boolean(),
cell: ({ value }) => (value ? "✓" : "—"),
},
{
id: "condition",
title: "Condition",
groupTitle: "Editors",
width: 168,
minWidth: 120,
maxWidth: 220,
accessor: (row) => {
if (typeof row.condition === "string") return row.condition;
const opts = ["new", "refurbished", "used"] as const;
return opts[Number(row.__i) % 3]!;
},
editor: {
type: "radio",
options: [
{ value: "new", label: "New" },
{ value: "refurbished", label: "Refurb" },
{ value: "used", label: "Used" },
],
},
validation: (zod: typeof z) => zod.enum(["new", "refurbished", "used"]),
cell: ({ value }) => {
const raw = typeof value === "string" ? value : "";
const label =
raw === "new"
? "New"
: raw === "refurbished"
? "Refurbished"
: raw === "used"
? "Used"
: "—";
return <span className="dt-demo-category">{label}</span>;
},
},
{
id: "contact",
title: "Contact",
groupTitle: "Editors",
width: 180,
minWidth: 140,
maxWidth: 260,
accessor: (row) =>
typeof row.contact === "string"
? row.contact
: `seller${Number(row.__i) % 9000}@catalog.demo`,
editor: "email",
validation: (zod: typeof z) => zod.string().email("Invalid email"),
deferCellRender: false,
},
{
id: "packTime",
title: "Pack time",
groupTitle: "Editors",
width: 110,
minWidth: 88,
maxWidth: 140,
accessor: (row) => {
if (typeof row.packTime === "string" && row.packTime) return row.packTime;
const h = String((Number(row.__i) % 12) + 8).padStart(2, "0");
const m = String((Number(row.__i) * 7) % 60).padStart(2, "0");
return `${h}:${m}`;
},
editor: "time",
validation: (zod: typeof z) => zod.string().min(4, "Required"),
cell: ({ value }) => (
<span className="dt-demo-date">
{typeof value === "string" ? value : String(value ?? "")}
</span>
),
},
{
id: "notes",
title: "Notes",
groupTitle: "Copy",
width: 180,
minWidth: 120,
maxWidth: 280,
accessor: (row) => {
if (typeof row.notes === "string" && row.notes.trim()) return row.notes;
return notesForRow(Number(row.__i));
},
editor: "textarea",
editRowHeight: 96,
validation: (zod: typeof z) => zod.string(),
cell: ({ value }) => (
<span className="dt-demo-desc-preview" title={String(value ?? "")}>
{typeof value === "string" && value ? value : "—"}
</span>
),
},
{
id: "description",
title: "Description",
groupTitle: "Copy",
width: 220,
minWidth: 160,
maxWidth: 360,
accessor: (row) => {
if (typeof row.description === "string" && row.description.trim()) {
return row.description;
}
return descriptionForRow(Number(row.__i));
},
editor: "textarea",
editRowHeight: 140,
editCell: ({ value, onCommit, onCancel, invalid, onRevealValidation }) => (
<DescriptionEditor
value={value}
invalid={invalid}
onCommit={onCommit}
onCancel={onCancel}
onRevealValidation={onRevealValidation}
/>
),
validation: (zod: typeof z) => zod.string().min(8, "Min 8 characters"),
cell: ({ value }) => <DescriptionPreview value={value} />,
},
];
}
function makeColumns(): ColumnSetting<Row>[] {
return [...makeDemoColumns(), ...makeDataColumns()];
}
function makeDataColumns(): ColumnSetting<Row>[] {
const cols: ColumnSetting<Row>[] = [];
for (let c = 0; c < COLS; c++) {
const width = c % 5 === 0 ? 140 : c % 3 === 0 ? 120 : 100;
const titles = [
"Warehouse",
"Material",
"Units",
"Restock",
"City",
"Attr code",
];
cols.push({
column: `c${c}`,
id: `c${c}`,
title: `${titles[c % 6]} ${c}`,
groupTitle: c < 6 ? "Attributes" : "Extended attrs",
width,
minWidth: 64,
maxWidth: 320,
...(c % 6 === 2 ? { filterType: "number" as const } : null),
...(c === 3 ? { enableResizing: false } : null),
...(c === 4 ? { enableReordering: false } : null),
});
}
return cols;
}
type CacheEntry = { row: Row; touched: number };
type ViewKey = {
sortCol: string | null;
sortDesc: boolean;
query: string;
filters: ColumnFilter[];
};
function viewKeyOf(v: ViewKey) {
return `${v.sortCol ?? ""}:${v.sortDesc ? 1 : 0}:${v.query.trim()}:${JSON.stringify(v.filters)}`;
}
function activeFiltersForRequest(
filters: ColumnFilter[],
columns: ColumnSetting<Row>[]
): ColumnFilter[] {
const byId = new Map(columns.map((c) => [c.id, c]));
return filters.filter((f) =>
isFilterActive(f, byId.get(f.id)?.filterType ?? "text")
);
}
export type DataTableDemoProps = {
theme?: DataTableTheme;
onThemeChange?: (theme: DataTableTheme) => void;
embedded?: boolean;
/** Optional leading control beside the demo title (e.g. docs back link). */
headerLeading?: ReactNode;
/** Hide the internal “VB Data Table” brand title when an outer page shell owns naming. */
hideBrandTitle?: boolean;
};
export function DataTableDemo({
theme: controlledTheme,
onThemeChange,
embedded = false,
headerLeading,
hideBrandTitle = false,
}: DataTableDemoProps = {}) {
const columns = useMemo(() => makeColumns(), []);
const cacheRef = useRef(new Map<number, CacheEntry>());
const touchRef = useRef(0);
const inFlightRef = useRef(new Map<string, Promise<void>>());
const rangeTimerRef = useRef<number | null>(null);
const acRef = useRef<AbortController | null>(null);
const fetchGenRef = useRef(0);
const viewportRef = useRef({ start: 0, end: 40 });
const [dataVersion, setDataVersion] = useState(0);
const [demoMode, setDemoMode] = useState<"windowed" | "sheet">("windowed");
const [datasetSize, setDatasetSize] = useState(1_000_000);
const [viewTotal, setViewTotal] = useState(1_000_000);
const [error, setError] = useState<string | null>(null);
/** Hard block only for initial boot */
const [bootLoading, setBootLoading] = useState(true);
/** Soft refresh while sorting/filtering — keep UI interactive */
const [refreshing, setRefreshing] = useState(false);
const [cachedCount, setCachedCount] = useState(0);
const datasetSizeRef = useRef(datasetSize);
datasetSizeRef.current = datasetSize;
const [sortCol, setSortCol] = useState<string | null>(null);
const [sortDesc, setSortDesc] = useState(false);
const [query, setQuery] = useState("");
const [columnFilters, setColumnFilters] = useState<ColumnFilter[]>([]);
const [enablePagination, setEnablePagination] = useState(false);
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
const [enableRowSelection, setEnableRowSelection] = useState(true);
const [enableMultiRowSelection, setEnableMultiRowSelection] = useState(true);
const [playgroundOpen, setPlaygroundOpen] = useState(false);
const [features, setFeatures] = useState<PlaygroundFeatures>({
enableEditing: true,
enableColumnSettings: true,
enableColumnResizing: true,
enableColumnReorder: true,
enableRowIndex: true,
});
const [rowHeight, setRowHeight] = useState(28);
const [undoLimit, setUndoLimit] = useState(20);
const [sheetRows, setSheetRows] = useState<Row[]>([]);
const sheetRowsRef = useRef(sheetRows);
sheetRowsRef.current = sheetRows;
const [uploadedRows, setUploadedRows] = useState<Row[]>([]);
const uploadedRowsRef = useRef(uploadedRows);
uploadedRowsRef.current = uploadedRows;
const [persistColumnSettings, setPersistColumnSettings] = useState(readPersistFlag);
const [seedLayout, setSeedLayout] = useState<ColumnLayoutSettings | undefined>(() =>
readPersistFlag() ? readPersistedLayout() : undefined
);
const [fabResetToken, setFabResetToken] = useState(0);
const lastLayoutRef = useRef<ColumnLayoutSettings | null>(seedLayout ?? null);
const [coachActive, setCoachActive] = useState(false);
const [internalTheme, setInternalTheme] = useState<DataTableTheme>("light");
const theme = controlledTheme ?? internalTheme;
const setTheme = useCallback((next: DataTableTheme) => {
if (controlledTheme === undefined) setInternalTheme(next);
onThemeChange?.(next);
}, [controlledTheme, onThemeChange]);
const tableSlotRef = useRef<HTMLDivElement | null>(null);
const [tableMaxHeight, setTableMaxHeight] = useState(480);
useEffect(() => {
if (embedded) return;
const root = document.documentElement;
root.setAttribute("data-dt-theme", theme);
root.style.colorScheme = theme;
return () => {
root.removeAttribute("data-dt-theme");
root.style.colorScheme = "";
};
}, [embedded, theme]);
useEffect(() => {
const el = tableSlotRef.current;
if (!el) return;
const ro = new ResizeObserver(() => {
const next = Math.floor(el.clientHeight);
if (next > 0) setTableMaxHeight((prev) => (prev === next ? prev : next));
});
ro.observe(el);
const initial = Math.floor(el.clientHeight);
if (initial > 0) setTableMaxHeight(initial);
return () => ro.disconnect();
}, []);
useEffect(() => {
if (bootLoading) return;
if (!shouldShowCoachOnBoot()) return;
const t = window.setTimeout(() => setCoachActive(true), 450);
return () => window.clearTimeout(t);
}, [bootLoading]);
const loadSheetRows = useCallback(async (signal?: AbortSignal) => {
const params = new URLSearchParams({
start: "0",
limit: String(SHEET_LIMIT),
cols: String(COLS),
total: String(datasetSizeRef.current),
deep: "0",
});
const res = await fetch(`/api/rows/window?${params}`, { signal });
if (!res.ok) throw new Error(`API ${res.status}`);
const data = await res.json();
setSheetRows((data.rows ?? []) as Row[]);
}, []);
const windowSize = useMemo(
() => Math.min(5_000, Math.max(200, Math.floor(MAX_CELLS_PER_REQ / COLS))),
[]
);
const activeViewRef = useRef<ViewKey>({
sortCol: null,
sortDesc: false,
query: "",
filters: [],
});
activeViewRef.current = { sortCol, sortDesc, query, filters: columnFilters };
const evictIfNeeded = useCallback(() => {
const cache = cacheRef.current;
if (cache.size <= CACHE_CAP) return;
const entries = Array.from(cache.entries()).sort((a, b) => a[1].touched - b[1].touched);
const drop = cache.size - CACHE_CAP;
for (let i = 0; i < drop; i++) cache.delete(entries[i][0]);
}, []);
const fetchWindowInto = useCallback(
async (
start: number,
limit: number,
gen: number,
signal: AbortSignal,
view: ViewKey,
target: Map<number, CacheEntry>
) => {
const key = `${viewKeyOf(view)}:${start}:${limit}`;
const existing = inFlightRef.current.get(key);
if (existing) {
await existing;
return;
}
const run = (async () => {
const params = new URLSearchParams({
start: String(start),
limit: String(limit),
cols: String(COLS),
total: String(datasetSizeRef.current),
deep: "0",
});
if (view.sortCol) {
params.set("sortCol", view.sortCol);
params.set("sortDesc", view.sortDesc ? "1" : "0");
}
if (view.query.trim()) params.set("q", view.query.trim());
const active = activeFiltersForRequest(view.filters, columns);
if (active.length) params.set("filters", JSON.stringify(active));
const res = await fetch(`/api/rows/window?${params}`, { signal });
if (!res.ok) throw new Error(`API ${res.status}`);
const data = await res.json();
if (gen !== fetchGenRef.current) return;
const rows: Row[] = data.rows ?? [];
const base = data.start ?? start;
if (typeof data.total === "number") setViewTotal(data.total);
for (let i = 0; i < rows.length; i++) {
target.set(base + i, { row: rows[i], touched: ++touchRef.current });
}
})();
inFlightRef.current.set(key, run);
try {
await run;
} finally {
inFlightRef.current.delete(key);
}
},
[columns]
);
const ensureRange = useCallback(
async (visStart: number, visEnd: number) => {
viewportRef.current = { start: visStart, end: visEnd };
const gen = fetchGenRef.current;
const ac = acRef.current;
if (!ac || ac.signal.aborted) return;
const uploadOffset = uploadedRowsRef.current.length;
const view = activeViewRef.current;
const start = Math.max(0, visStart - uploadOffset - FETCH_OVERSCAN);
const end = Math.min(viewTotal, visEnd - uploadOffset + FETCH_OVERSCAN);
if (start >= end) return;
const missing: Array<{ s: number; e: number }> = [];
let gapStart: number | null = null;
for (let i = start; i < end; i++) {
if (!cacheRef.current.has(i)) {
if (gapStart == null) gapStart = i;
} else if (gapStart != null) {
missing.push({ s: gapStart, e: i });
gapStart = null;
}
}
if (gapStart != null) missing.push({ s: gapStart, e: end });
if (!missing.length) return;
setRefreshing(true);
try {
for (const gap of missing) {
for (let s = gap.s; s < gap.e; s += windowSize) {
if (gen !== fetchGenRef.current) return;
const lim = Math.min(windowSize, gap.e - s);
await fetchWindowInto(s, lim, gen, ac.signal, view, cacheRef.current);
}
}
evictIfNeeded();
setCachedCount(cacheRef.current.size);
setDataVersion((v) => v + 1);
setError(null);
} catch (e: unknown) {
if ((e as { name?: string })?.name !== "AbortError") {
console.error(e);
setError(e instanceof Error ? e.message : "Failed to load rows");
}
} finally {
if (gen === fetchGenRef.current) setRefreshing(false);
}
},
[viewTotal, windowSize, fetchWindowInto, evictIfNeeded]
);
/** Atomic view switch: optimistic sort/filter UI, swap cache when window ready. */
const switchView = useCallback(
async (next: ViewKey) => {
fetchGenRef.current += 1;
acRef.current?.abort();
acRef.current = new AbortController();
const gen = fetchGenRef.current;
const ac = acRef.current;
// Optimistic header/meta — don't wait for network
setSortCol(next.sortCol);
setSortDesc(next.sortDesc);
setQuery(next.query);
setColumnFilters(next.filters);
setRefreshing(true);
setError(null);
const uploadOffset = uploadedRowsRef.current.length;
const vp = viewportRef.current;
const start = Math.max(0, vp.start - uploadOffset - FETCH_OVERSCAN);
const end = Math.max(
start + windowSize,
vp.end - uploadOffset + FETCH_OVERSCAN
);
const limit = Math.min(windowSize, Math.max(end - start, windowSize));
const nextCache = new Map<number, CacheEntry>();
try {
await fetchWindowInto(start, limit, gen, ac.signal, next, nextCache);
if (gen !== fetchGenRef.current) return;
cacheRef.current = nextCache;
inFlightRef.current.clear();
setCachedCount(nextCache.size);
setDataVersion((v) => v + 1);
} catch (e: unknown) {
if ((e as { name?: string })?.name !== "AbortError") {
console.error(e);
setError(
e instanceof Error
? e.message
: "Failed to reach API — run npm run dev:all"
);
}
} finally {
if (gen === fetchGenRef.current) setRefreshing(false);
}
},
[windowSize, fetchWindowInto]
);
// Load the genuine remote window or the bounded local editing sheet.
useEffect(() => {
fetchGenRef.current += 1;
acRef.current?.abort();
acRef.current = new AbortController();
const gen = fetchGenRef.current;
const ac = acRef.current;
setBootLoading(true);
setError(null);
setViewTotal(demoMode === "windowed" ? datasetSize : Math.min(SHEET_LIMIT, datasetSize));
setPagination((p) => ({ ...p, pageIndex: 0 }));
(async () => {
try {
if (demoMode === "windowed") {
cacheRef.current = new Map();
const firstWindow = new Map<number, CacheEntry>();
await fetchWindowInto(
0,
windowSize,
gen,
ac.signal,
{ sortCol: null, sortDesc: false, query: "", filters: [] },
firstWindow
);
if (gen !== fetchGenRef.current) return;
cacheRef.current = firstWindow;
setCachedCount(firstWindow.size);
setDataVersion((version) => version + 1);
} else {
await loadSheetRows(ac.signal);
}
if (gen !== fetchGenRef.current) return;
} catch (e: unknown) {
if ((e as { name?: string })?.name !== "AbortError") {
console.error(e);
setError(
e instanceof Error
? e.message
: "Failed to reach API — run npm run dev:all"
);
}
} finally {
if (gen === fetchGenRef.current) setBootLoading(false);
}
})();
return () => {
ac.abort();
};
}, [datasetSize, demoMode, loadSheetRows, fetchWindowInto, windowSize]);
const changeDatasetSize = useCallback((size: number) => {
if (size === datasetSizeRef.current) return;
setDatasetSize(size);
}, []);
const changeDemoMode = useCallback((mode: "windowed" | "sheet") => {
setDemoMode((current) => {
if (current === mode) return current;
if (mode === "sheet") {
setEnableRowSelection(true);
setEnableMultiRowSelection(true);
setFeatures((currentFeatures) => ({
...currentFeatures,
enableEditing: true,
enableRowIndex: true,
}));
}
return mode;
});
}, []);
const getRow = useCallback(
(index: number): Row | undefined => {
const uploaded = uploadedRowsRef.current;
if (index < uploaded.length) return uploaded[index];
const hit = cacheRef.current.get(index - uploaded.length);
if (!hit) return undefined;
hit.touched = ++touchRef.current;
return hit.row;
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[dataVersion]
);
/** Apply paste / cell edits into the client window cache and bump dataVersion. */
const onCellsEdit = useCallback(
(edits: Array<{ rowIndex: number; patch: Partial<Row> }>) => {
if (!edits.length) return;
const uploadedCount = uploadedRowsRef.current.length;
const uploadedEdits = edits.filter((edit) => edit.rowIndex < uploadedCount);
if (uploadedEdits.length) {
setUploadedRows((current) => {
let changed = false;
const next = current.slice();
for (const { rowIndex, patch } of uploadedEdits) {
const currentRow = next[rowIndex];
if (!currentRow) continue;
next[rowIndex] = { ...currentRow, ...patch } as Row;
changed = true;
}
if (changed) uploadedRowsRef.current = next;
return changed ? next : current;
});
}
const cache = cacheRef.current;
let changed = false;
for (const { rowIndex, patch } of edits) {
if (rowIndex < uploadedCount) continue;
const remoteIndex = rowIndex - uploadedCount;
// Prefer __i match: selection uses stable row id; cache is keyed by visual slot
let entry: CacheEntry | undefined;
for (const e of cache.values()) {
if ((e.row as { __i?: number }).__i === remoteIndex) {
entry = e;
break;
}
}
if (!entry) entry = cache.get(remoteIndex);
if (entry) {
entry.row = { ...entry.row, ...patch } as Row;
entry.touched = ++touchRef.current;
changed = true;
} else {
cache.set(remoteIndex, {
row: { __i: remoteIndex, ...patch } as Row,
touched: ++touchRef.current,
});
changed = true;
}
}
if (!changed) return;
setCachedCount(cache.size);
// Do not bump dataVersion — that remounts every cell via itemKey.
// Table bumps per-cell versions + editRevision after onCellsEdit.
},
[]
);
const handleUploadRows = useCallback((rows: Row[]) => {
if (!rows.length) return;
setUploadedRows((current) => {
const next = [
...rows.map((row, index) => ({ ...row, __i: index })),
...current.map((row, index) => ({
...row,
__i: rows.length + index,
})),
];
uploadedRowsRef.current = next;
return next;
});
setDataVersion((version) => version + 1);
}, []);
const onVisibleRangeChange = useCallback(
(start: number, end: number) => {
if (rangeTimerRef.current != null) window.clearTimeout(rangeTimerRef.current);
rangeTimerRef.current = window.setTimeout(() => {
rangeTimerRef.current = null;
void ensureRange(start, end);
}, RANGE_DEBOUNCE_MS);
},
[ensureRange]
);
const onSortChange = useCallback(
(next: { id: string | null; desc: boolean }) => {
void switchView({
sortCol: next.id,
sortDesc: !!next.desc,
query,
filters: columnFilters,
});
},
[switchView, query, columnFilters]
);
const onFilterChange = useCallback(
(q: string) => {
void switchView({
sortCol,
sortDesc,
query: q,
filters: columnFilters,
});
},
[switchView, sortCol, sortDesc, columnFilters]
);
const onColumnFiltersChange = useCallback(
(filters: ColumnFilter[]) => {
void switchView({
sortCol,
sortDesc,
query,
filters,
});
},
[switchView, sortCol, sortDesc, query]
);
const statusLabel = error
? `Error: ${error}`
: bootLoading
? `Loading ${demoMode === "windowed" ? "remote window" : "sheet"}…`
: demoMode === "windowed"
? `Windowed · ${(viewTotal + uploadedRows.length).toLocaleString()} rows × ${COLS} columns = ${((viewTotal + uploadedRows.length) * COLS).toLocaleString()} addressable cells · ${cachedCount.toLocaleString()} cached locally${uploadedRows.length ? ` · ${uploadedRows.length.toLocaleString()} uploaded` : ""}`
: `Editable sheet · ${sheetRows.length.toLocaleString()} rows · undo / download / upload / right-click rows`;
const createEmptySheetRow = useCallback(
(index: number): Row => {
const template = sheetRows[0];
if (!template) return { __i: index };
const empty: Row = { ...template, __i: index };
for (const key of Object.keys(empty)) {
if (key === "__i") continue;
empty[key] = "";
}
return empty;
},
[sheetRows]
);
const getRowsForExport = useCallback(
async (ctx: import("../Datatable/types").ExportContext) => {
if (demoMode === "sheet") {
const rows = sheetRowsRef.current;
return ctx.scope === "selected"
? ctx.selectedBaseIndexes
.map((index) => rows[index])
.filter((row): row is Row => row != null)
: rows;
}
if (ctx.scope === "selected") {
return ctx.selectedBaseIndexes
.map((index) => {
const uploaded = uploadedRowsRef.current;
if (index < uploaded.length) return uploaded[index];
return cacheRef.current.get(index - uploaded.length)?.row;
})
.filter((row): row is Row => row != null);
}
const total = datasetSizeRef.current;
const page = Math.max(200, windowSize);
const view = activeViewRef.current;
return (async function* exportRemoteRows() {
const uploaded = uploadedRowsRef.current;
if (uploaded.length) yield uploaded;
for (let start = 0; start < total; start += page) {
if (ctx.signal.aborted) {
throw new DOMException("Export cancelled", "AbortError");
}
const limit = Math.min(page, total - start);
const params = new URLSearchParams({
start: String(start),
limit: String(limit),
cols: String(COLS),
total: String(total),
deep: "0",
});
if (view.sortCol) {
params.set("sortCol", view.sortCol);
params.set("sortDesc", view.sortDesc ? "1" : "0");
}
if (view.query.trim()) params.set("q", view.query.trim());
const active = activeFiltersForRequest(view.filters, columns);
if (active.length) params.set("filters", JSON.stringify(active));
const response = await fetch(`/api/rows/window?${params}`, {
signal: ctx.signal,
});
if (!response.ok) {
throw new Error(`Export fetch failed (${response.status})`);
}
const data = await response.json();
yield (data.rows ?? []) as Row[];
}
})();
},
[demoMode, windowSize, columns]
);
const busy = bootLoading || refreshing;
const tableLoading = bootLoading;
const finishCoach = useCallback(() => {
persistCoachDismissed();
setCoachActive(false);
}, []);
const replayTips = useCallback(() => {
clearCoachDismissed();
setPlaygroundOpen(false);
setCoachActive(true);
}, []);
const handleColumnSettingsChange = useCallback(
(settings: ColumnLayoutSettings) => {
lastLayoutRef.current = settings;
setSeedLayout(settings);
if (persistColumnSettings) {
writePersistedLayout(settings);
}
},
[persistColumnSettings]
);
const handlePersistToggle = useCallback((on: boolean) => {
setPersistColumnSettings(on);
writePersistFlag(on);
if (!on) {
clearPersistedLayout();
return;
}
const snap = lastLayoutRef.current;
if (snap) {
writePersistedLayout(snap);
}
}, []);
return (
<div className={`dt-demo${embedded ? " is-embedded" : ""}`} data-dt-theme={theme} style={{ colorScheme: theme }}>
<header className="dt-demo__header">
<div className="dt-demo__title-row">
<div className="dt-demo__title-cluster">
{headerLeading ? (
<div className="dt-demo__header-leading">{headerLeading}</div>
) : null}
<div>
{hideBrandTitle ? null : (
<h1 className="dt-demo__title">VB Data Table</h1>
)}
<span className={`dt-demo__status${error ? " is-error" : ""}`}>{statusLabel}</span>
</div>
</div>
<div className="dt-demo__header-actions">
<button type="button" className="dt-demo__tips-button" onClick={replayTips}>
Tips
</button>
</div>
</div>
</header>
<div ref={tableSlotRef} className="dt-demo__table-slot">
<DataTable<Row>
className="dt-table"
theme={theme}
title={demoMode === "windowed" ? "Remote inventory" : "Editable orders"}
loading={tableLoading}
dataSource={demoMode === "sheet" ? sheetRows : undefined}
onChange={demoMode === "sheet" ? setSheetRows : undefined}
rowCount={demoMode === "windowed" ? viewTotal + uploadedRows.length : undefined}
getRow={demoMode === "windowed" ? getRow : undefined}
dataVersion={dataVersion}
onVisibleRangeChange={demoMode === "windowed" ? onVisibleRangeChange : undefined}
onSortChange={demoMode === "windowed" ? onSortChange : undefined}
onFilterChange={demoMode === "windowed" ? onFilterChange : undefined}
onColumnFiltersChange={demoMode === "windowed" ? onColumnFiltersChange : undefined}
externalSort={demoMode === "windowed" ? { id: sortCol, desc: sortDesc } : undefined}
externalFilter={demoMode === "windowed" ? query : undefined}
externalColumnFilters={demoMode === "windowed" ? columnFilters : undefined}
onCellsEdit={demoMode === "windowed" ? onCellsEdit : undefined}
onUploadRows={demoMode === "windowed" ? handleUploadRows : undefined}
createEmptyRow={demoMode === "sheet" ? createEmptySheetRow : undefined}
enableDownload
enableUpload
defaultFileName="data"
defaultExportFormat="csv"
exportRowCount={demoMode === "windowed" ? viewTotal + uploadedRows.length : sheetRows.length}
exportConfirmThreshold={100_000}
getRowsForExport={getRowsForExport}
xlsxAdapter={XLSX_ADAPTER}
enableRowActions={demoMode === "sheet"}
columnSettings={columns}
defaultColumnLayoutSettings={seedLayout}
enableColumnResizing={features.enableColumnResizing}
enableColumnReorder={features.enableColumnReorder}
enableColumnSettings={features.enableColumnSettings}
enableEditing={features.enableEditing}
enableFindReplace={demoMode === "sheet"}
enableGoToCell
enableRowIndex={features.enableRowIndex}
onColumnSettingsChange={handleColumnSettingsChange}
maxHeight={tableMaxHeight}
rowHeight={rowHeight}
headerHeight={32}
enablePagination={enablePagination}
pagination={pagination}
onPaginationChange={setPagination}
onPageIndexChange={(pageIndex) => setPagination((p) => ({ ...p, pageIndex }))}
onPageSizeChange={(pageSize) => setPagination({ pageIndex: 0, pageSize })}
enableRowSelection={enableRowSelection}
enableMultiRowSelection={enableMultiRowSelection}
selectionDisabled={busy}
undoLimit={demoMode === "sheet" ? undoLimit : 0}
redoLimit={demoMode === "sheet" ? undoLimit : 0}
/>
</div>
<PlaygroundFab
pulse={coachActive}
panelOpen={playgroundOpen}
resetToken={fabResetToken}
onClick={() => {
if (coachActive) finishCoach();
setPlaygroundOpen(true);
}}
/>
<PlaygroundDrawer
open={playgroundOpen}
onClose={() => setPlaygroundOpen(false)}
mode={demoMode}
onModeChange={changeDemoMode}
busy={busy}
datasetSize={datasetSize}
datasetOptions={DATASET_OPTIONS}
onDatasetSizeChange={changeDatasetSize}
enablePagination={enablePagination}
onEnablePaginationChange={setEnablePagination}
theme={theme}
onThemeChange={setTheme}
enableRowSelection={enableRowSelection}
enableMultiRowSelection={enableMultiRowSelection}
onSelectionModeChange={(mode) => {
if (mode === "off") {
setEnableRowSelection(false);
setEnableMultiRowSelection(false);
} else if (mode === "single") {
setEnableRowSelection(true);
setEnableMultiRowSelection(false);
} else {
setEnableRowSelection(true);
setEnableMultiRowSelection(true);
}
}}
features={features}
onFeaturesChange={(patch) => setFeatures((f) => ({ ...f, ...patch }))}
rowHeight={rowHeight}
onRowHeightChange={setRowHeight}
undoLimit={undoLimit}
onUndoLimitChange={setUndoLimit}
persistColumnSettings={persistColumnSettings}
onPersistColumnSettingsChange={handlePersistToggle}
onReplayTips={replayTips}
onResetFabPosition={() => setFabResetToken((t) => t + 1)}
/>
<CoachTour active={coachActive} onDone={finishCoach} />
</div>
);
}