ultimate_grid 0.1.0
ultimate_grid: ^0.1.0 copied to clipboard
Scalable, themable 2D data-grid for Flutter — 9-region freeze, canvas-paint body, merges, async data, selection, sort, filter, search.
Ultimate Grid #
A scalable, themable 2D data-grid package for Flutter — a free, open-source
alternative aimed at being more capable than pluto_grid and more ergonomic
than the built-in TwoDimensional widgets, while scaling to millions of
cells without jitter on low-end devices.
Status: in active development. The public API surface is stable for Phase 3 (headless model + 9-region grid + custom
RenderObjectbody).
What it does today (Phase 3) #
- Matrix-map data model.
GridDataSourcewith sparse cell storage and a lazy, sparse metadata side-channel that costs zero per-cell memory when unused. - Sealed
CellValuetypes.Empty / Number / Text / Bool / Date / Formula / Custom. Strongly typed; renderers pattern-match. - Per-cell behavior, explicit or by rule.
InteractionPolicy<T>withMapPolicy(per-cell map) andPredicatePolicy(e.g.PredicatePolicy.evenCells(...)— fires only on cells where both indices are even). Both shapes compose withoverriddenBy. - 9-region freeze layout. Left-frozen / scrollable / right-frozen columns
× top-frozen / scrollable / bottom-frozen rows. Non-contiguous freezes
(e.g. freeze columns 1, 2, and 8 to the left) ordered by explicit pin
priority. All cumulative offsets in
Float64List; binary-searchfirstVisibleMiddle. - Single-pass derived state.
GridControllerrebuilds column layout, row layout, and the filter/sort/search pipeline once per revision — not once per frame. - Themable. Default
GridTheme.mark85. Per-column / per-row / per-cell style overrides flow through the sameInteractionPolicyshape. - Pluggable cell renderers.
CellRendererRegistry: per-column override → per-CellKinddefault → fallback. Defaults ship for number, text, bool, date. - Custom-render body. A single
RenderUltimateBodyper column slice paints visible cells directly via a cachedTextPainterLRU — no widget tree per cell. Bool cells render a filled tickbox; number cells are right aligned with tabular figures. Tap opens a single overlayTextFieldfor edit-in-place; Enter commits, Esc cancels. - Excel-style selection. Drag inside the body to draw a rectangle;
Shift-click extends; Cmd/Ctrl-click pushes a non-contiguous range.
Cmd/Ctrl+Ccopies the bounding rectangle as TSV to the system clipboard (round-trips with Excel / Numbers / Sheets). This is the package's "web-like text copy" story — drag-to-highlight a substring inside a cell would need a widget tree per cell, so it's intentionally not supported in the body's fast paint path. Double-tap a cell to enter the editor, where theTextFielditself supports full text selection / native context menu / copy. - Cell merges. Declare a
MergeRangeon the data source; the body renderer skips occluded cells and expands the anchor to span the merge. Merges that get split by sort / filter are silently dropped that frame. - Drag-to-resize columns.
UltimateResizableHeaderships an 8-px right-edge handle per column with aresizeColumnmouse cursor. - Header menu + filter UI.
showUltimateColumnMenu(...)opens a Material popup with sort, pin, hide, resize-to-fit, filter actions;showUltimateFilterDialogships type-appropriate inputs (Contains for text/date, Min/Max for numbers).Filters.*are pre-built predicates. - Search field.
UltimateSearchFieldis a drop-in input; toggle Highlight ↔ Filter mode to either mark matches or drop non-matches. - Widget cells for interactive columns. Pass
widgetColumns+cellWidgetBuildertoUltimateTableto render specific columns' body cells as widgets instead of fast paragraph paint — useful when a column needs checkboxes, buttons, or custom layouts. Other columns keep the fast path.
Quick start #
import 'package:ultimate_grid/ultimate_grid.dart';
final schema = GridSchema(
columns: const [
ColumnSpec(id: 'sku', header: 'SKU', defaultWidth: 110,
defaultFrozen: FrozenSide.start),
ColumnSpec(id: 'name', header: 'Product', defaultWidth: 220),
ColumnSpec(id: 'price', header: 'Price', defaultWidth: 100,
kind: CellKind.number),
],
rows: [for (var i = 0; i < 100; i++) RowSpec(id: 'r$i')],
);
final source = MapGridDataSource(
rowIds: [for (final r in schema.rows) r.id],
colIds: [for (final c in schema.columns) c.id],
);
source.setValue('r0', 'sku', const TextCell('SKU-0001'));
source.setValue('r0', 'name', const TextCell('Widget A'));
source.setValue('r0', 'price', const NumberCell(19.99));
final controller = GridController(schema: schema, source: source);
// In your widget tree:
UltimateTable(controller: controller);
For a fuller gallery — financial sheet with merges, 100k-row async paging,
search & filter UI, theme switcher with three presets, office time log,
budget tracker, datagrid, spreadsheet, and a 5 M-row stress test — see the
example/ directory.
Constraints #
- Flutter web: uses
Uint32Listfor bitsets (JavaScript has no native 64-bit ints, soUint64Listisn't supported on web). Anything you add internally must follow the same rule. - No widget tree per cell in the body region. Body cells are painted
directly by
RenderUltimateBodyvia a paragraph LRU cache. The public widget API is unchanged; the body region simply hosts a single render-object widget per column slice plus one overlay editor widget.
Architecture map #
src/
├── model/ CellValue, CellAddress, ColumnSpec, RowSpec, GridSchema, FrozenSide
├── source/ GridDataSource (abstract) + MapGridDataSource
├── interaction/ InteractionPolicy: MapPolicy, PredicatePolicy, composition
├── controller/ Selection, ColumnLayout, RowLayout, GridController
├── filter_sort/ ViewPipeline (filter → sort → search; one pass)
├── theme/ GridTheme, ColumnStyle, RowStyle, CellStyle
├── cells/ CellRenderer + registry; default Number/Text/Bool/Date renderers
└── view/ SyncedScrollGroup, UltimateTable, UltimateTableHeader
License #
MIT — see LICENSE.