Changelog¶
Changelog¶
All notable changes to this project will be documented in this file.
2.3.2 — 2026-08-03¶
Column resizing works again with vertical dividers turned off. Column geometry counted a divider after every column whether or not one is drawn, so the resize strips drifted further right with each column. No API change.
- Fixed: with
showVerticalDividersoff, the resize strip for column i sat i divider widths right of its boundary, over the next column's header, and dragging the visible boundary did nothing. The table width and the scroll-into-view target were off by the same phantom dividers (#62). - Fixed: a column stopped resizing after any change to the table settings. The gesture outlives the composition that started it and kept writing into the state holder those settings replaced.
Compare: v2.3.1...v2.3.2
2.3.1 — 2026-08-03¶
Keying a row no longer reads it. Compose asks a lazy list for keys across its nearest range — some 130
rows around a twenty-row viewport — and the table answered each one by resolving the row. On a paged
table that made every key lookup a data read, and since PagingMap.get is also what tells a pager
where the viewport is, the table reported a viewport six times the real one: the pager streamed and
cached around it, evicted the rows actually on screen, and re-read them in a loop that never settled.
Keys now come from the index, and the paged adapter answers a custom rowKey from the rows the paging
map already holds. rowKeyAt(index) is the new public entry point for that; rowKey still works and
the paged overloads gained no parameter.
- Fixed: the table resolved a row for every key a lazy list asked for, and Compose asks over its
nearest range — 130 rows from the top of the list, against the twenty on screen. On a paged table
that turned a key lookup into a data read:
PagingMap.getis what tells a pager where the viewport is, so the table reported a viewport of ~130 rows, the pager streamed and cached around it, and the rows actually being looked at were evicted and re-read in a loop (69 portion requests, 63 of them cancelled, from a stationary viewport). A key that does not need the item no longer resolves one, and the paged adapter answers a customrowKeyfrom the rows already loaded in the paging map — the same row the accessor would return, minus the access the pager acts on. Building the list no longer resolves a row the table is not rendering (#60). - Added:
rowKeyAt(index)on the core table entry points — a key-by-index lookup that supersedesrowKeywherever the table needs a key, so no row is resolved to produce one. Pass it (answering the same keyrowKeywould) when a read from your loader has a side effect the table should not trigger while keying rows. The paged overloads set it themselves and gained no parameter. - Updated:
ua.wwind.paging:paging-coreto 2.3.1 (from 2.3.0), which fixes the pager half of the same loop — a window oscillating between the two ends of a read span wider than it (paging-kmp#45). It is animplementationdependency, so you declare your own version; either fix alone stops the loop.
Compare: v2.3.0...v2.3.1
2.3.0 — 2026-08-02¶
The paged table stops recomposing on every pager emission. paging-core is built without the Compose
compiler plugin — that is what keeps it usable from plain Kotlin and free of a Compose runtime
dependency — and the compiler infers stability only for classes it compiles itself, so it treated
PagingData and the types around it as unstable. Strong skipping compares an unstable parameter by
instance rather than by value, and the pagers publish a fresh snapshot per state change, so the paged
Table overloads recomposed on every emission however little of the window had actually changed.
paging-core 2.3.0 ships a stability configuration declaring those types stable; table-paging now
compiles against it, and nothing about the API or what you see changes.
- Fixed: the paged
Tableoverloads recomposed on every emission from a pager.table-pagingnow compiles with a Compose stability configuration for thepaging-coretypes, which putsitemsback on value comparison under strong skipping. A stability configuration governs only the module doing the compiling, so composables of your own that take aPagingData— a screen, a view-model-bound wrapper, aLazyColumnusinghandleLoadState— need the same file in your build; the table-paging docs show how to wire it up. - Updated:
ua.wwind.paging:paging-coreto 2.3.0 (from 2.2.7). It is animplementationdependency, so you declare your own version and this does not force the upgrade; the typestable-pagingconsumes are unchanged between the two. Note that 2.3.0 does carry breaking changes of its own —Pagergained positional parameters and aconcurrency = 4default, and themacosX64target was dropped — none of which this library touches.
Compare: v2.2.0...v2.3.0
2.2.0 — 2026-07-31¶
The table stops depending on material-icons-extended and draws its own set instead. That artifact is
deprecated upstream, pinned at 1.7.3 and no longer tracking Compose releases, and it shipped roughly two
thousand icons so the table could draw seventeen; those seventeen are now vendored as TableIcons and
public, so the defaults behind TableHeaderDefaults.icons() can be referenced and reused. Nothing renders
differently. Alongside it, two long-standing viewport bugs are fixed: the active-filter chips no longer
scroll away with the content, and the last column's resize strip stays reachable as the column grows.
- Added:
TableIcons— the icon set the library draws, vendored and public, so the defaults behindTableHeaderDefaults.icons()can be referenced and reused directly. - Removed: the dependency on
org.jetbrains.compose.material:material-icons-extended. It was pinned at 1.7.3, is deprecated upstream and no longer tracks Compose releases; it also shipped roughly two thousand icons so the table could draw seventeen. Icons render exactly as before — the vendored path data is copied from that artifact, not redrawn. It was animplementationdependency, so it was never on any consumer's compile classpath and this cannot break a build; if you relied on it reaching your runtime classpath transitively, declare it yourself going forward. - Fixed: the active-filters header scrolled with the table. It was laid out inside the horizontally scrolling container and sized to the table width, so scrolling a wide table right carried the chips past the left edge and its overflow arrows never appeared; it is now a sibling of the scroll container, sized to the viewport. The header also renders nothing at all when no filter is active, instead of leaving a bare divider above the table.
- Fixed: widening the last column pushed its resize strip out of reach. The boundary sits at the right edge of the content, so the growth happened off screen and the pointer ran out of travel within a few pixels of the window edge; the overshoot is now dispatched to the horizontal scroll, and holding the pointer within 24dp of that edge keeps growing the column and scrolling under it. The last strip is also 6dp wide rather than 3dp, having no content to its right to hang over.
Compare: v2.1.1...v2.2.0
2.1.1 — 2026-07-23¶
- Added:
FormatDialogContent— the conditional-formatting editor as portable content, so it can be hosted in any container (a custom dialog host, a side panel, a split pane) instead of only the built-inAlertDialog.FormatDialogis unchanged; both reuse the same internal title, buttons and body blocks. - Added:
FormatDialogColorsandFormatDialogDefaults.colors()(defaulting to the Material3AlertDialogdefaults), and acolorsparameter onFormatDialogandFormatDialogContent.FormatDialogContentapplies the title and text content colors itself; the container owns background and elevation. - Fixed: reordering a row within a block stopped committing once the block had been moved as a whole. The nested
reorderable's
onSettlecaptured a stale base offset, so the move mapped outside the block and was rejected; the base offset is now read live. - Fixed: the Condition tab of the formatting dialog centered its field list vertically, leaving a gap under the tabs when the list was shorter than the dialog; the list now top-aligns.
- Updated: Android Gradle Plugin to 9.3.1 (from 9.3.0).
Compare: v2.1.0...v2.1.1
2.1.0 — 2026-07-22¶
TableState splits into state holders. It had grown to carry four unrelated jobs at once, and the detekt debt
2.0.0 left behind was pointing straight at it; column layout, selection and editing now live in holders of their
own, reached through the same state object. Nothing breaks — every displaced member stays on TableState,
deprecated, with the ReplaceWith migration the IDE applies for you, exactly the cycle 2.0.0 used for
@ExperimentalTableApi. Because 2.0.0 shipped one day earlier, the 2.0 migration guide carries this mapping too,
so an upgrade from 1.x lands on the final names in a single pass.
- Added:
TableStatenow composes three state holders of its own, reached asstate.columns,state.selectionandstate.editing.TableColumnsStateowns column order, width overrides and the measurements auto-fit works from;TableSelectionStateowns the focused row, the checked rows and the selected cell;TableEditingStateowns the edited row and column together with the edit callbacks. Sorting, grouping and filtering stay onTableStateitself — they belong to the table as a whole rather than to one of the three. New public types:TableColumnsState,TableSelectionState,TableEditingState. - Deprecated: the 31
TableStatemembers those holders took over. Every one stays as a forwarder carrying aReplaceWithmigration, so existing code keeps compiling and the IDE rewrites each call site for you; they are removed in the next major. Columns:columnOrder→columns.order,columnWidths→columns.widths,columnContentMaxWidths→columns.contentMaxWidths,columnHeaderWidths→columns.headerWidths,autoWidthAppliedForEmptyandautoWidthAppliedForData→ the same names undercolumns,resolveColumnWidth→columns.resolveWidth,moveColumn→columns.move,setColumnOrder→columns.setOrder,resizeColumn→columns.resize,setColumnWidths→columns.setWidths,updateMaxContentWidth→columns.updateMaxContentWidth,setColumnWidthToMaxContent→columns.fitToContent,recalculateAutoWidths→columns.recalculateAutoWidths. Selection:selectedIndex,checkedIndices,selectedCell,focusRow,toggleCheck,toggleCheckAllandselectCell→ the same names underselection, andtoggleSelect→selection.toggleRow(named for what it toggles, next totoggleCheck). Editing:editingRow→editing.rowIndex,editingColumn→editing.column,onRowEditStart,onRowEditCompleteandonEditCancel→ the same names underediting,startEditing→editing.start,tryCompleteEditing→editing.tryComplete,completeCurrentCellEdit→editing.completeCurrentCell,cancelEditing→editing.cancel.TableState.SelectedCelldeliberately does not move: a nested type cannot be re-exported under a new name the way a function can, so moving it would break every consumer that names it. The 2.0 migration guide carries the full mapping, so an upgrade from 1.x lands on the final names in one pass rather than two. - Changed: the
TooManyFunctionsdetekt debt baselined in 1.11.0 is paid down rather than suppressed. The split above resolvesTableState;SampleViewModelgives up two stateless helpers that never read its state (matchesPersonforwarded toPersonFilterMatcher,buildFormatFilterDatais nowPersonFormatFilterData.build). The rule itself stops counting private and deprecated members, with the reasoning recorded inconfig/detekt/detekt.yml: a private helper is whatCyclomaticComplexMethodandLongMethodask a class to extract, and a deprecated member is a forwarder on its way out, so counting either makes the rules contradict each other. - Changed: the
ComplexConditiondebt baselined in 1.11.0 is paid down too — three conditions intable-formatandtable-samplerewritten as guard clauses or named predicates, with no behaviour or API change. Neither module carries a baseline any more.
Compare: v2.0.0...v2.1.0
2.0.0 — 2026-07-22¶
The API is stable. Nothing in the library carries @ExperimentalTableApi any more — Table,
EditableTable and the table-paging adapter have been in production use since 2025, the surface has
settled, and the opt-in was outliving the uncertainty it was meant to signal. This release also clears
the technical debt that had accumulated behind that marker: the deprecated members carried through the
1.x line are gone, the public composables now follow the Compose conventions for parameter order,
naming and modifier handling, and the two filter apply paths that had silently drifted apart are
reconciled. Every break is listed below with its migration; see the
2.0 migration guide for the
consolidated version.
- Changed (breaking): the table API is stable and requires no opt-in.
@ExperimentalTableApiis offTable,EditableTableand thetable-pagingadapter — delete the@OptIn(ExperimentalTableApi::class)annotation from your call sites. The marker itself is kept for one release, deprecated, so existing opt-ins still compile with a warning that names the fix; it is removed in the next major. - Removed (breaking): the members deprecated during 1.x. Each has carried a
ReplaceWithmigration for at least one minor release.TableSettings.isDragEnabled→rowReorderEnabled.TableSettings.fixedColumnsCount→pinnedColumnsCount;TableSettings.fixedColumnsSide→pinnedColumnsSide; theFixedSidetypealias →PinnedSide.TableDimensions.fixedColumnDividerThickness→pinnedColumnDividerThickness.TableRowContext.isGroup→isInRowBlock.- The
TableSettings.isRowReorderEnabledextension goes with them: it existed only to OR the new and deprecated reorder flags together, so withisDragEnabledgone it is exactlysettings.rowReorderEnabled.isInteractionLockByRowReorderEnabledis unaffected and stays.
- Changed (breaking):
CustomFilterRenderer.RenderPanelnow returnsUnitand takes aCustomFilterPanelActionsparameter instead of returningTableFilterType.CustomFilterActions. It emits the filter panel UI, so returning a value tripped the Compose naming/emitter rules — a@Composablethat both draws and returns is exactly what the convention warns against. Implementers must add thepanelActionsparameter (placed aftertableData, ahead of theonDismiss/onChangelambdas), drop the return type, and replacereturn <actions>withpanelActions.set(<actions>)where the oldreturnwas. A panel that publishes nothing now leaves the host's Apply/Clear buttons as no-ops. New public type:CustomFilterPanelActions. - Changed (breaking): parameter order on five public composables now follows the Compose convention — required
parameters,
modifier, optional parameters, trailing lambda. Callers using named arguments are unaffected; positional callers must update.FilterDropdownFieldandFilterDropdownAnyField:valuesandonClickmove ahead ofgetTitleandplaceholder.TableActiveFilters:modifiermoves ahead ofstrings.rememberCustomization:matchesmoves ahead ofkey. Note thatkeyisAny?, so a positional call passingmatchesthird would still compile while binding the lambda tokey— check these by hand.FormatDialogTabRow:createTabmoves ahead ofmodifier; thecontenttrailing lambda stays last.
- Added (breaking):
FormatDialogDesignTab,FormatDialogConditionTabandFormatDialogFieldTabnow take amodifier, applied to the component's own root node — the three tab panels could not be sized, padded or positioned by their caller at all. It sits where the Compose convention puts it, as the first optional parameter, so it lands ahead of the existingscrollbarRenderer: named-argument callers are unaffected, a positional call passingscrollbarRenderermust update. Default behaviour is unchanged. - Changed (breaking): event callbacks are named for the event in present tense, per the Compose convention
(
onClick,onValueChange) — past-tense names described the action after the fact. Unlike the reorder above this breaks callers using named arguments, but the compiler flags every one of them.EditableTable:onEditCancelled→onEditCancel. TheTableStateedit-state property of the same name is renamed with it, so the parameter and the state it writes still read alike.FormatDialog:onRulesChanged→onRulesChange.
- Changed:
FilterDropdownField,FilterDropdownAnyFieldandFormatDialogTabRownow apply theirmodifierto their own root node instead of to an inner element. Positioning, sizing and padding modifiers therefore affect the whole component — previously they reached only the text field, or were applied twice. Source-compatible, and the components render as before at every call site in this repository. - Fixed: pressing Apply and letting auto-apply fire now produce the same filter. Every
remember*FilterStatefactory implemented its emit rule twice — once in the debounced auto-apply effect, once inapplyFilter— and the two copies had drifted, so identical input produced different filters depending on which path ran. Each filter now resolves through a single pure function, so the paths cannot disagree. Two behaviours change as a result:- Number: invalid, incomplete or inverted input (
tobelowfrom) is an error — the filter is not applied, the input is preserved so it can be corrected, and the field is flagged. Only empty input clears the filter. The UI reads oneisErrorsource of truth instead of recomputing its own. - Enum: an empty selection combined with
IS_NULL/IS_NOT_NULLstays applied instead of being dropped by the debounced path. - Date, text and boolean are merged with no behaviour change; an incomplete date range still clears, which is deliberate and now documented.
- Number: invalid, incomplete or inverted input (
- Changed: the quality debt recorded in 1.11.0 is paid down rather than suppressed. 223 of the 234 baselined detekt and compose-rules findings are fixed; 11 remain baselined. The breaking convention changes above are the consumer-visible part of that work. Unit tests were added over the filter resolve functions.
- Updated: Android Gradle Plugin to 9.3.0 (from 9.2.1).
Compare: v1.11.0...v2.0.0
1.11.0 — 2026-07-21¶
- Added: row blocks — a new
rowBlocksparameter onTable,EditableTableand thetable-pagingadapter makes adjacent rows sharing a block id render and drag as one unit, with an optional header band, reordering within a block, and one key-based commit event per gesture. SupersedesonRowMovewhile declared, and is mutually exclusive withstate.groupBy. See the Row blocks guide.- New API:
RowBlocks,RowBlockMove,RowWithinBlockMove,RowBlockHeaderScope,TableState.rowBlocksSuppressedByGroupBy, theMutableList.applyRowBlockMove()/applyRowReorderWithinBlock()andList.sortedWithinRowBlocks()/filteredWholeRowBlocks()helpers, and theTableDimensions.rowBlockSpacing/TableColors.rowBlockContainerColortheming parameters.
- New API:
- Changed:
state.setSort()is now a warning no-op while row reorder is enabled, matching the existinginitialSortnormalization — under an active sort a reorder would be unobservable. - Deprecated:
TableRowContext.isGroup, renamed toisInRowBlock. A read-only forwarder keeps existing customizations compiling, and the flag is now actually set — true for rows inside a row block, soTableCustomizationcan style block members (previously alwaysfalse). - Fixed: table width no longer freezes when
ColumnSpec.visiblechanges after the first render. The visible column list was a plain field, so Compose served a cached width and revealed columns stayed off screen and out of horizontal scroll range while the header laid them out. - Added: the module's first tests — row blocks end to end (derivation, managed drag and commit, data helpers under seeded property harnesses, paged sources, groupBy suppression, selection and editing remap), the row-to-unit index, and table width across column hide, show and resize.
- Changed: the Android build migrated to the AGP 9.2.1 KMP library plugin
(
com.android.kotlin.multiplatform.library) — AGP 9 dropped Kotlin Multiplatform support fromcom.android.library.- Publishing is unaffected: artifact ids and repository layout are unchanged. Only the Gradle variant names
change (
release*→android*), which consumers do not match on. table-sampleis split into the multiplatform module plus a thintable-sample-androidapplication module, since a KMP target andcom.android.applicationcan no longer share one module.
- Publishing is unaffected: artifact ids and repository layout are unchanged. Only the Gradle variant names
change (
- Changed: quality tooling reworked.
- ktlint-gradle replaced by Spotless driving ktlint
1.8.0; the tree was reformatted mechanically, with no behaviour change. - detekt moved to
dev.detekt2.0.0-alpha.5and now actually analyses the KMP source sets — the aggregate task had been reporting success on NO-SOURCE. The 171 pre-existing findings are recorded in per-source-set baselines. io.nlopez.compose.rulesadded to detekt for Compose-specific linting; its 63 findings are baselined, as most fixes would change the published API.
- ktlint-gradle replaced by Spotless driving ktlint
- Changed: the Gradle daemon JVM is pinned to Java 17 (Temurin) via
gradle/gradle-daemon-jvm.properties, matching thejvmToolchain(17)used for compilation and the JDK installed on CI. - Removed: the
-Xcontext-parameterscompiler flag — context parameters are stable in Kotlin 2.4.0. - Updated: Kotlin to 2.4.10 (from 2.4.0).
- Updated: Android Gradle Plugin to 9.2.1 (from 8.13.2).
compileSdkandtargetSdkstay at37. - Updated: library dependencies.
- AndroidX Lifecycle upgraded from
2.10.0to2.11.0. - Collections Immutable upgraded from
0.5.0to0.5.1.
- AndroidX Lifecycle upgraded from
- Updated: Build tooling dependencies.
- BuildKonfig upgraded from
0.21.2to0.22.0. - Spotless upgraded from
8.7.0to8.8.0. - Compose Rules upgraded from
0.6.2to0.6.3.
- BuildKonfig upgraded from
Compare: v1.10.0...v1.11.0
1.10.0 — 2026-06-24¶
- Updated: Kotlin to 2.4.0 (from 2.3.21).
- Updated: Compose Multiplatform to 1.11.1 (from 1.11.0).
- Updated: KotlinX dependencies.
- Collections Immutable upgraded from
0.4.0to0.5.0.
- Collections Immutable upgraded from
- Updated: Logging dependencies.
- SLF4J upgraded from
2.0.17to2.0.18.
- SLF4J upgraded from
- Updated: UI library dependencies.
- Color Picker upgraded from
1.1.4to1.2.0.
- Color Picker upgraded from
- Updated: Build tooling dependencies.
- BuildKonfig upgraded from
0.20.0to0.21.2. - Maven Publish plugin upgraded from
0.36.0to0.37.0. - Compose Stability Analyzer upgraded from
0.7.4to0.10.0.
- BuildKonfig upgraded from
Compare: v1.9.0...v1.10.0
1.9.0 — 2026-05-14¶
- Updated: Compose Multiplatform to 1.11.0 (from 1.10.3).
- Compose Material3 upgraded from
1.10.0-alpha05to1.11.0-alpha07.
- Compose Material3 upgraded from
- Updated: KotlinX dependencies.
- Coroutines upgraded from
1.10.2to1.11.0. - Datetime upgraded from
0.7.1to0.8.0.
- Coroutines upgraded from
- Updated: Android SDK targets.
compileSdkandtargetSdkbumped from36to37.
- Updated: Build tooling dependencies.
- Gradle wrapper upgraded from
9.4.0to9.5.0. - Compose Hot Reload upgraded from
1.1.0to1.1.1. - BuildKonfig upgraded from
0.19.0to0.20.0.
- Gradle wrapper upgraded from
Compare: v1.8.3...v1.9.0
1.8.3 — 2026-05-05¶
- Updated: Kotlin to 2.3.21 (from 2.3.20).
- Updated: Build tooling dependencies.
- Compose Hot Reload upgraded from
1.0.0to1.1.0. - BuildKonfig upgraded from
0.17.1to0.19.0. - Dokka upgraded from
2.1.0to2.2.0. - Maven Publish plugin upgraded from
0.35.0to0.36.0. - Compose Stability Analyzer upgraded from
0.7.2to0.7.4.
- Compose Hot Reload upgraded from
- Updated: UI library dependencies.
- Reorderable upgraded from
3.0.0to3.1.0. - Color Picker upgraded from
1.1.3to1.1.4.
- Reorderable upgraded from
Compare: v1.8.2...v1.8.3
1.8.2 — 2026-04-08¶
- Fixed: Header content state and tooltip behavior for truncated titles.
TruncationTooltipBoxnow accepts a composable title provider for better content flexibility.- Header content renders via
movableContentOfto prevent state loss on recomposition. - Updated tooltip position provider usage for consistent tooltip placement.
- Updated: Kotlin to 2.3.20.
- Kotlin version bumped from 2.3.10 to 2.3.20.
- Refactored: Compose dependencies migration to version catalog.
- Replaced direct Compose extension dependencies with
libs.versions.tomlaliases. - Aligned module usage across convention and sample Gradle scripts.
- Replaced direct Compose extension dependencies with
- Updated: Gradle wrapper to 9.4.0 version.
Compare: v1.8.1...v1.8.2
1.8.1 — 2026-03-25¶
- Added: Improved mobile platform interactions for column headers.
- Column headers now have draggable handles enabled on mobile for reordering operations.
- Hover interactions are now disabled on mobile platforms to prevent touch gesture conflicts.
- Changed:
enableDragToScrollsetting now defaults to platform-aware value.- Default value changed from
truetogetPlatform().isMobile()- enabled on mobile, disabled on desktop. - Drag-to-scroll remains available on desktop when explicitly enabled in settings.
- Default value changed from
- Changed: Removed incompatible normalization between row reorder and drag-to-scroll.
rowReorderEnabledandenableDragToScrollcan now be enabled simultaneously.- Previously, enabling row reorder would force disable drag-to-scroll; this behavior has been removed.
- Updated: Sample app settings sidebar to reflect new interaction options.
- Drag to scroll and row reorder toggles are now independent controls.
Compare: v1.8.0...v1.8.1
1.8.0 — 2026-03-25¶
- Added: Public row reordering API for table rows powered by Reorderable.
- Added
onRowMovecallback toTableandEditableTablefor handling row position changes. - Added
TableItemScopeandTableCellScopeto expose item-scoped drag helpers inside cell content. - Added
Modifier.draggableHandle()andModifier.longPressDraggableHandle()for building custom row drag handles directly incell { ... }. - Row reordering now works for both regular lazy tables and embedded table bodies.
- Added
- Changed: Table cell DSL now uses Kotlin context parameters.
ColumnSpec.celland thecell { ... }builder now run withcontext(TableCellScope).- Existing cell implementations usually continue to work unchanged, while advanced cell content can now access row-scoped helpers without extra parameters.
- Changed: Row reorder mode now normalizes incompatible interactions more explicitly.
TableSettings.rowReorderEnabledis now the primary setting name; deprecatedisDragEnabledremains supported for compatibility.- When row reorder mode is enabled,
enableDragToScrollis forced tofalseandinitialSortis ignored. - Sorting and grouping interactions are disabled while row reorder mode is active to avoid conflicting gestures and state.
- Fixed: Number filter source text formatting in
NumberFilterState.- Improves formatting consistency for number filter source values.
- Updated: Compose Multiplatform and related dependencies.
- Compose Multiplatform upgraded from
1.10.2to1.10.3. - AndroidX Lifecycle upgraded from
2.9.6to2.10.0.
- Compose Multiplatform upgraded from
Compare: v1.7.15...v1.8.0
1.7.15 — 2026-03-13¶
- Fixed: Disabled SelectionContainer during row editing to prevent cross-hierarchy text selection issues.
- When text selection is enabled and a row is being edited, SelectionContainer is now disabled.
- Resolves issues with popup-based editors on Desktop targets where text selection could interfere with editing.
- Fixed: Prevent parent click activation on Space key in edit cell focus synchronization.
- Added key event handler to prevent Space key from being interpreted as click activation on parent elements.
- Improves keyboard navigation experience in editable tables.
- Updated: Compose Multiplatform upgraded to 1.10.2.
- AndroidX Activity Compose updated from 1.12.4 to 1.13.0.
- Kermit updated from 2.0.8 to 2.1.0.
- Paging for KMP updated from 2.2.6 to 2.2.7.
- Ktlint Gradle updated from 14.0.1 to 14.2.0.
Compare: v1.7.14...v1.7.15
1.7.14 — 2026-02-28¶
- Fixed: Crash caused by ScrollState handling in dropdown menu.
- Removed scrollbar renderer from
FormatDropdownFieldand related components. - The scrollbar state management was causing crashes in certain scenarios.
- Format dialog now works reliably without custom scrollbar integration in dropdowns.
- Removed scrollbar renderer from
- Changed: Refactored FormatDialog data classes to separate files.
EditFormatRulemoved toua.wwind.table.format.datapackage with@Immutableannotation.FormatDialogSettingsmoved toua.wwind.table.format.datapackage with@Immutableannotation.- Improves code organization and stability for Compose.
- Changed: Added button in FormatDialog now uses FloatingActionButton instead of IconButton.
- Provides better visual prominence and Material 3 compliance.
- Close button now uses explicit close icon for clarity.
- Updated: Sample app import for
FormatDialogSettingsto use new package location.
Compare: v1.7.13...v1.7.14
1.7.13 — 2026-02-18¶
- Added: Null-check filter support (IS_NULL / IS_NOT_NULL) for text, number, and date filters.
- Added
FilterConstraint.isNullCheck()extension function to identify null-checking constraints. - Added
TableFilterState.isActive()extension function to determine if a filter should be shown as active in the UI. - Filter UI now hides input fields when null constraint is selected (text, number filters).
- Previously, filters with null constraints would not display properly in active filter chips.
- Added
- Fixed: Active filter chips now correctly display null-check filters.
- Previously, filters with IS_NULL/IS_NOT_NULL constraints had empty values list and were not shown as active.
- Now these filters are properly recognized as active and displayed in the active filters row.
- Changed: Active filters row now uses scrollable LazyRow with navigation arrows.
- "Clear all" chip is now fixed on the left while filter chips can scroll horizontally.
- Left/right arrow buttons appear when filters overflow the available width.
- Improves usability when many filters are active simultaneously.
- Added: Date filter support in conditional formatting dialog.
- Full date filter UI with date picker, constraint selection, and range support.
- Supports IS_NULL and IS_NOT_NULL constraints for conditional formatting rules.
- Added: Vertical scrollbar support for formatting dialogs.
- New
VerticalScrollbarRendererandVerticalScrollbarStatefor custom scrollbar rendering. - Optional
scrollbarRendererparameter added toFormatDialogandFormatDialogConditionTab. - Enables proper scrollbar integration in desktop environments.
- New
- Changed: Updated Kotlin to 2.3.10 and Compose Multiplatform to 1.10.1.
- AndroidX Activity Compose updated from 1.12.2 to 1.12.4.
- Paging for KMP updated from 2.2.3 to 2.2.6.
- Liquid updated from 1.1.0 to 1.1.1.
- Kover (code coverage) updated from 0.9.4 to 0.9.7.
Compare: v1.7.12...v1.7.13
1.7.12 — 2026-01-21¶
- Fixed: Prevent redundant scroll to top when sort state is unchanged.
- Added state tracking with
rememberSaveableto detect actual sort changes. - Previously, any
LaunchedEffectre-execution would trigger scroll to top, even when sort state remained the same. - Now, scroll to top only occurs when the sort state actually changes, improving user experience during recomposition.
- Added state tracking with
Compare: v1.7.11...v1.7.12
1.7.11 — 2026-01-06¶
- Fixed: Header widths are now preserved during auto-width recalculation.
- Added
columnHeaderWidthsmap to store header measurements separately from row measurements. - When
recalculateAutoWidths()is called, header widths are retained and used as base values. - Previously, calling
recalculateAutoWidths()would clear all measurements including headers, causing columns to lose header-based sizing since headers only measure once on initial render.
- Added
Compare: v1.7.10...v1.7.11
1.7.10 — 2026-01-06¶
Add debug logging for column auto-width measurement
- Add verbose-level logging to track auto-width calculation flow
- Log width updates with source identification (Header/Row[N])
- Log auto-width reset and recalculation triggers
- Log computed widths with measured/base/final values
- Log phase transitions (empty table / first data)
Tag: TableAutoWidth
Compare: v1.7.9...v1.7.10
1.7.9 — 2026-01-06¶
- Fixed: trigger header width re-measurement when title text changes. Previously, headers with lazy-loaded content would only measure once with empty/initial text, causing incorrect column widths on double-click resize.
Compare: v1.7.8...v1.7.9
1.7.8 — 2026-01-06¶
- Fixed: Fast Filters row now only displays when at least one visible column has a filter configured.
- Previously, the row would show even when no columns had filters, resulting in an empty filters row.
- Now requires both
showFastFilters = trueAND at least one visible column with a non-null, non-disabled filter. - Columns with
nullorDisabledTableFilterare excluded from the visibility check.
Compare: v1.7.7...v1.7.8
1.7.7 — 2026-01-05¶
- Added: Configurable divider visibility settings in
TableSettings.showVerticalDividerscontrols vertical dividers between columns (default:true).showRowDividerscontrols horizontal dividers between rows (default:true).showHeaderDividercontrols horizontal divider below the header (default:true).showFastFiltersDividercontrols horizontal divider below the fast filters row (default:true).
- Added: Validation for
TableDimensionsdivider thickness values.dividerThicknessandpinnedColumnDividerThicknessmust be at least1.dp.- Invalid values now throw
IllegalArgumentExceptionwith descriptive message.
- Fixed: Pinned footer not visible when table body content is large.
- Changed footer rendering from sequential Column layout to overlay positioning using Box with
Alignment.BottomStart. - Added bottom padding to body content to prevent overlap with footer.
- Changed footer rendering from sequential Column layout to overlay positioning using Box with
- Changed: Refactored
EditableTableto reduce cyclomatic complexity.
Compare: v1.7.6...v1.7.7
1.7.6 — 2026-01-05¶
- Added:
borderparameter toTableandEditableTablecomposables for customizable outer table border.- Accepts
BorderStroke?to fully customize border appearance (color, thickness, brush). null(default) applies theme-based border usingdividerThicknessandMaterialTheme.colorScheme.outlineVariant.TableDefaults.NoBordersentinel value disables the outer border entirely.- Paging
Tablefunctions updated with the sameborderparameter for API consistency.
- Accepts
Compare: v1.7.5...v1.7.6
1.7.5 — 2026-01-03¶
- Added:
enableTextSelectionsetting inTableSettingsfor optional text selection support in table rows.- When enabled, wraps the table body in Compose
SelectionContainerto allow users to select and copy text content. - Defaults to
falseto preserve existing behavior and avoid interfering with row click/selection interactions. - Useful for data display tables where users need to copy cell values without custom clipboard handling.
- When enabled, wraps the table body in Compose
Compare: v1.7.4...v1.7.5
1.7.4 — 2026-01-03¶
- Changed: Updated Kotlin to 2.3.0 and related tooling dependencies.
- Kotlin version bumped from 2.2.21 to 2.3.0.
- AndroidX Activity Compose updated from 1.12.0 to 1.12.2.
- Kover (code coverage) updated from 0.9.3 to 0.9.4.
- Compose Stability Analyzer updated from 0.6.0 to 0.6.6.
Compare: v1.7.3...v1.7.4
1.7.3 — 2025-12-05¶
BREAKING CHANGES:
- Changed: Cell lambda signature updated to include
tableDataparameter for consistent API across all table components.ColumnSpec.cellsignature changed from@Composable BoxScope.(T) -> Unitto@Composable BoxScope.(T, E) -> Unit.- All cell content now receives the table data parameter, matching the signature of
header,footer, andeditCell. - Provides access to shared state within regular cell rendering, enabling dynamic behavior based on table-wide context.
- Migration: Update all
cell { item -> ... }declarations tocell { item, tableData -> ... }orcell { item, _ -> ... }if table data is not needed. - Example:
Benefits of this change:
- Consistent lambda signatures across all table component slots (cell, header, footer, editCell).
- Cells can now access shared table state for dynamic rendering without prop drilling.
- Better alignment with the table data pattern introduced in v1.7.1.
- Enables more flexible cell customization based on table-wide context.
Compare: v1.7.2...v1.7.3
1.7.2 — 2025-12-04¶
- Added:
shapeparameter to pagingTablecomposables for customizable table surface shape (default:RoundedCornerShape(4.dp)).- Aligns paging module API with core table module for consistent shape customization.
- Both overloads of paging
Tablefunction now support shape parameter.
- Added: New paging
Tablecomposable overload withtableDataparameter for shared state access.- Enables passing table data to headers, footers, and edit cells in paging tables.
- Generic parameter
Erepresents table data type, consistent with core table API. - Provides full feature parity with core
Tablecomposable for table data customization.
Compare: v1.7.1...v1.7.2
1.7.1 — 2025-12-02¶
- Added: Support for custom table data (
tableDataparameter) that can be shared across headers, footers, filters, and edit cells.- New
Tableoverload that acceptstableDataparameter for passing shared state to table components. - Generic parameter
Enow represents table data type instead of edit state type for improved flexibility. - Headers, footers, and edit cells receive table data as a parameter, enabling access to shared state during rendering.
- Filters (custom, fast, and panel) receive table data for enhanced customization and state synchronization.
EditableTableparameter renamed fromeditStatetotableDatato better reflect its purpose.
- New
- Changed: Enhanced column specification API for table data support.
ColumnSpec.headersignature changed from@Composable () -> Unitto@Composable (E) -> Unit.ColumnSpec.footersignature changed from@Composable BoxScope.() -> Unitto@Composable BoxScope.(E) -> Unit.ColumnSpec.editCelldocumentation updated to clarify table data parameter usage.ReadonlyTableColumnsBuildernow generic over table data typeEfor consistent API surface.TableFilterType.Custominterface methods updated to receive table data parameter.
- Changed: Documentation improvements and sample app refactoring.
- README updated with comprehensive table data usage examples and best practices.
- Sample app refactored to demonstrate table data patterns with centralized validation logic.
- New utility classes added:
PersonValidator,PersonFilterMatcher,PersonFilterStateFactory,DefaultFormatRulesProvider, andPersonSorter. PersonTableDatamodel introduced in sample to showcase shared state management across table components.
Compare: v1.7.0...v1.7.1
1.7.0 — 2025-12-02¶
- Added: Table footer support with full customization and pinning capabilities.
- New
footerDSL inColumnSpecbuilder for defining per-column footer content. TableSettings.showFooterenables footer row rendering below table body.TableSettings.footerPinnedcontrols whether footer stays pinned at the bottom (non-embedded tables only).- Footer respects pinned column configuration for consistent horizontal scrolling behavior.
TableColorsextended withfooterContainerColorandfooterContentColorproperties.TableDimensionsextended withfooterHeightproperty for customizable footer sizing.
- New
- Changed: Terminology update from "fixed" to "pinned" columns for better clarity.
TableSettings.fixedColumnsCount→pinnedColumnsCount(deprecated withReplaceWithmigration).TableSettings.fixedColumnsSide→pinnedColumnsSide(deprecated withReplaceWithmigration).FixedSideenum →PinnedSideenum (deprecated as typealias for backward compatibility).FixedColumnState→PinnedColumnState(internal state class renamed).TableDimensions.fixedColumnDividerThickness→pinnedColumnDividerThickness(deprecated withReplaceWithmigration).- All internal references updated to use "pinned" terminology throughout the codebase.
- Changed: Refactored
TableDefaultsinto a dedicated object with factory methods.- Extracted from
TableColors.ktto newTableDefaults.ktmodule for better organization. TableDefaults.colors()composable for creating color schemes with Material 3 theme defaults.TableDefaults.standardDimensions()for comfortable spacing and standard sizes (52dp rows, 56dp header).TableDefaults.compactDimensions()for minimal spacing and compact sizes (36dp rows, 40dp header).TableDimensionsconstructor now requires all parameters (no default values) to encourage explicit configuration.
- Extracted from
- Fixed: Auto-width adjustment for embedded tables now uses dedicated
ApplyAutoWidthEffectutility.- Resolves issues with incorrect column width calculations in nested table scenarios.
- Ensures embedded tables properly measure and apply auto-width independently from parent tables.
Compare: v1.6.4...v1.7.0
1.6.4 — 2025-11-27¶
- Added:
TableState.tableWidthcomputed property to dynamically calculate total table width based on visible columns and their widths.- Automatically recalculates when column order, widths, or visible columns change using
derivedStateOf. - Accounts for both regular column widths and divider widths, including special handling for fixed column dividers.
- Eliminates need for manual width computation in row-embedded content and other custom layouts.
- Automatically recalculates when column order, widths, or visible columns change using
- Removed:
calculateTableWidth()function fromLayoutUtils.ktin favor of centralizedtableWidthproperty inTableState. - Changed: Internal visibility improvements and code cleanup:
- Changed
tableKeyboardNavigationmodifier from public to internal as it's not part of the public API. - Simplified
LocalTableStatedeclaration with explicit type annotation. - Minor formatting adjustments to KDoc comments and code structure.
- Changed
- Fixed: Row-embedded content (e.g., nested tables) now uses its own width instead of
tableWidthfor proper width alignment.
Compare: v1.6.3...v1.6.4
1.6.3 — 2025-11-27¶
- Added:
TableState.recalculateAutoWidths()method for manual column width recalculation.- Useful for deferred/paginated data loading scenarios where initial auto-width calculation occurred on empty data.
- After data loads and content is measured, call this method to recompute column widths based on actual content.
Compare: v1.6.1...v1.6.3
1.6.1 — 2025-11-22¶
- Added: Support for fully custom filters with renderer and state provider, enabling flexible and consistent custom
filter UI and behavior.
- New
TableFilterType.Custominterface allows defining custom filter types with dedicated composable UI. CustomFilterStateProviderinterface for building chip text and managing custom filter state.CustomFastFiltercomponent for rendering custom fast filters with animated visibility.CustomFiltercomponent for rendering custom filters in filter panels with action buttons.- Sample implementation includes
NumericRangeFilterwith range slider, histogram visualization, and predefined range buttons.
- New
Compare: v1.6.0...v1.6.1
1.6.0 — 2025-11-21¶
- Added: Row-scoped cell editing mode with custom edit UI, validation, and keyboard navigation.
- Enable editing via
TableSettings(editingEnabled = true). - Use
EditableTable<T, C, E>composable for tables with editing support. - Declare editable columns with
editableTableColumns<T, C, E> { ... }DSL and per-celleditCellcontent. - Lifecycle callbacks:
onRowEditStart,onRowEditComplete,onEditCancelledfor validation and state management. - Keyboard navigation: Enter/Done moves to next editable cell, Escape cancels editing (desktop targets).
- Double-click any editable cell to enter row edit mode; all editable cells in the row become active simultaneously.
- Enable editing via
- Added:
TableCellTextFieldcomponent for text editing inside table cells.- Focus integration via
syncEditCellFocus()modifier ensures correct keyboard navigation and focus handling. - Compact layout with reduced paddings and optional border visibility for dense table rows.
- Visual consistency with Material 3 inputs used throughout table UI.
- Supports error states, keyboard actions, and customizable styling.
- Focus integration via
- Added:
EditCellFocusSync.ktmodule for coordinating focus between table state and editable cell composables. - Changed:
TableTextFieldvisibility changed from internal to public for reuse in custom filter implementations. - Changed: Filter components enhanced with configurable border visibility and improved layout:
- Added
showBorderparameter toTableTextField, date filters, and number filters. - Adjusted filter row height for better visual consistency.
- Number range slider text aligned to end for improved readability.
- Added
- Changed: Major refactoring of
TableStateto support editing mode, edit cell tracking, and edit lifecycle. - Changed:
ColumnSpecAPI extended witheditCellDSL for defining per-cell edit content. - Changed: Sample app updated with comprehensive editing demo including validation, error handling, and state management.
Compare: v1.5.1...v1.6.0
1.5.1 — 2025-11-20¶
- Fixed: Ensure
TableTextField(table text field) uses the current theme's default text style to improve text visibility and consistency with app theming. - Removed: Unused
okioanddndreferences from build scripts; updated documentation to reflect removed third-party libraries.
Compare: v1.5.0...v1.5.1
1.5.0 — 2025-11-20¶
- Added: Fixed (pinned) columns support with configurable side and count via
TableSettings.- New
fixedColumnsCountandfixedColumnsSideproperties allow pinning one or more columns on the left or right side of the table header and body. - Fixed columns remain visible while horizontally scrolling the rest of the table.
- Visual separators and z-index handling ensure clear separation between fixed and scrollable columns.
- New
- Added: Public access to
TableState.settingsandTableState.dimensionsto simplify integration with custom UI controls and preview tooling.
Compare: v1.4.1...v1.5.0
1.4.1 — 2025-11-20¶
BREAKING CHANGES:
- Removed:
rowLeadingandrowTrailingparameters from allTablecomposable functions.- These parameters previously allowed rendering custom content before or after table rows.
- Migration: Use regular table columns defined via
ColumnSpecinstead. For leading content (e.g., checkboxes, avatars), define a dedicated column at the start of your column list. For trailing content (e.g., action buttons), add a column at the end. - Example:
// Before (1.4.0) Table( rowLeading = { item -> Checkbox(...) }, rowTrailing = { item -> IconButton(...) }, columns = dataColumns, // ... ) // After (1.4.1) val columns = tableColumns<Item, Field> { // Leading column for checkbox column(Field.Select, valueOf = { it }) { width(48.dp) cell { Checkbox(...) } } // Your data columns // ... // Trailing column for actions column(Field.Actions, valueOf = { it }) { width(64.dp) cell { IconButton(...) } } } Table( columns = columns, // ... )
- Removed: All internal infrastructure related to
rowLeading/rowTrailing:hasLeading,leadingColumnWidth,rowLeadingPresent,leadingOffsetparameters removed from internal functions.RowLeadingSectioncomponent removed.
Benefits of this change:
- Simplified API with fewer special-case parameters.
- Leading/trailing content now benefits from full column capabilities: sorting, resizing, filtering, custom styling, etc.
- More consistent and flexible table architecture.
Compare: v1.4.0...v1.4.1
1.4.0 — 2025-11-19¶
- Added: Support for embedded detail tables via the
embeddedflag androwEmbeddedslot for rendering nested tables inside parent rows. - Added:
DisabledTableFiltertype to explicitly disable filtering and conditional formatting conditions for specific columns while still satisfying per-column filter type requirements.
Compare: v1.3.1...v1.4.0
1.3.1 — 2025-11-17¶
- Changed: Updated Kotlin to 2.2.21 and Compose Multiplatform to 1.9.2.
Compare: v1.3.0...v1.3.1
1.3.0 — 2025-11-17¶
- Added: Fast filter components with animated visibility and synchronized state management for boolean, enum, number, text, and date filters.
- Added: Date filter state management with synchronized external state and debounce handling for improved filtering functionality.
- Added:
enableDragToScrollsetting toTableSettingsfor customizable scroll behavior. - Added: Custom
TableTextFieldcomponent with configurable padding for consistent styling across filter components. - Changed: Converted all list types to immutable collections (
ImmutableList) for better performance, thread safety, and prevention of unintended modifications. - Fixed: Scroll to top on sort change for better user experience.
- Fixed: Updated
LaunchedEffectdependencies to track table state for correct cell selection handling. - Chore: Sample app updated with horizontal scrolling toolbar, new demo data, and Position enum.
- Chore: Documentation updated with Immutable Collections section in README.
Compare: v1.2.2...v1.3.0
1.2.2 — 2025-09-26¶
- Added: Inertial fling support with velocity tracking and decay animation for smoother scroll momentum.
- Fixed: Added table state dependency to
LaunchedEffectin auto width effect and measurement utilities for correct recomposition and measurement updates.
Compare: v1.2.1...v1.2.2
1.2.1 — 2025-09-26¶
- Fixed: Render sticky header for unordered list.
Compare: v1.2.0...v1.2.1
1.2.0 — 2025-09-26¶
- Added: Group by functionality with customizable group headers and sticky positioning.
- Added: Column header dropdown menu with group by option for enhanced data organization.
- Fixed: Viewport scrolling logic and cell visibility calculations for improved user interaction.
- Changed: Extracted cell visibility logic and nested scroll handling for better user experience.
- Chore: Unused filter expression class to clean up codebase.
Compare: v1.1.3...v1.2.0
1.1.3 — 2025-09-23¶
- Fixed: Resolved header resizing bug that could cause column misalignment during dynamic layout updates.
Compare: v1.1.2...v1.1.3
1.1.2 — 2025-09-23¶
- Changed: Table core and interaction internals updated (layout, dimensions, keyboard navigation, viewport utils) and sample columns updated to the new header API.
Compare: v1.1.1...v1.1.2
1.1.1 — 2025-09-22¶
- Fixed: Cell selection border now covers the full cell height in dynamic row height mode.
- Fixed: Vertical scrolling to the selected cell behaves like Excel — only the next/previous row is revealed without jumping.
- Added: Precise row height measurement and caching to support minimal, direction-aware scrolling.
- Added: Cache of measured row heights is automatically cleared when
itemsCountchanges (new data loaded).
Compare: v1.1.0...v1.1.1
1.1.0 — 2025-09-22¶
- Added: Auto-fit column widths based on content (measurement utilities and smart sizing).
- Added: Dynamic row height with min/max constraints via table settings and column specifications.
- Added: Keyboard navigation and cell selection; improved cell visibility handling.
- Changed: Refactored
TableHeaderfor more stable layout and interaction handling. - Changed: Dependencies updated; deprecated modules removed.
- Changed: Samples updated (JS/WASM styles, better demo data and layout).
- Fixed: Header column animation during resizing.
Compare: v1.0.3...v1.1.0 on GitHub (https://github.com/White-Wind-LLC/table/compare/v1.0.3...main).