Repository navigation
TableView
🌐 This page in: English · Português · Español · Français · Deutsch
TableView is a window onto a grid larger than any screen, on BOTH axes, as Flutter's TableView
is: you describe how far each row and each column reaches, hand it a builder that answers the cell
at a row and a column, and say how many leading rows and columns stay pinned. It builds only the
cells in view plus a margin around them, so Excel's whole sheet, 1 048 576 rows by 16 384 columns,
costs what a screenful costs. It is write-once: the same C# runs as GPU paint on Photon and as DOM
on the web.
TableView(
rows: new UniformTableSpans(1_048_576 + 1, 28),
columns: new SparseTableSpans(16_384 + 1, 96, new Dictionary<int, double> { [0] = 48, [3] = 180 }),
cellBuilder: (row, column) => Text(row == 0 ? $"{column}" : $"{row}:{column}", TypeRole.Caption),
pinnedRows: 1,
pinnedColumns: 1,
width: SizeValue.Fill,
height: SizeValue.Fill,
label: "Sheet")The dashboard sample's /table screen is this grid with each cell showing its address, a header row
and a column of row numbers pinned.
Each axis is a TableSpans: how many there are, how far they reach in all, and the two questions the
viewport asks, where index i starts (OffsetOf) and which index is at an offset (IndexAt).
-
UniformTableSpans(count, extent): every row (or column) reaches as far. Every offset is one multiplication. -
SparseTableSpans(count, extent, extents): a default extent, and the few that have their own, as a sheet where someone widened column D. The overrides are kept sorted with a running sum, so an offset costs a binary search over the overrides, however long the axis. - Your own: derive from
TableSpansand answerCount,Total,ExtentOf,OffsetOfandIndexAt.
Offsets are double on both targets: a million rows of 28 reach 29 360 128, past where a float
still counts single points.
The table builds the rows and columns the viewport shows plus overscan (3 by default) past each
edge. The window has hysteresis: a scroll that keeps what is visible inside the cells already built
builds nothing, and the viewport moves those cells by itself. The table builds again only when the
visible rows or columns leave them.
A cell is keyed by its position ("row:column"), so a cell that stays in the window keeps its
element, and its state, when the window moves.
The first frame is built for a common window (1 024 × 768) rather than for nothing, so it shows cells; the viewport then reports its real size and the next frame builds for it.
pinnedRows and pinnedColumns keep the leading rows at the top and the leading columns at the
start while the rest scroll. The viewport draws four bands, each under its own clip: the corner,
which never moves; the pinned rows, which move across with the grid; the pinned columns, which move
down with it; and the body, which moves both ways. A cell is drawn in the band of its first row and
column.
marks are drawn ABOVE the cells, over the range each covers, and take no press and no focus: a selection, an
outline, the band of a find. Each is a TableViewCell made with mark: true, its rows and
columns saying how many it covers:
TableViewCell(row: 4, column: 2, Box(new BoxStyle { BorderWidth = 2, BorderColor = theme.FocusRing }),
mark: true, rows: 3, columns: 2)A mark that crosses the edge of a pinned band is cut there, one piece per band, so it shows in each band it reaches and hides where it passes under one.
The viewport OWNS its offset, on both targets: the browser scrolls nothing. That is not a taste. A browser draws an element at most 16 777 214 px tall, and Excel's rows reach 29 million, so a grid the browser scrolled could never reach its last row. The table moves its cells by transform instead, and draws its own scrollbars.
- A wheel and a trackpad move both axes. A trackpad's precise deltas move the grid as the fingers do; a mouse wheel's notch glides to where it lands. Shift turns a vertical wheel across.
- The scrollbars sit in strips on the end and bottom edges. A thumb drags, and a press on the track moves a page toward the pointer.
- A finger that travels past a 12-point slop pans the grid, and a lift while it moves flings it. A press that never travels is still a tap on the cell under it.
- What the grid cannot take at an edge goes on to the scroller around it, so a page keeps scrolling once the grid is at its end.
- The keyboard: the table is a stop of the Tab order itself, before its cells' controls. With the focus, the arrows move a step, PageUp and PageDown a page, Home and End to the first and the last row, and with Control or Command to the first and the last column too. Tab visits the cells' controls row by row.
- A mouse or a trackpad's pointer on a phone or a tablet presses the cells, as it does on a desktop; only a finger pans.
- Reduced motion lands every step at once.
request scrolls on your behalf, once per version:
request: new TableScrollRequest(version) { Reveal = new TableRange(1_048_575, 0) }Offset puts the grid at an offset; Reveal brings a range into view, clear of the pinned bands.
A request is honoured when its Version is new, so handing the same one on every build does nothing
twice. It holds until the person moves the grid, so a layout that settles in two passes, a row that
measures the table before and after its fixed siblings, still lands it where the last pass says.
onScrolled says where the grid stands (a TableOffset, across and down) and onViewportChanged
how large the part the cells scroll in is, each once per change.
A table IS a window, so give it one: a fixed size, or SizeValue.Fill inside something that has a
size. A Fill axis with no bound, which is what a page that scrolls on that axis hands its content,
takes the visible extent of the nearest scroller around it, or the window's, and the development log
says so once, naming the extent it took. Its own content never decides its size, and a width of its own wider than the room its parent
gives is held to that room, on both targets.
The viewport is a group named by label, over the cells it holds. The cells it does not hold are not
in the tree at all, on either target. Tab reaches the controls in the cells it holds, and one the focus
lands on past the window is scrolled into view, clear of the pinned bands. A mark is never a stop of the
Tab order, and a screen reader does not reach it.
TableView is a component over a node of the vocabulary, TableViewport, which places the
TableViewCells it is handed and carries the scroll model (ITableScrollModel, implemented by
TableScroll). A component that decides its own window, as the Spreadsheet will, can
compose the node directly with the same model: the realizers feed the model the wheel, the pointer
and the clock, and draw what it answers.
🏁 Start here
🏗️ Architecture
- Architecture Overview
- Write-Once Components
- Declarative Surface
- Package Architecture
- Components
- Styling
- Localization
- Analytics & GTM
📱 Write-once
- Photon Engine
- Design System
- Capabilities
- Storage
- Forms
- Code Editor
- Table View
- Markdown
- Mermaid
- Email Rendering
⚙️ Compilation
⚡ Runtime
🔌 Server
🎨 Ecosystem
🚀 Development