table-layout

The pure geometry behind a data table — column widths, and which rows are worth drawing. No DOM, no Babylon, no SVG: it turns a column spec plus a scroll offset into rectangles, so the view is a straight paint and the arithmetic is unit-tested.

Two problems, both of which are only annoying when you get them wrong:

import { resolveColumns, visibleRows, rowAt } from 'tosijs-3d'

const cols = resolveColumns(
  [{ key: 'name', flex: 1 }, { key: 'qty', width: 60 }, { key: 'price', width: 80 }],
  { width: 400, gap: 8 }
)
// → [{key:'name', x:0, width:236}, {key:'qty', x:244, width:60}, {key:'price', x:312, width:80}]

visibleRows({ scroll: 120, rowHeight: 28, viewportHeight: 100, count: 500 })
// → { start: 4, end: 12, offsetY: -8 }   ← build 8 rows, not 500

Why virtualize at all

A table on a 3D texture pays twice for rows you can't see: once building SVG nodes, and again rasterizing the whole sheet to a DynamicTexture every time it changes. A 500-row inventory becomes 500 <g>s in a texture that shows twelve of them. The window keeps it proportional to the viewport instead of the data.

overscan draws a row or two beyond each edge so a fast scroll doesn't flash empty bands before the next paint.