Data operations
Sort, filter, paginate, aggregate. All run off the stateManager from
onLoaded.
Sort, filter, paginate
sm.toggleSortColumn(column); // asc -> desc -> none
sm.sortByColumns([ // multi-column
(column: sm.columns[2], ascending: true),
(column: sm.columns[0], ascending: true),
]);
sm.sortByColumns([]); // clear
sm.setFilter((row) => // keep matching rows; source untouched
'${row.cells['name']?.value}'.toLowerCase().contains(query));
sm.setFilter(null); // clear
sm.setPageSize(15); sm.setPage(2); // paginate
Drop-in pager widget:
Column(children: [
Expanded(child: BCellGrid(columns: cols, rows: rows,
onLoaded: (e) => setState(() => _sm = e.stateManager))),
if (_sm != null) BCellPagination(_sm!),
]);
Aggregates
sm.sum('price'); sm.average('price');
sm.min('price'); sm.max('price'); sm.count('price');
Drop-in status bar (live Count / Sum / Average of the selection):
Column(children: [
Expanded(child: BCellGrid(columns: cols, rows: rows,
onLoaded: (e) => setState(() => _sm = e.stateManager))),
if (_sm != null) BCellAutoSum(_sm!),
]);
AutoFilter
Excel-style per-column filter dropdown (columns AND together). One controller drives every funnel button; or compose the predicate yourself.
final filter = BCellAutoFilter(sm);
BCellColumnFilterButton(controller: filter, column: someColumn);
filter.setAllowed('role', {'Engineer'}); filter.clear();
// Without the buttons:
sm.distinctValues('role'); // -> [Engineer, Admiral, ...]
sm.filterByValues({'role': {'Engineer'}});
Slicer
A chip bar of one column's distinct values; tapping chips filters the grid
(chips within a column OR together, and it composes with filterByValues):
Column(children: [
BCellSlicer(sm, field: 'role'),
Expanded(child: BCellGrid(...)),
]);
Row grouping (outline)
Excel-style outline grouping over a run of displayed rows. The group's first row stays visible as its header and carries a +/− toggle in its first cell; collapsing hides the detail rows below it (they simply drop out of the display pipeline — filter → groups → paging).
final group = sm.groupRows(1, 3); // rows 1..3; row 1 = header
sm.toggleRowGroup(group!); // collapse / expand (the +/− tap)
sm.ungroupRows(group); // dissolve
sm.rowGroups; // current groups
Groups capture the row objects, so they survive sorting and filtering.
Single-level groups only (no nesting, no column grouping); overlapping
ranges are rejected (groupRows returns null).
Find & Replace, freeze rows
sm.replaceAll('ada', 'X'); // case-insensitive; skips formula/non-text cells
sm.setFrozenRowCount(2); // pin the top 2 rows below the header (freeze panes)