Skip to content

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 showVerticalDividers off, 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.get is 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 custom rowKey from 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 supersedes rowKey wherever the table needs a key, so no row is resolved to produce one. Pass it (answering the same key rowKey would) 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-core to 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 an implementation dependency, 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 Table overloads recomposed on every emission from a pager. table-paging now compiles with a Compose stability configuration for the paging-core types, which puts items back on value comparison under strong skipping. A stability configuration governs only the module doing the compiling, so composables of your own that take a PagingData — a screen, a view-model-bound wrapper, a LazyColumn using handleLoadState — need the same file in your build; the table-paging docs show how to wire it up.
  • Updated: ua.wwind.paging:paging-core to 2.3.0 (from 2.2.7). It is an implementation dependency, so you declare your own version and this does not force the upgrade; the types table-paging consumes are unchanged between the two. Note that 2.3.0 does carry breaking changes of its own — Pager gained positional parameters and a concurrency = 4 default, and the macosX64 target 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 behind TableHeaderDefaults.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 an implementation dependency, 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-in AlertDialog. FormatDialog is unchanged; both reuse the same internal title, buttons and body blocks.
  • Added: FormatDialogColors and FormatDialogDefaults.colors() (defaulting to the Material3 AlertDialog defaults), and a colors parameter on FormatDialog and FormatDialogContent. FormatDialogContent applies 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 onSettle captured 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: TableState now composes three state holders of its own, reached as state.columns, state.selection and state.editing. TableColumnsState owns column order, width overrides and the measurements auto-fit works from; TableSelectionState owns the focused row, the checked rows and the selected cell; TableEditingState owns the edited row and column together with the edit callbacks. Sorting, grouping and filtering stay on TableState itself — they belong to the table as a whole rather than to one of the three. New public types: TableColumnsState, TableSelectionState, TableEditingState.
  • Deprecated: the 31 TableState members those holders took over. Every one stays as a forwarder carrying a ReplaceWith migration, so existing code keeps compiling and the IDE rewrites each call site for you; they are removed in the next major. Columns: columnOrdercolumns.order, columnWidthscolumns.widths, columnContentMaxWidthscolumns.contentMaxWidths, columnHeaderWidthscolumns.headerWidths, autoWidthAppliedForEmpty and autoWidthAppliedForData → the same names under columns, resolveColumnWidthcolumns.resolveWidth, moveColumncolumns.move, setColumnOrdercolumns.setOrder, resizeColumncolumns.resize, setColumnWidthscolumns.setWidths, updateMaxContentWidthcolumns.updateMaxContentWidth, setColumnWidthToMaxContentcolumns.fitToContent, recalculateAutoWidthscolumns.recalculateAutoWidths. Selection: selectedIndex, checkedIndices, selectedCell, focusRow, toggleCheck, toggleCheckAll and selectCell → the same names under selection, and toggleSelectselection.toggleRow (named for what it toggles, next to toggleCheck). Editing: editingRowediting.rowIndex, editingColumnediting.column, onRowEditStart, onRowEditComplete and onEditCancel → the same names under editing, startEditingediting.start, tryCompleteEditingediting.tryComplete, completeCurrentCellEditediting.completeCurrentCell, cancelEditingediting.cancel. TableState.SelectedCell deliberately 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 TooManyFunctions detekt debt baselined in 1.11.0 is paid down rather than suppressed. The split above resolves TableState; SampleViewModel gives up two stateless helpers that never read its state (matchesPerson forwarded to PersonFilterMatcher, buildFormatFilterData is now PersonFormatFilterData.build). The rule itself stops counting private and deprecated members, with the reasoning recorded in config/detekt/detekt.yml: a private helper is what CyclomaticComplexMethod and LongMethod ask 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 ComplexCondition debt baselined in 1.11.0 is paid down too — three conditions in table-format and table-sample rewritten 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. @ExperimentalTableApi is off Table, EditableTable and the table-paging adapter — 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 ReplaceWith migration for at least one minor release.
    • TableSettings.isDragEnabledrowReorderEnabled.
    • TableSettings.fixedColumnsCountpinnedColumnsCount; TableSettings.fixedColumnsSidepinnedColumnsSide; the FixedSide typealias → PinnedSide.
    • TableDimensions.fixedColumnDividerThicknesspinnedColumnDividerThickness.
    • TableRowContext.isGroupisInRowBlock.
    • The TableSettings.isRowReorderEnabled extension goes with them: it existed only to OR the new and deprecated reorder flags together, so with isDragEnabled gone it is exactly settings.rowReorderEnabled. isInteractionLockByRowReorderEnabled is unaffected and stays.
  • Changed (breaking): CustomFilterRenderer.RenderPanel now returns Unit and takes a CustomFilterPanelActions parameter instead of returning TableFilterType.CustomFilterActions. It emits the filter panel UI, so returning a value tripped the Compose naming/emitter rules — a @Composable that both draws and returns is exactly what the convention warns against. Implementers must add the panelActions parameter (placed after tableData, ahead of the onDismiss/onChange lambdas), drop the return type, and replace return <actions> with panelActions.set(<actions>) where the old return was. 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.
    • FilterDropdownField and FilterDropdownAnyField: values and onClick move ahead of getTitle and placeholder.
    • TableActiveFilters: modifier moves ahead of strings.
    • rememberCustomization: matches moves ahead of key. Note that key is Any?, so a positional call passing matches third would still compile while binding the lambda to key — check these by hand.
    • FormatDialogTabRow: createTab moves ahead of modifier; the content trailing lambda stays last.
  • Added (breaking): FormatDialogDesignTab, FormatDialogConditionTab and FormatDialogFieldTab now take a modifier, 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 existing scrollbarRenderer: named-argument callers are unaffected, a positional call passing scrollbarRenderer must 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: onEditCancelledonEditCancel. The TableState edit-state property of the same name is renamed with it, so the parameter and the state it writes still read alike.
    • FormatDialog: onRulesChangedonRulesChange.
  • Changed: FilterDropdownField, FilterDropdownAnyField and FormatDialogTabRow now apply their modifier to 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*FilterState factory implemented its emit rule twice — once in the debounced auto-apply effect, once in applyFilter — 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 (to below from) 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 one isError source of truth instead of recomputing its own.
    • Enum: an empty selection combined with IS_NULL / IS_NOT_NULL stays 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.
  • 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 rowBlocks parameter on Table, EditableTable and the table-paging adapter 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. Supersedes onRowMove while declared, and is mutually exclusive with state.groupBy. See the Row blocks guide.
    • New API: RowBlocks, RowBlockMove, RowWithinBlockMove, RowBlockHeaderScope, TableState.rowBlocksSuppressedByGroupBy, the MutableList.applyRowBlockMove() / applyRowReorderWithinBlock() and List.sortedWithinRowBlocks() / filteredWholeRowBlocks() helpers, and the TableDimensions.rowBlockSpacing / TableColors.rowBlockContainerColor theming parameters.
  • Changed: state.setSort() is now a warning no-op while row reorder is enabled, matching the existing initialSort normalization — under an active sort a reorder would be unobservable.
  • Deprecated: TableRowContext.isGroup, renamed to isInRowBlock. A read-only forwarder keeps existing customizations compiling, and the flag is now actually set — true for rows inside a row block, so TableCustomization can style block members (previously always false).
  • Fixed: table width no longer freezes when ColumnSpec.visible changes 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 from com.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-sample is split into the multiplatform module plus a thin table-sample-android application module, since a KMP target and com.android.application can no longer share one module.
  • 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.detekt 2.0.0-alpha.5 and 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.rules added to detekt for Compose-specific linting; its 63 findings are baselined, as most fixes would change the published API.
  • Changed: the Gradle daemon JVM is pinned to Java 17 (Temurin) via gradle/gradle-daemon-jvm.properties, matching the jvmToolchain(17) used for compilation and the JDK installed on CI.
  • Removed: the -Xcontext-parameters compiler 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). compileSdk and targetSdk stay at 37.
  • Updated: library dependencies.
    • AndroidX Lifecycle upgraded from 2.10.0 to 2.11.0.
    • Collections Immutable upgraded from 0.5.0 to 0.5.1.
  • Updated: Build tooling dependencies.
    • BuildKonfig upgraded from 0.21.2 to 0.22.0.
    • Spotless upgraded from 8.7.0 to 8.8.0.
    • Compose Rules upgraded from 0.6.2 to 0.6.3.

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.0 to 0.5.0.
  • Updated: Logging dependencies.
    • SLF4J upgraded from 2.0.17 to 2.0.18.
  • Updated: UI library dependencies.
    • Color Picker upgraded from 1.1.4 to 1.2.0.
  • Updated: Build tooling dependencies.
    • BuildKonfig upgraded from 0.20.0 to 0.21.2.
    • Maven Publish plugin upgraded from 0.36.0 to 0.37.0.
    • Compose Stability Analyzer upgraded from 0.7.4 to 0.10.0.

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-alpha05 to 1.11.0-alpha07.
  • Updated: KotlinX dependencies.
    • Coroutines upgraded from 1.10.2 to 1.11.0.
    • Datetime upgraded from 0.7.1 to 0.8.0.
  • Updated: Android SDK targets.
    • compileSdk and targetSdk bumped from 36 to 37.
  • Updated: Build tooling dependencies.
    • Gradle wrapper upgraded from 9.4.0 to 9.5.0.
    • Compose Hot Reload upgraded from 1.1.0 to 1.1.1.
    • BuildKonfig upgraded from 0.19.0 to 0.20.0.

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.0 to 1.1.0.
    • BuildKonfig upgraded from 0.17.1 to 0.19.0.
    • Dokka upgraded from 2.1.0 to 2.2.0.
    • Maven Publish plugin upgraded from 0.35.0 to 0.36.0.
    • Compose Stability Analyzer upgraded from 0.7.2 to 0.7.4.
  • Updated: UI library dependencies.
    • Reorderable upgraded from 3.0.0 to 3.1.0.
    • Color Picker upgraded from 1.1.3 to 1.1.4.

