flutter_table_plus 2.15.3
flutter_table_plus: ^2.15.3 copied to clipboard
A highly customizable and efficient table widget for Flutter, featuring synchronized scrolling, theming, sorting, selection, column reordering, hover buttons, and expandable rows.
2.15.3 #
- DEPS:
just_tooltip: ^0.4.3→^0.4.4. A floor, not a preference. 0.4.4 stops a tooltip that has nothing to draw from displacing the tooltips around it (just_tooltip#46) — the very guarantee 2.15.2 secured for itself by never building such a tooltip. That guard is gone now, so under 0.4.3 this package would reissue the bug 2.15.2 fixed, rather than merely miss an upstream improvement- The SDK floor is unchanged. 0.4.4 still declares
sdk >=3.1.0 <4.0.0andflutter >=3.13.0, so this package's Flutter 3.13 floor stands - 0.4.4 also makes a tooltip that is already on screen follow changes to its
message,tooltipBuilder,theme,directionandalignment(just_tooltip#47); the overlay rendered them live but nothing ever asked it to rebuild. Nothing here relied on the old behaviour — all 382 package tests and 67 example tests pass against 0.4.4 unmodified
- The SDK floor is unchanged. 0.4.4 still declares
- REFACTOR:
wrapWithTooltipno longer skips the wrap when the resolved message is empty. just_tooltip owns that rule now, and a local copy of the predicate was a second thing that had to agree with the first, with nothing in the build to make ita cell tooltip that cannot show does not suppress the carddid not change one character. It observes the contract — the row card survives — and not the mechanism, so it kept passing once upstream took the mechanism over- Removing the guard while allowing 0.4.3 is what would break, and only there: the same test is the single failure across the suite when both are done
- DOCS: 2.15.2 argued for its fix with a rationale that 0.4.4 has made false, and no test guards a rationale. It held that suppressing ancestors from
MouseRegion.onEnterwas an intended contract of just_tooltip, so a tooltip that cannot show must never be built. Upstream has since judged that a trap in its own default and fixed it there. What 2.15.2 shipped was right for 0.4.3; the reason it gave was not, and a reader following that reason today would conclude the guard must stay- Withdrawn from
TablePlusTooltipTheme.hideOnEmptyMessage,TablePlusColumn.tooltipFormatter,wrapWithTooltip,docs/THEMING.mdand the tests that repeated it.hideOnEmptyMessagenow reads "draws nothing, and displaces nothing" FlutterTablePlus.rowTooltipBuilderneeded no correction: "a cell only takes the card's place when it has something to show" is what 0.4.4 made upstream law
- Withdrawn from
2.15.2 #
- FIX: a cell whose
tooltipFormatterreturns an empty string no longer swallows therowTooltipBuildercard. Hovering such a cell showed nothing at all — not the cell's tooltip, not the row's- A tooltip suppresses its ancestors from
MouseRegion.onEnter, before it decides whether it has anything to draw, andhideOnEmptyMessagedecides "nothing" inside_show(). The cell had already taken the card down. Hoisting that guard to where the cell is wrapped means a tooltip that cannot show is never built, so it cannot suppress anything. The header has guarded this way all along — an empty label gets no tooltip — but a cell's message is only known oncetooltipFormatterhas run hideOnEmptyMessage: falseis unchanged: that asks for the empty bubble, and it still wins over the card
- A tooltip suppresses its ancestors from
- DOCS:
rowTooltipBuilder's "the whole row is the hover region" is now pinned by tests, including over a cell whose value is empty. It was never broken there — a row'sMouseRegionis opaque and hit-tests itself, so a zero-widthTextunder the pointer changes nothing — but nothing said so, and the neighbouringtooltipFormatterbug looked exactly like a hole in that region. The doc comment now says which boundaries the region actually has, and which cells take the card's place - DOCS:
TablePlusTooltipTheme.hideOnEmptyMessagewas absent fromdocs/THEMING.mdaltogether. It was a footnote when a tooltip stood alone; it decides whether the row card appears once one nests inside another
2.15.1 #
- DEPS:
just_tooltip: ^0.4.2→^0.4.3. No API change — 0.4.3 declares the same public classes, enums and fields, and touches two internal files. All 378 package tests and 28 example tests pass against it unmodified, and its SDK floor is the same, so this package's Flutter 3.13 floor stands- It fixes an
interactivetooltip dying for good once the cursor returns from the tooltip body to its child (just_tooltip#43). Leaving the tooltip armed a 100 ms bridge that re-entering the child never cancelled; it fired unseen, started a fade-out, and a pointer already inside the child sends no furtheronEnterto revive it. The tooltip vanished roughly 250 ms after the cursor came home and stayed gone - This was reachable from the defaults.
TablePlusTooltipTheme.interactiveistrueand every cell, header and row tooltip passes it.TooltipAnchor.pointer, added in 2.15.0, draws the tooltip beside the cursor — exactly the arrangement that walks the cursor into the tooltip body
- It fixes an
- EXAMPLE: The playground's settings panel is searchable
- 68 controls across five sections, and no way to find one except to remember which section held it. Typing narrows the panel to the controls whose labels match; a section holding a match opens to show them, one holding none stands aside, and clearing the search restores what was open before
- The row card gained its own wait duration, separate from the cells'. A card interrupts more than a line of text does. Its transparent background and zero padding stay fixed: the builder draws its own surface, so a tooltip surface behind it would be a second card
Tooltip Enabledalso silences the row card, which the toggle never said. It says so now
- INTERNAL: No library code changed in this release —
lib/is byte-for-byte the 2.15.0 tree
2.15.0 #
-
BREAKING: minimum Flutter is now
3.13.0(Dart3.1.0), up from3.10.0.just_tooltip0.4.2 walksRenderObject.parent, which wasAbstractNode?— a type with nodescribeApproximatePaintClip— before Flutter 3.13. Thejust_tooltip: ^0.4.0constraint already resolved 0.4.2, so the old floor had quietly become a promise this package could not keep; raising it is the honest fix, not the cause -
DEPS:
just_tooltip: ^0.4.2. Upstream now anchorsTooltipAnchor.childto the visible part of the child, re-aims a shown tooltip when its child moves, and hides it once the child is clipped out of sight. Cell and row tooltip behaviour here is unchanged; all 378 tests pass unmodified -
FEAT:
TablePlusTooltipTheme.anchor— position a tooltip beside the cursor instead of beside the widget it wrapsTooltipAnchor.child(the default) anchors to the visible part of the hovered widget's rect. That is wrong whenever the widget is far wider than the neighbourhood the user is pointing at: a cell in a column wider than the viewport gets a tooltip at the centre of whatever slice is on screen, wherever the cursor may be.TooltipAnchor.pointerkeeps the same hover region and anchors at the cursor- Against a point there are no target edges to align to, so under
TooltipAnchor.pointerthealignmentfield selects which of the tooltip's own edges lands on the cursor TooltipAnchoris now re-exported, so you no longer needjust_tooltipin your ownpubspec.yamlto name it- Row tooltips built by
rowTooltipBuilderalways anchored at the pointer and still do; they ignore this field. A row is as wide as the table's content, so a child anchor would aim at the centre of whatever slice of the row is on screen — visible, but unrelated to where along the row the cursor is
-
FEAT:
TablePlusTheme.headerTooltipTheme— style header tooltips apart from cell tooltips- Header and cell tooltips were styled by one
tooltipTheme, so tooltip behavior was separable (headerTooltipBehaviorvstooltipBehavior) while tooltip style was not. Withanchorexposed, that asymmetry bit: anchoring a header at the pointer dragged every cell along with it - Nullable, and falls back to
tooltipThemewhen unset — the same shape asrowTooltipTheme. Leave it null and nothing changes
- Header and cell tooltips were styled by one
-
FIX: A scaled table no longer loses
rowTooltipThemeTablePlusTheme.scaledBy()rebuilt the theme without carryingrowTooltipTheme, so at anyscaleother than1.0it went null and the documented fallback handed the row tooltip totooltipThemeinstead. A card styled for its own surface — transparent, unpadded — came back wearing the grey text tooltip'sscaledBy(1.0)returns the receiver untouched, so this never fired at the default scale, which is why it went unnoticedscaledBy()now names only the sub-themes it actually scales and leans oncopyWithto carry the rest, so a theme field added later cannot be dropped by forgetting to list it
2.14.0 #
- FIX: Row splash / hover / highlight now use the colors you set on
TablePlusBodyTheme, in every mode- The row's ink colors were passed as
nullwhenever the row was not tap-selectable — notably in editing mode — to suppress the ink. Butnulldoes not suppress it:InkWellresolveswidget.splashColor ?? Theme.of(context).splashColor, so the table silently rendered Flutter's faint grey default instead of your theme. On a white row that default is nearly invisible, which made "no splash" and "a splash you cannot see" look identical.TablePlusBodyTheme.splashColoralready documented the real contract: passColors.transparentto disable - Which ink appears is now decided by which callbacks are wired, which is what
InkWellactually gates on — a splash/highlight needs a primary-button callback, a hover highlight needs any callback. The row shell previously forwardedonDoubleTap/onSecondaryTapDownas non-null closures that merely called a possibly-null handler, soInkWellalways looked enabled and painted a splash for a tap that did nothing. Those closures are now forwarded only when a handler exists, and the theme's colors are always passed through
- The row's ink colors were passed as
- FEAT:
rowTooltipBuilder— a rich card shown while hovering anywhere on a rowFlutterTablePlus.rowTooltipBuilder(context, rowData)returns the card, ornullfor a row that should not have one. The whole row is the hover region, so the card does not blink off as the pointer crosses columns, and it is anchored beside the pointer rather than at the row's centre — a row is as wide as the table's content, which can far exceed the viewport- A cell with a tooltip of its own wins: exactly one tooltip is visible, the innermost under the pointer
TooltipBehavior.always— the default — leaves the card nowhere to appear.alwaysmeans "whenever the column is ellipsized", not "whenever the text is actually cut", so every ordinary text column already has a tooltip. UseTooltipBehavior.onlyTextOverflowon text columns alongside a row card- Style it with
TablePlusTheme.rowTooltipTheme(falls back totooltipTheme). A card draws its own surface, so it wantspadding: EdgeInsets.zero, a transparentbackgroundColorand no elevation — settings that would ruin plain text tooltips - Merged rows stand for several data rows, so there is no single
rowDatato build from; they carry no card
- FIX: A column's
tooltipBuilderno longer depends on the cell's text being truncated, and custom cells can finally have a tooltip- The decision "should this cell show a tooltip?" was made by asking "has this cell's text been truncated?" — so a
tooltipBuilder, whose content has nothing to do with that text, only rendered on a column that happened to be ellipsized and non-empty. SincetextOverflowdefaults toTextOverflow.ellipsis, this only bit columns that opt intoTextOverflow.visible, which dynamic row heights encourage - A cell built by
statefulCellBuilderreturned before the tooltip wrapper was ever reached, in the normal row and in both merged-row paths. Such a column — a status badge, say, exactly the kind that wants a rich tooltip — could not have one at all - Ink for a widget tooltip now covers the whole cell, not just the text. An empty cell's
Textis zero-wide and a short one leaves most of its cell unhoverable, so the tooltip was attached but unreachable. Text tooltips still belong to the glyphs: hovering the blank part of a wide column shows nothing, exactly as before TooltipBehavior.neverstill suppresses everything. For a widget tooltip,alwaysandonlyTextOverflowboth show it — "text overflow" is undefined for builder content
- The decision "should this cell show a tooltip?" was made by asking "has this cell's text been truncated?" — so a
- BEHAVIOR: Tapping a row now selects it while
isEditableis true- Tapping an editable column still starts editing that cell — the cell's own
GestureDetectorwins the gesture arena against the row-level tap. Tapping anywhere else on a selectable row now toggles its selection, as it does outside editing mode - Row-tap selection was suppressed during editing since editing was introduced, when the two modes were mutually exclusive (
assert((isSelectable && isEditable) == false)). That assert was removed to let them coexist, but the row guard survived, leaving a contradiction: while editing you could still select a row via its checkbox, just not by clicking it - If you relied on rows not selecting on tap while editing, handle it in your
onRowSelectionChanged
- Tapping an editable column still starts editing that cell — the cell's own
- PERF: The selection cell no longer allocates its own
Materials — up to three per row become oneTablePlusSelectionCellwrapped its checkbox in a transparentMaterial, and its cell-tapInkWellin another, so thatFlutterCheckbox's internalInkWellwould find aMaterialancestor and the table would render without aScaffold(#3). ButCustomInkWellalready wraps the whole row in one, and the selection cell only renders when the row is selectable — which is exactly when that rowMaterialexists. Both were redundant- A
Materialis not cheap: with the defaultcanvastype it expands toAnimatedDefaultTextStyle→AnimatedPhysicalModel→PhysicalModel→_InkFeatures, i.e. two render objects and two implicit-animation controllers. Removing them takes a selection-enabled row from three to one - Ink is still painted per row, so this is unrelated to the root-
Materialhoist rejected in #38 — that regressed scroll because it moved ink painting outside each row'sRepaintBoundary
- CHORE: Bump
just_tooltipdependency^0.3.0→^0.4.0- Picks up a fix for tooltips laid out in a nested
Navigatoror an insetOverlay, which were displaced by the Overlay's offset (just_tooltip#24), and reliable innermost-wins behavior for nested tooltips (just_tooltip#22). Neither is reachable from this package's current widget tree, so nothing changes here - Adds
TooltipAnchor.pointer, which #45 needs: a row tooltip must keep the whole row as its hover region while anchoring beside the cursor
- Picks up a fix for tooltips laid out in a nested
- TEST: The #3 regression guard now exercises the checkbox, not just its rendering
InkWellresolvesMaterial.ofonly when it paints ink — on tap (_createSplash) and on hover/press (updateHighlight) — so a checkbox with noMaterialancestor builds fine and throws only when touched. The old guard pumped the table and asserted no exception, which passed for an incidental reason: the row'sInkdemands aMaterialat build. The guard now taps the checkbox, taps the cell (cellTapTogglesCheckbox, previously untested), and hovers the checkbox
2.13.1 #
- PERF: Rows no longer rebuild on pointer hover when there are no hover buttons
- Each row wrapped its content in a hover-tracking
MouseRegionthat calledsetStateon every pointer enter/exit, but that state drives only the hover button. WhenhoverButtonBuilder == null(the common case), the rebuild — all cells, including tooltip overflow re-measurement — had no visible effect, and fired continuously while the mouse moved over the table during scrolling - The
MouseRegion+setStateare now installed only when there are hover buttons. Row hover / splash / highlight colors are unchanged (painted by the row'sCustomInkWell), and the hover-button reveal is unchanged when a builder is set
- Each row wrapped its content in a hover-tracking
- PERF: Cell
FocusNodeis now allocated lazily, only when a cell can editTablePlusCellcreated aFocusNode(and registered a listener) for every cell ininitState, though only editing uses it. It is now created on first edit, so non-editable tables and non-editable columns allocate none — one fewer object + listener per visible cell as rows scroll into view
- CHORE: Bump
just_tooltipdependency^0.2.5→^0.3.0 - PERF: Uniform-height tables now scroll with O(1)-per-frame layout instead of O(n)
TablePlusBodyusedListView.builder(itemExtentBuilder: ...)unconditionally, which drivesRenderSliverVariedExtentList— it sums every row's extent on each layout to know the total scroll extent and to map a scroll offset to an index. On a 100k-row table this is ~O(n) per frame and caused visible scroll jank- When there are no merged groups and no
calculateRowHeight(i.e. all rows sharetheme.rowHeight), the body now passes a fixeditemExtent, so Flutter usesRenderSliverFixedExtentList(offset↔index by division). Merged-group and dynamic-height tables keepitemExtentBuilderwith identical geometry - Local harness (100k uniform rows, 60
jumpTolayouts): ~83.6 ms/jump → ~22.1 ms/jump end-to-end, and the per-frame layout cost no longer grows with row count
2.13.0 #
- FIX: Row-level gestures now fire while
isEditableistrueonRowDoubleTapandonRowSecondaryTapDownpreviously never fired in edit mode because the row's interaction layer was gated on selection being active (isSelectable && !isEditable). They now fire whenever a handler is provided — e.g. right-click a row to delete it while other rows stay editable- Tap-to-select is still suppressed while editing (a single tap edits a cell); the selection ink splash is not shown for edit-mode gestures
- FIX: Checkboxes no longer require a
Material/Scaffoldancestor- The
InkWellinsideFlutterCheckboxneeds aMaterialancestor; the row selection cell and header select-all cell now wrap the checkbox in a transparentMaterial, so the table works in non-Material desktop apps without aScaffold
- The
- FIX: Narrow selection columns no longer clip the checkbox
- The row and header select-all cells no longer wrap the (already-centered) checkbox in the full horizontal padding, which squeezed the content area to zero. A
checkboxColumnWidthbelow ~40 now keeps the checkbox visible
- The row and header select-all cells no longer wrap the (already-centered) checkbox in the full horizontal padding, which squeezed the content area to zero. A
- PERF: Column-width layout is now near-linear in the column count
- The max-width redistribution used to cap one exceeding column and restart the scan (~O(n²)). It now settles all caps in a single sweep over columns sorted by
maxWidth / width(~O(n log n)) — identical widths, verified by a differential test against the previous algorithm across randomized inputs - ~28× faster at 10,000 columns in the local benchmark (9.7 ms → 0.35 ms); negligible difference for normal column counts
- The max-width redistribution used to cap one exceeding column and restart the scan (~O(n²)). It now settles all caps in a single sweep over columns sorted by
- INTERNAL: Large testability refactor — no public API or behavior change
- The column-width algorithm, the row hit-test geometry behind the drag-selection
RowLocatorport, table metrics (total height / row count / renderable indices), the sort-direction cycle, the single/double-tap timing, the scroll-sync reentrancy guard, and several theme/row helpers were extracted into pure, unit-tested modules - Test count
271→331; line coverage ~85% → ~91%. Addedbenchmark/pure_paths_benchmark.dartmicrobenchmarks (run withflutter test benchmark/pure_paths_benchmark.dart)
- The column-width algorithm, the row hit-test geometry behind the drag-selection
2.12.2 #
- CHORE: Bump
flutter_checkboxdependency^0.2.0→^0.2.1
2.12.1 #
- FIX: Explicitly set
mouseCursor: SystemMouseCursors.clickon interactiveInkWellwidgets so the pointing-hand cursor appears reliably on recent Flutter versionsCustomInkWell(used by row and merged-row selection) now resolves toSystemMouseCursors.clickwhenonTaporonDoubleTapis provided, and toMouseCursor.deferotherwise —deferleaves the cursor decision to an ancestorMouseRegion(e.g., resize handles) instead of stomping it withbasicTablePlusSelectionCell's checkbox-cellInkWellmirrors the same pattern, guarded onrowId != null- Previously, the wrapped
InkWellrelied on the implicitWidgetStateMouseCursor.clickabledefault, which no longer flips toclickin all configurations after recent Flutter cursor-resolution changes
2.12.0 #
- FEAT: Horizontal auto-scroll during drag selection
- When the table is wider than its viewport (
SingleChildScrollViewis scrollable horizontally), dragging the pointer near the left or right edge of the visible viewport now scrolls the table horizontally at proximity-proportional speed, mirroring the existing vertical auto-scroll - Edge detection runs in visible-viewport coordinates (uses
horizontalController.position.viewportDimension), not body-local coordinates, so the edge zone tracks the user's actual visible area - Both axes can scroll simultaneously when the pointer is in a corner (e.g., bottom-right)
- The rubber band rectangle is content-anchored on both axes — its origin tracks the underlying content via the symmetric
downLocal − hDelta/vDeltaformula, so scrolling on either axis grows the rectangle visually - Horizontal proximity is clamped to
[0, 1]so dragging the pointer far past the visible viewport edge does not overshoot the configured max speed (the vertical axis retains its original feel) - The drag-selection
Listenersits at the body's viewport level (outside the body's horizontalScrollable), so itsevent.localPositionis viewport-local on both axes — there is no "body slid in screen" compensation to maintain. As a consequence, the auto-scroll engine progresses cleanly tomaxScrollExtentwhile the pointer is held in an edge zone, instead of stalling once accumulated horizontal scroll delta would have pushed a stale captured origin out of the zone
- When the table is wider than its viewport (
- FEAT: Rubber band rectangle for drag selection (Finder/Explorer-style marquee)
- When
enableDragSelectionis true, a translucent rectangle is now drawn from the pointer-down position to the current pointer position while the drag is active, providing immediate visual feedback for the selection range - The rectangle is decoupled from anchor establishment: it appears as soon as the activation threshold is passed, even when the drag stays entirely inside empty space (no row anchor yet) or when the pointer crosses back above row 1 into the header area
- Content-anchored rectangle: the origin is fixed to the underlying content rather than the viewport, so auto-scroll causes the rectangle to grow visually — matching OS marquee conventions
- New
TablePlusDragSelectionTheme(composed intoTablePlusTheme.dragSelectionTheme) withshow,fillColor,borderColor,borderWidth,borderRadius. Defaultshow: true; set tofalseto keep drag-selection logic without the visual cue borderWidthparticipates inTablePlusTheme.scaledBy(); colors andborderRadiusare intentionally not scaled- Re-exported from the main library for convenient theming
- When
- FIX: Drag selection no longer snaps to the last row when the pointer is in the empty area below the data
_renderIndexFromLocalYnow returnsnullfor coordinates outside the actual row area (above the first row or below the last); callers' existing null-guards activate sticky behavior at the last reached row instead of clamping to a row the pointer never crossed- Lazy activation: when pointer-down lands in the empty area below the last row, the drag anchor is deferred until the pointer first crosses into a real row — starting a drag from the empty area and moving into rows still works, but a drag confined to empty space no longer selects anything
- Side-aware release: when a drag started from the below-data empty area and the pointer returns to that same area, the lazy anchor is released and the selection collapses (mirroring OS marquee behavior in Finder/Explorer). Re-entering a row lazy-activates a fresh anchor. Crossing instead above row 1 into the header area preserves the sticky range, so sweeping up through every row and continuing past the header keeps the full selection intact
- Sticky preservation for in-row starts: when a drag started inside the rows moves past the last row into empty space (or above row 1 into the header area), the selection freezes at the last reached row instead of being coerced to the boundary
- EXAMPLE: Added
5quick preset to the playground for testing small-data drag-selection scenarios (logarithmic slider lower bound:10→5) - INTERNAL: Drag-selection coordinate model unified to a single viewport-local frame
- Header and body now use independent horizontal
SingleChildScrollViews synced via a shared-controller pattern (SyncedScrollControllersadds a 5th controller slot for the header). The header isNeverScrollableScrollPhysics— body is the master input source TablePlusBodyis a pure row renderer; drag-selection state, pointer handlers, auto-scroll engine, and rubber band painter all live in_FlutterTablePlusState.TablePlusBodyStateexposesrenderIndexAtLocalYandrowIdsBetweenfor the parent's lookup needs (accessed viaGlobalKey<TablePlusBodyState<T>>)- Single-axis auto-scroll engine extracted (
_performAxisAutoScroll); per-axis wrappers now differ only in coordinate source and aclampProximityflag. Vertical retains its historical >1 acceleration past the edge zone; horizontal still caps atmaxSpeed
- Header and body now use independent horizontal
- TEST: Added
test/drag_selection_test.dartwith 8 widget tests covering basic drag, threshold gating, sticky range at empty/header boundaries, vertical / horizontal / dual-axis auto-scroll, and merged-group traversal
2.11.0 #
- BREAKING: Rename
blockCtrlScroll→blockModifierScrollto accurately reflect platform-aware behavior (Ctrl on Windows/Linux, Cmd on macOS) - FIX: Use platform-aware modifier key check to fix Cmd+scroll zoom not working on macOS
- FEAT: Export
isScaleModifierPressed()helper for library consumers implementing custom Ctrl/Cmd + scroll zoom
2.10.0 #
- BREAKING: Replaced Material
Checkboxwithflutter_checkboxpackageTablePlusCheckboxThemenow uses a singleCheckboxStyle styleproperty instead of individual Material properties- Removed:
fillColor,overlayColor,checkColor,focusColor,hoverColor,side,shape,materialTapTargetSize,visualDensity,splashRadius,size,tapTargetSize - Added:
style(CheckboxStyle) — controls all visual aspects (colors, shape, size, border, hover ring, animations) buildCheckbox()now createsFlutterCheckboxwith CustomPainter rendering for crisp display at any sizescaledBy()usesCheckboxStyle.scalefor accurate visual scaling (previously SizedBox-only scaling)- Renamed
material3()factory →colored()factory - Re-exported
FlutterCheckbox,CheckboxStyle,CheckboxShapefrom main library for convenience
- Migration:
TablePlusCheckboxTheme(fillColor: ..., checkColor: ..., size: 18)→TablePlusCheckboxTheme(style: CheckboxStyle(activeColor: ..., checkColor: ..., size: 18))tapTargetSize→CheckboxStyle(hoverRingPadding: ...)splashRadius→ removed (hover ring replaces ripple effect)- Table-specific properties (
showCheckboxColumn,showSelectAllCheckbox,checkboxColumnWidth,cellTapTogglesCheckbox,showRowCheckbox) remain unchanged
2.9.1 #
- FEAT: Added
blockModifierScrollparameter toFlutterTablePlus— independently control whether Ctrl+wheel (Cmd+wheel on macOS) scrolling is blocked- When
true, Ctrl+wheel events are consumed and do not scroll the table - When
false, Ctrl+wheel scrolls normally even ifonScaleChangedis set - Defaults to
null— automatically followsonScaleChanged(blocked when non-null, allowed when null), preserving existing behavior - Enables use cases where Ctrl+scroll blocking is desired without zoom, or zoom without scroll blocking
- When
2.9.0 #
- FEAT: Added
scaleparameter toFlutterTablePlus— zoom in/out by scaling all table dimensions- Multiplies column widths, row heights, font sizes, padding, and icon sizes by the scale factor
- Default
1.0(100%); no upper limit enforced — caller is responsible for clamping assert(scale > 0)prevents division-by-zero crashes- Scroll positions are automatically adjusted when scale changes so that the same content remains visible
- Resized column widths are stored in logical (unscaled) units — survive scale changes and
onColumnResizedreports logical widths
- FEAT: Added
onScaleChangedcallback — enables Ctrl+wheel (Cmd+wheel on macOS) zoom with scroll prevention- When non-null, the library intercepts Ctrl+wheel events and calls the callback with the proposed new scale
- Uses
_ScaleBlockingScrollPhysicsinternally: overridesshouldAcceptUserOffset()to returnfalsewhen Ctrl is held, preventingScrollablefrom registering a scroll handler — no scroll contamination - Pre-scale scroll offsets are saved as a backup for position correction in
didUpdateWidget scaleStepparameter controls the increment per wheel tick (default0.05)
- FEAT: Added
scaledBy(double factor)method to all theme classesTablePlusTheme,TablePlusHeaderTheme,TablePlusBodyTheme,TablePlusCheckboxTheme,TablePlusEditableTheme,TablePlusScrollbarTheme,TablePlusHoverButtonTheme- Returns a new instance with dimensional values (heights, font sizes, padding, icon sizes) scaled by the factor
- Colors, booleans, durations, and border thickness are intentionally not scaled
- Scrollbar theme and tooltip theme are excluded from scaling (UI chrome, overlay)
- Short-circuits with
return thiswhenfactor == 1.0
- IMPROVEMENT: Sort icons now wrapped in
FittedBox— custom sort icon widgets scale correctly withsortIconWidth - IMPROVEMENT: Body
_cachedRowHeightscleared when scale changes to prevent stale height values
2.8.2 #
- REFACTOR: Deduplicated checkbox creation across header, body, and merged row widgets
- Added
buildCheckbox()helper method toTablePlusCheckboxTheme— builds a fully-themedCheckboxwidget in one call - Replaced 3 identical inline
Checkbox(...)blocks (~47 lines) withcheckboxTheme.buildCheckbox()calls
- Added
- REFACTOR: Deduplicated vertical divider border creation across cell widgets
- Added
verticalDividerSideandverticalDividerBordergetters toTablePlusBodyTheme - Replaced 5 identical inline
Border(right: BorderSide(...))blocks with single getter calls
- Added
- REFACTOR: Moved
_shouldShowBottomBorderlogic toTablePlusBodyTheme.shouldShowBottomBorder()- Removed identical private methods from
_TablePlusRowStateand_TablePlusMergedRowState
- Removed identical private methods from
- REFACTOR: Consolidated hover button positioning logic into
HoverButtonPosition.buildPositioned()- Added
buildPositioned()method toHoverButtonPositionenum - Replaced identical switch-case blocks (~25 lines each) in
TablePlusRowandTablePlusMergedRowwith single method calls
- Added
- REFACTOR: Removed dead if/else branch in
_handleRegularRowSelectionToggle— both branches were identical - REFACTOR: Moved
nonSelectionColumnsfiltering outsideList.generateloop inTablePlusMergedRowto avoid redundant per-iteration computation - REFACTOR: Extracted
_buildScrollbarTrack()helper to consolidate identical vertical/horizontal scrollbar widget trees (~140 lines → ~60 lines) - No API or behavioral changes — all appearance and functionality remain identical
2.8.1 #
- Bumped
just_tooltipdependency to^0.2.5- Supports new
TooltipAlignment.startTargetCenterandTooltipAlignment.endTargetCentervalues — arrow dynamically points toward the center of the target widget
- Supports new
2.8.0 #
- BREAKING: Migrated tooltip system from custom implementation to
just_tooltippackage- Removed
CustomTooltipWrapperwidget andCustomTooltipWrapperThemeclass - Removed
decoration,margin,preferBelow,verticalOffset,exitDuration,customWrapperfromTablePlusTooltipTheme - Added
just_tooltip-based properties:backgroundColor,borderRadius,elevation,boxShadow,borderColor,borderWidth,showArrow,arrowBaseWidth,arrowLength,arrowPositionRatio - Added layout/behavior properties:
direction,alignment,offset,crossAxisOffset,screenMargin,enableTap,enableHover,interactive,animation,animationCurve,fadeBegin,scaleBegin,slideOffset,rotationBegin,animationDuration,hideOnEmptyMessage FlutterTooltipPlusnow accepts bothmessage(String?) andtooltipBuilder(WidgetBuilder?) — unified text and widget tooltips- Added
toJustTooltipTheme()helper onTablePlusTooltipTheme - Re-exported
TooltipDirection,TooltipAlignment,TooltipAnimationfromjust_tooltip
- Removed
2.7.1 #
- FIX:
initialResizedWidthsnow reacts to runtime changes viadidUpdateWidget- Previously only applied once at widget creation (
initState); switching data contexts (e.g., different servers) kept stale resize widths - Uses
mapEqualsfor value-based comparison — avoids unnecessary resets when state management (Riverpod, Provider, etc.) rebuilds pass an equivalent map - Widget no longer needs a
Keyswap to apply new initial widths
- Previously only applied once at widget creation (
2.7.0 #
- FEAT: Added
stretchLastColumnparameter toFlutterTablePlus— last column absorbs remaining space when all columns have fixed widths- When
false(default), columns keep their exact widths and empty space may appear on the right (Windows Explorer behavior) - When
true, the last visible column stretches to fill any leftover space after auto-fit or manual resize - Only activates when remaining space exists; no effect when columns already fill or exceed available width
- Selection column (
__selection__) is excluded from stretching
- When
- FIX: Column reorder now works when dragging to empty space right of the last column
- Added trailing
DragTargetin header row to accept drops beyond the last column - Dropped column moves to the last position, consistent with drag-to-column behavior
- Added trailing
- FEAT: Added
initialResizedWidthsparameter toFlutterTablePlus— restore saved column widths from a previous session- Columns in this map are treated as fixed (exact pixel width), same as user-resized columns
- Only applied once at widget creation; subsequent user resizes override at runtime
- Pair with
onColumnResizedto implement full column width persistence
- FIX: Flexible columns no longer jump in size when window crosses the fixed-total threshold
- Changed proportional distribution condition from
spaceForFlexible <= 0tospaceForFlexible < flexiblePreferredTotal - Flexible columns keep their preferred width (with horizontal scroll) until enough space exists for proportional expansion
- Ensures smooth, continuous width transitions during window resize
- Changed proportional distribution condition from
2.6.0 #
- FEAT: Added
autoFitColumnWidthcallback toFlutterTablePlus— override default auto-fit measurement for columns with custom cell builders- Return a width to override, or
nullto fall back to built-in text measurement - Useful for
statefulCellBuildercolumns with custom styles, padding, or text transformations - Result is clamped to per-column
minWidth/maxWidthconstraints
- Return a width to override, or
- FEAT: Added
TableColumnWidthCalculatorutility class for external column width measurementmeasureTextWidth()— measure a single text string with style, padding, and extra widthcalculateColumnWidth()— measure header + all body values and return optimal width- Follows the same
TextPainter-based pattern asTableRowHeightCalculator
2.5.0 #
- FEAT: Added
showRowCheckboxtoTablePlusCheckboxTheme— hide individual row checkboxes while keeping the header select-all checkbox- When
false, the checkbox column still renders with the header select-all checkbox, but row cells show no checkbox - Row selection is done via row tap only; defaults to
true(backward compatible) - Supported in normal rows, merged rows, and the
material3factory
- When
- BREAKING: Removed deprecated
cellBuilderfromTablePlusColumn- Use
statefulCellBuilderinstead — same functionality with additionalisSelectedandisDimparameters - Migration:
cellBuilder: (ctx, row) => ...→statefulCellBuilder: (ctx, row, _, _) => ...
- Use
2.4.2 #
- FEAT: Added
sortIconWidthtoTablePlusHeaderThemefor accurate tooltip overflow detection with custom sort icons- Sort icon is wrapped in
SizedBox(width: sortIconWidth)to enforce consistent layout - Header tooltip calculation uses
sortIconSpacing + sortIconWidthinstead of hardcoded24.0 - Tooltip now checks actual icon visibility — no space subtracted when
unsortedicon isnull - Default
16.0matches built-inSortIcons.defaultIconssize
- Sort icon is wrapped in
2.4.1 #
- FIX: Columns hitting
maxWidthno longer leave unused space at the end of the table- Proportional width distribution now uses iterative redistribution — when a flexible column is clamped to
maxWidth, the excess space is re-distributed to remaining flexible columns - Guarantees all available width is consumed when uncapped columns exist
- Proportional width distribution now uses iterative redistribution — when a flexible column is clamped to
2.4.0 #
- FEAT: Added
statefulCellBuildertoTablePlusColumn— custom cell builder withisSelectedandisDimstate- Signature:
Widget Function(BuildContext context, T rowData, bool isSelected, bool isDim) - Takes precedence over
cellBuilderwhen both are provided - Added
hasCustomCellBuildergetter andbuildCustomCell()helper onTablePlusColumn
- Signature:
- DEPRECATED:
cellBuilder— usestatefulCellBuilderinstead for access to row selection and dim state
2.3.5 #
- FIX: Last row no longer obscured by horizontal scrollbar
- Automatically reserves space equal to
scrollbarTheme.trackWidthwhen horizontal scrollbar is visible - No new parameters required — applied internally based on existing scroll/theme conditions
- Automatically reserves space equal to
2.3.4 #
- FEAT: Added
cellTapTogglesCheckboxtoTablePlusCheckboxTheme— expands checkbox tap area to the entire selection column cell, preventing accidental single-select when missing the checkbox
2.3.3 #
- FIX:
checkboxColumnWidthbelow defaultminWidth(50) no longer crashes- Selection column now sets
minWidthequal tocheckboxColumnWidth, preventingclamp(min > max)error - e.g.
checkboxColumnWidth: 45previously threwInvalid argument(s): 50.0
- Selection column now sets
2.3.2 #
- FIX: Checkbox column no longer expands proportionally with available width
- Added
maxWidthconstraint equal tocheckboxColumnWidthon the internal__selection__column - Ensures the selection column stays at its configured fixed width regardless of table size
- Added
- FIX: Fixed-width columns no longer cause space loss in proportional layout
- Columns whose
maxWidthcaps their preferred width are now excluded from proportional distribution - Remaining space is distributed only among flexible columns, eliminating the right-side gap
- Columns whose
2.3.1 #
- BREAKING: Extracted
TablePlusResizeHandleThemefrom flat fields onTablePlusHeaderTheme- Removed
resizeHandleWidth,resizeHandleColor,resizeHandleThickness,resizeHandleIndent,resizeHandleEndIndentfromTablePlusHeaderTheme - Added
TablePlusResizeHandleThemeclass withwidth,color,thickness,indent,endIndentandcopyWith - New composed property:
TablePlusHeaderTheme.resizeHandle(defaultconst TablePlusResizeHandleTheme()) - Consistent with existing
TablePlusHeaderBorderTheme/TablePlusHeaderDividerThemepattern
- Removed
- FIX: Hide vertical divider on column reorder drag feedback
- The floating header cell during drag-and-drop reorder no longer renders the right-edge vertical divider
- Added
showDividerparameter to_HeaderCell(defaulttrue, set tofalsefor feedback only)
2.3.0 #
- FEAT: Added
tapTargetSizetoTablePlusCheckboxTheme- Expands the checkbox tap/hover hit-test area without changing the visual checkbox size
- Configurable in logical pixels (e.g.,
tapTargetSize: 40gives a 40×40 hit area) - Defaults to
sizewhen not set — fully backward compatible - Applied to body rows, header select-all, and merged row checkboxes
- BREAKING: Refactored header border/divider into separate theme classes
- Removed
showVerticalDividers,showBottomDivider,dividerColor,dividerThicknessfromTablePlusHeaderTheme - Added
TablePlusHeaderBorderThemefor top/bottom horizontal borders (show,color,thickness) - Added
TablePlusHeaderDividerThemefor vertical column dividers withindent/endIndentsupport - New properties:
topBorder(default hidden),bottomBorder(default visible),verticalDivider(default visible) - Vertical dividers now rendered as
Stackoverlay instead ofBoxDecoration.border, enabling indent control
- Removed
- FEAT: Resize handle
indent/endIndent/thicknessthemingresizeHandleThicknesscontrols the visible indicator line width (default2.0)resizeHandleIndent/resizeHandleEndIndentinset the indicator from top/bottom edges
- FIX: Resize handle now centered on column boundary
- Previously the handle was positioned entirely inside the left column (
right: 0), making it asymmetric - Now uses a header-level
Stackoverlay withleft: cumulativeWidth - handleWidth / 2, giving equal hit area on both sides of the border - Visual indicator line renders at the exact column boundary center
ValueKeyper handle ensures stable state across rebuilds and column reorders
- Previously the handle was positioned entirely inside the left column (
2.2.0 #
- FEAT: Drag-to-select rows
enableDragSelectionparameter enables mouse drag row selection (Excel/Finder style)onDragSelectionUpdatecallback fires during drag with the dragged range row IDsonDragSelectionEndcallback fires once when drag ends- Auto-scroll when dragging near viewport edges (~60fps, speed proportional to edge proximity)
- 8px activation threshold prevents conflicts with existing tap/click gestures
- Works with uniform heights (O(1)), dynamic heights, and merged row groups
- Parent controls selection behavior (replace or additive) — consistent with UI-only philosophy
2.1.1 #
- IMPROVEMENT: Auto-scroll during column resize drag
- When dragging a resize handle near the viewport edge, the table automatically scrolls in that direction
- Scroll speed is proportional to pointer proximity to the edge (50px activation zone)
2.1.0 #
- FEAT: Column resizing support
resizableparameter enables drag-to-resize on column header edgesonColumnResizedcallback fires with(String columnKey, double newWidth)for persistence- Respects per-column
minWidth/maxWidthconstraints; selection column excluded
- FEAT: Resize handle theming in
TablePlusHeaderThemeresizeHandleWidth(default8.0) andresizeHandleColorproperties
- FIX:
minWidth/maxWidthconstraints now enforced in all layout calculation paths
2.0.2 #
- FIX:
showCheckboxColumn: falseinTablePlusCheckboxThemenow properly hides the checkbox column - IMPROVEMENT: Header select-all checkbox auto-hides when
onSelectAllis null
2.0.1 #
- FIX: Fixed header-body column width misalignment when table width exceeds total column widths
2.0.0 #
- BREAKING: Migrated from
Map<String, dynamic>to generic type parameter<T>FlutterTablePlus<T>accepts any data model typerowIdKey→rowId: String Function(T)dimRowKey/invertDimRow→isDimRow: bool Function(T)?TablePlusColumn<T>requiresvalueAccessor: (T) => dynamiccellBuilder,hoverButtonBuilder,calculateRowHeightsignatures useTinstead ofMaponCellChangedreceivesTinstead ofMapMergedRowGroup<T>parameterized with data typesummaryRowData→summaryBuilder: Widget? Function(String columnKey)?
1.17.2 #
- PERF: Eliminated full table rebuilds on mouse hover
- PERF: Cached total data height, row count, and visible columns computation
- PERF: Added overflow detection caching in
TablePlusCell - FIX: Added missing
TextPainter.dispose()inTableRowHeightCalculator
1.17.1 #
- FIX: Fixed scroll controllers being destroyed on every parent rebuild
- FIX: Improved scroll sync reliability in
SyncedScrollControllers
1.17.0 #
- IMPROVEMENT: Added
itemExtentBuilderfor improved scroll performance with large datasets (10,000+ rows)
1.16.7 #
- FEAT: Added
verticalOffsettoTablePlusTooltipThemefor customizable tooltip positioning
1.16.6 #
- BREAKING: Replaced
isDimRowcallback withdimRowKeyandinvertDimRow
1.16.5 #
- FEAT: Added dim row feature with
isDimRowcallback and theme support
1.16.4 #
- FIX: Rapid consecutive taps now correctly trigger multiple
onRowTapwhenonRowDoubleTapis null
1.16.3 #
- FEAT: Added
tooltipBuilderfor custom widget tooltips (priority:tooltipBuilder>tooltipFormatter> default) - FEAT: Enhanced tooltip timing with
exitDurationproperty and hover interaction support - FEAT: Intelligent tooltip positioning that adapts to available screen space
- FEAT: Added
CustomTooltipWrapperThemefor tooltip configuration
1.16.2 #
- FEAT: Added configurable
doubleClickTimetoTablePlusBodyTheme(default 500ms)
1.16.1 #
- FEAT: Added
isSelectedparameter toonRowSecondaryTapDowncallback - IMPROVEMENT: Enhanced
TablePlusScrollbarThemewith independent track/thumb styling (trackWidth,thickness,radius,thumbColor,trackBorder)
1.16.0 #
- BREAKING: Removed deprecated
TablePlusSelectionTheme- Selection styling →
TablePlusBodyTheme(selectedRowColor,selectedRowTextStyle) - Checkbox properties →
TablePlusCheckboxTheme - Row interaction colors →
TablePlusBodyTheme
- Selection styling →
- BREAKING:
onRowSecondaryTap→onRowSecondaryTapDownwithTapDownDetailsandRenderBox
1.15.6 #
- FEAT: Added
TooltipBehavior.onlyTextOverflow— tooltips only appear when text overflows - FIX: Fixed row hover colors not appearing due to Stack blocking
CustomInkWell - REFACTOR: Moved row interaction properties from
TablePlusSelectionThemetoTablePlusBodyTheme - DEPRECATED: Row interaction properties in
TablePlusSelectionTheme(removed in 1.16.0)
1.15.5 #
- FEAT: Added
TablePlusCheckboxThemewith Material 3WidgetStatePropertysupport - DEPRECATED: Checkbox properties in
TablePlusSelectionTheme
1.15.4 #
- BREAKING: Removed
TooltipBehavior.onOverflowOnly
1.15.3 #
- FEAT: Added
onCheckboxChangedcallback to distinguish checkbox clicks from row clicks - FEAT: Added checkbox color customization (
hoverColor,focusColor,fillColor,side) - FIX: Fixed tooltip null check error during column reordering
1.15.2 #
- FEAT: Added
tooltipFormattertoTablePlusColumnfor custom tooltip content - UPDATE: Minimum Flutter version changed to >=3.10.0
1.15.1 #
- FIX: Static analysis fixes
1.15.0 #
- BREAKING: Removed frozen column functionality (
frozenColumns,TablePlusFrozenTheme,TablePlusDividerTheme) - FEAT: Hover button system with
hoverButtonBuilder,HoverButtonPosition, andTablePlusHoverButtonTheme - FEAT: Enhanced expandable row functionality for merged row groups
1.14.2 #
- FEAT: Expandable summary rows for merged row groups
isExpandable,isExpanded,summaryRowDataonMergedRowGrouponMergedRowExpandTogglecallback
- FEAT: Added
summaryRowBackgroundColortoTablePlusBodyTheme - FIX: Fixed tooltip and height calculation in merged rows
1.14.1 #
- FEAT: Added
lastRowBorderBehaviortoTablePlusBodyTheme(never,always,smart) - FIX: Fixed sorting with merged rows
1.14.0 #
- FEAT: Frozen column divider with
TablePlusDividerTheme - FIX: Fixed layout overflow in constrained height containers
- FIX: Fixed vertical scrollbar track height calculation
1.13.2 #
- FIX: Fixed single selection mode — clicking selected row now correctly deselects
1.13.1 #
- FEAT: Added
calculateRowHeightcallback andTableRowHeightCalculatorutility
1.13.0 #
- BREAKING: Removed dynamic row height feature (
RowHeightMode,minRowHeight,TextHeightCalculator)
1.12.0 #
- FEAT: Dynamic row height with
RowHeightMode.dynamicandminRowHeight - FEAT: Improved sorting with merged rows
- REFACTOR:
MergedRowGroupusesrowKeysinstead oforiginalIndices - FIX: Fixed alternate row color logic and background color application
1.11.1 #
- IMPROVEMENT: Improved focus handling for editable cells
1.11.0 #
- FEAT: Merged row functionality with
MergedRowGroupandMergeCellConfig- Selection and editing support for merged cells
- Auto-save on focus loss for editable cells
- FEAT: Added
cellContainerPaddingtoTablePlusEditableTheme
1.10.1 #
- CHORE: Applied
dart format
1.10.0 #
- FEAT: Dynamic row height calculation based on content
- FEAT: Dynamic scrollbar visibility based on content height
1.9.0 #
- FEAT: Added
headerTooltipBehaviortoTablePlusColumn
1.8.0 #
- FEAT: Added
tooltipBehaviortoTablePlusColumn(always,onOverflowOnly,never) - DEPRECATED:
showTooltipOnOverflowin favor oftooltipBehavior
1.7.0 #
- FEAT: Added
noDataWidgetfor custom empty state display - IMPROVEMENT: Sorting auto-disabled when data is empty
1.6.2 #
- FEAT: Added
visibleproperty toTablePlusColumnfor column visibility control
1.6.1 #
- FEAT: Added
selectedTextStyletoTablePlusTheme
1.6.0 #
- FEAT:
decorationandcellDecorationinTablePlusHeaderTheme - FEAT: Granular row interaction colors in
TablePlusSelectionTheme(hoverColor,splashColor,highlightColor) - FEAT:
dividerThicknessinTablePlusHeaderTheme - FEAT:
rowIdKeyfor custom row identifier field - FEAT:
textOverflowproperty onTablePlusColumnwith auto-tooltip on ellipsis
1.5.0 #
- FEAT: Added
SelectionMode.singlefor single row selection
1.4.0 #
- FEAT: Disable column reordering with
onColumnReorder: null - FEAT: Disable sorting with
onSort: null
1.3.0 #
- FEAT: Enabled simultaneous selection and editing
1.2.0 #
- FEAT: Configurable sort cycle order via
sortCycle
1.1.2 #
- FEAT: Added
hintTexttoTablePlusColumnandhintStyletoTablePlusEditableTheme - FEAT: Added
onRowDoubleTapandonRowSecondaryTapcallbacks
1.1.1 #
- Updated README.md
1.1.0 #
- FEAT: Cell editing with
isEditable, per-columneditable,onCellChanged, andTablePlusEditableTheme
1.0.0 #
- Initial release — customizable table widget with synchronized scrolling, theming, sorting, selection, column reordering, and custom cell builders