Compare: v1.8.2...v1.8.3

1.8.2 — 2026-04-08

  • Fixed: Header content state and tooltip behavior for truncated titles.
    • TruncationTooltipBox now accepts a composable title provider for better content flexibility.
    • Header content renders via movableContentOf to 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.toml aliases.
    • Aligned module usage across convention and sample Gradle scripts.
  • 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: enableDragToScroll setting now defaults to platform-aware value.
    • Default value changed from true to getPlatform().isMobile() - enabled on mobile, disabled on desktop.
    • Drag-to-scroll remains available on desktop when explicitly enabled in settings.
  • Changed: Removed incompatible normalization between row reorder and drag-to-scroll.
    • rowReorderEnabled and enableDragToScroll can 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 onRowMove callback to Table and EditableTable for handling row position changes.
    • Added TableItemScope and TableCellScope to expose item-scoped drag helpers inside cell content.
    • Added Modifier.draggableHandle() and Modifier.longPressDraggableHandle() for building custom row drag handles directly in cell { ... }.
    • Row reordering now works for both regular lazy tables and embedded table bodies.
  • Changed: Table cell DSL now uses Kotlin context parameters.
    • ColumnSpec.cell and the cell { ... } builder now run with context(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.rowReorderEnabled is now the primary setting name; deprecated isDragEnabled remains supported for compatibility.
    • When row reorder mode is enabled, enableDragToScroll is forced to false and initialSort is 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.2 to 1.10.3.
    • AndroidX Lifecycle upgraded from 2.9.6 to 2.10.0.

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 FormatDropdownField and related components.
    • The scrollbar state management was causing crashes in certain scenarios.
    • Format dialog now works reliably without custom scrollbar integration in dropdowns.
  • Changed: Refactored FormatDialog data classes to separate files.
    • EditFormatRule moved to ua.wwind.table.format.data package with @Immutable annotation.
    • FormatDialogSettings moved to ua.wwind.table.format.data package with @Immutable annotation.
    • 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 FormatDialogSettings to 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.
  • 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 VerticalScrollbarRenderer and VerticalScrollbarState for custom scrollbar rendering.
    • Optional scrollbarRenderer parameter added to FormatDialog and FormatDialogConditionTab.
    • Enables proper scrollbar integration in desktop environments.
  • 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 rememberSaveable to detect actual sort changes.
    • Previously, any LaunchedEffect re-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.

Compare: v1.7.11...v1.7.12

1.7.11 — 2026-01-06

  • Fixed: Header widths are now preserved during auto-width recalculation.
    • Added columnHeaderWidths map 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.

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 = true AND at least one visible column with a non-null, non-disabled filter.
    • Columns with null or DisabledTableFilter are 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.
    • showVerticalDividers controls vertical dividers between columns (default: true).
    • showRowDividers controls horizontal dividers between rows (default: true).
    • showHeaderDivider controls horizontal divider below the header (default: true).
    • showFastFiltersDivider controls horizontal divider below the fast filters row (default: true).
  • Added: Validation for TableDimensions divider thickness values.
    • dividerThickness and pinnedColumnDividerThickness must be at least 1.dp.
    • Invalid values now throw IllegalArgumentException with 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: Refactored EditableTable to reduce cyclomatic complexity.

Compare: v1.7.6...v1.7.7

1.7.6 — 2026-01-05

  • Added: border parameter to Table and EditableTable composables for customizable outer table border.
    • Accepts BorderStroke? to fully customize border appearance (color, thickness, brush).
    • null (default) applies theme-based border using dividerThickness and MaterialTheme.colorScheme.outlineVariant.
    • TableDefaults.NoBorder sentinel value disables the outer border entirely.
    • Paging Table functions updated with the same border parameter for API consistency.

Compare: v1.7.5...v1.7.6

1.7.5 — 2026-01-03

  • Added: enableTextSelection setting in TableSettings for optional text selection support in table rows.
    • When enabled, wraps the table body in Compose SelectionContainer to allow users to select and copy text content.
    • Defaults to false to 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.

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 tableData parameter for consistent API across all table components.
    • ColumnSpec.cell signature changed from @Composable BoxScope.(T) -> Unit to @Composable BoxScope.(T, E) -> Unit.
    • All cell content now receives the table data parameter, matching the signature of header, footer, and editCell.
    • Provides access to shared state within regular cell rendering, enabling dynamic behavior based on table-wide context.
    • Migration: Update all cell { item -> ... } declarations to cell { item, tableData -> ... } or cell { item, _ -> ... } if table data is not needed.
    • Example:
      // Before (1.7.2)
      cell { item ->
          Text(item.name)
      }
      
      // After (1.7.3)
      cell { item, tableData ->
          // Can now access tableData for conditional rendering
          Text(item.name)
      }
      
      // Or use underscore if not needed
      cell { item, _ ->
          Text(item.name)
      }
      

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: shape parameter to paging Table composables 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 Table function now support shape parameter.
  • Added: New paging Table composable overload with tableData parameter for shared state access.
    • Enables passing table data to headers, footers, and edit cells in paging tables.
    • Generic parameter E represents table data type, consistent with core table API.
    • Provides full feature parity with core Table composable for table data customization.

Compare: v1.7.1...v1.7.2

1.7.1 — 2025-12-02

  • Added: Support for custom table data (tableData parameter) that can be shared across headers, footers, filters, and edit cells.
    • New Table overload that accepts tableData parameter for passing shared state to table components.
    • Generic parameter E now 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.
    • EditableTable parameter renamed from editState to tableData to better reflect its purpose.
  • Changed: Enhanced column specification API for table data support.
    • ColumnSpec.header signature changed from @Composable () -> Unit to @Composable (E) -> Unit.
    • ColumnSpec.footer signature changed from @Composable BoxScope.() -> Unit to @Composable BoxScope.(E) -> Unit.
    • ColumnSpec.editCell documentation updated to clarify table data parameter usage.
    • ReadonlyTableColumnsBuilder now generic over table data type E for consistent API surface.
    • TableFilterType.Custom interface 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, and PersonSorter.
    • PersonTableData model 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 footer DSL in ColumnSpec builder for defining per-column footer content.
    • TableSettings.showFooter enables footer row rendering below table body.
    • TableSettings.footerPinned controls whether footer stays pinned at the bottom (non-embedded tables only).
    • Footer respects pinned column configuration for consistent horizontal scrolling behavior.
    • TableColors extended with footerContainerColor and footerContentColor properties.
    • TableDimensions extended with footerHeight property for customizable footer sizing.
  • Changed: Terminology update from "fixed" to "pinned" columns for better clarity.
    • TableSettings.fixedColumnsCountpinnedColumnsCount (deprecated with ReplaceWith migration).
    • TableSettings.fixedColumnsSidepinnedColumnsSide (deprecated with ReplaceWith migration).
    • FixedSide enum → PinnedSide enum (deprecated as typealias for backward compatibility).
    • FixedColumnStatePinnedColumnState (internal state class renamed).
    • TableDimensions.fixedColumnDividerThicknesspinnedColumnDividerThickness (deprecated with ReplaceWith migration).
    • All internal references updated to use "pinned" terminology throughout the codebase.
  • Changed: Refactored TableDefaults into a dedicated object with factory methods.
    • Extracted from TableColors.kt to new TableDefaults.kt module 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).
    • TableDimensions constructor now requires all parameters (no default values) to encourage explicit configuration.
  • Fixed: Auto-width adjustment for embedded tables now uses dedicated ApplyAutoWidthEffect utility.
    • 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.tableWidth computed 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.
  • Removed: calculateTableWidth() function from LayoutUtils.kt in favor of centralized tableWidth property in TableState.
  • Changed: Internal visibility improvements and code cleanup:
    • Changed tableKeyboardNavigation modifier from public to internal as it's not part of the public API.
    • Simplified LocalTableState declaration with explicit type annotation.
    • Minor formatting adjustments to KDoc comments and code structure.
  • Fixed: Row-embedded content (e.g., nested tables) now uses its own width instead of tableWidth for 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.Custom interface allows defining custom filter types with dedicated composable UI.
    • CustomFilterStateProvider interface for building chip text and managing custom filter state.
    • CustomFastFilter component for rendering custom fast filters with animated visibility.
    • CustomFilter component for rendering custom filters in filter panels with action buttons.
    • Sample implementation includes NumericRangeFilter with range slider, histogram visualization, and predefined range buttons.

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-cell editCell content.
    • Lifecycle callbacks: onRowEditStart, onRowEditComplete, onEditCancelled for 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.
  • Added: TableCellTextField component 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.
  • Added: EditCellFocusSync.kt module for coordinating focus between table state and editable cell composables.
  • Changed: TableTextField visibility changed from internal to public for reuse in custom filter implementations.
  • Changed: Filter components enhanced with configurable border visibility and improved layout:
    • Added showBorder parameter to TableTextField, date filters, and number filters.
    • Adjusted filter row height for better visual consistency.
    • Number range slider text aligned to end for improved readability.
  • Changed: Major refactoring of TableState to support editing mode, edit cell tracking, and edit lifecycle.
  • Changed: ColumnSpec API extended with editCell DSL 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 okio and dnd references 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 fixedColumnsCount and fixedColumnsSide properties 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.
  • Added: Public access to TableState.settings and TableState.dimensions to 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: rowLeading and rowTrailing parameters from all Table composable functions.
    • These parameters previously allowed rendering custom content before or after table rows.
    • Migration: Use regular table columns defined via ColumnSpec instead. 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, leadingOffset parameters removed from internal functions.
    • RowLeadingSection component 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 embedded flag and rowEmbedded slot for rendering nested tables inside parent rows.
  • Added: DisabledTableFilter type 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: enableDragToScroll setting to TableSettings for customizable scroll behavior.
  • Added: Custom TableTextField component 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 LaunchedEffect dependencies 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 LaunchedEffect in 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 itemsCount changes (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 TableHeader for 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).