From 22da9beb2d95c5f89f2138b5e4a4169f498e63f6 Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Wed, 12 Aug 2026 13:14:12 +0800 Subject: [PATCH 1/3] fix(table-core): flatten filtered parent rows ahead of their sub-rows --- .../features/column-filtering/filterRowsUtils.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/table-core/src/features/column-filtering/filterRowsUtils.ts b/packages/table-core/src/features/column-filtering/filterRowsUtils.ts index e8f3e09336..7385b440fc 100644 --- a/packages/table-core/src/features/column-filtering/filterRowsUtils.ts +++ b/packages/table-core/src/features/column-filtering/filterRowsUtils.ts @@ -65,20 +65,25 @@ function filterRowModelFromLeafs< newRow.columnFilters = row.columnFilters if (row.subRows.length && depth < maxDepth) { + // Descendants are filtered first, so in the flat list a parent must + // be inserted before its surviving children (pre-order) to match the + // readable `rows` tree and the core/sorted/paginated row models. + const flatIndex = newFilteredFlatRows.length + newRow.subRows = recurseFilterRows(row.subRows, depth + 1) row = newRow if (filterRow(row) && !newRow.subRows.length) { + newFilteredFlatRows.splice(flatIndex, 0, row) filteredRows.push(row) newFilteredRowsById[row.id] = row - newFilteredFlatRows.push(row) continue } if (filterRow(row) || newRow.subRows.length) { + newFilteredFlatRows.splice(flatIndex, 0, row) filteredRows.push(row) newFilteredRowsById[row.id] = row - newFilteredFlatRows.push(row) continue } } else { @@ -127,6 +132,11 @@ function filterRowModelFromRoot< const pass = filterRow(row) if (pass) { + // Take this row's slot before descending, so a parent stays ahead of + // its own sub-rows in flatRows (pre-order). + const flatIndex = newFilteredFlatRows.length + newFilteredFlatRows.push(row) + if (row.subRows.length && depth < maxDepth) { const newRow = constructRow( table, @@ -139,10 +149,10 @@ function filterRowModelFromRoot< ) newRow.subRows = recurseFilterRows(row.subRows, depth + 1) row = newRow + newFilteredFlatRows[flatIndex] = row } filteredRows.push(row) - newFilteredFlatRows.push(row) newFilteredRowsById[row.id] = row // When maxLeafRowFilterDepth stops the recursion, the kept row's From 0bdaabf0c95dbb94e19acb82c701098698b9ad34 Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Wed, 12 Aug 2026 13:15:14 +0800 Subject: [PATCH 2/3] test: add parent-first flatRows order tests for filtered row model --- .../createFilteredRowModel.test.ts | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts b/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts index 67637123f7..67d40cea19 100644 --- a/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts +++ b/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts @@ -209,6 +209,20 @@ describe('createFilteredRowModel', () => { expect(model.rowsById[keepA.id]).toBe(keepA) expect(model.rowsById[keepA.subRows[0]!.id]).toBe(keepA.subRows[0]) }) + + it('flattens each parent ahead of its own sub-rows (from root)', () => { + const table = makeNestedTable() + const model = table.getFilteredRowModel() + + // Pre-order: a parent precedes its own surviving sub-rows in flatRows, + // matching the readable `rows` tree. + expect(rowNames(model.flatRows)).toEqual([ + 'keep-a', + 'keep-a1', + 'keep-c', + 'keep-d', + ]) + }) }) describe('filterFromLeafRows', () => { @@ -270,6 +284,23 @@ describe('createFilteredRowModel', () => { expect(rowNames(keepA.subRows)).toEqual(['keep-a1']) expect(keepA.subRows[0]!.subRows).toEqual([]) }) + + it('flattens each parent ahead of its own sub-rows (from leaf)', () => { + const table = makeNestedTable({ filterFromLeafRows: true }) + const { rows, flatRows } = table.getFilteredRowModel() + + // drop-b is retained because keep-b1 matches, and its ancestor chain + // must appear before its descendants in flatRows (pre-order). + expect(rowNames(rows)).toEqual(['keep-a', 'drop-b', 'keep-c', 'keep-d']) + expect(rowNames(flatRows)).toEqual([ + 'keep-a', + 'keep-a1', + 'drop-b', + 'keep-b1', + 'keep-c', + 'keep-d', + ]) + }) }) describe('maxLeafRowFilterDepth', () => { @@ -324,13 +355,12 @@ describe('createFilteredRowModel', () => { const model = table.getFilteredRowModel() // Depth-1 children are still filtered (drop-a2 removed), while the - // depth-2 subtree of keep-a1 is kept as-is and joins flatRows. The - // pre-existing flatRows order pushes recursed children before their - // parent. + // depth-2 subtree of keep-a1 is kept as-is and joins flatRows. Each + // parent flattens ahead of its own sub-rows (pre-order). expect(rowNames(model.flatRows)).toEqual([ + 'keep-a', 'keep-a1', 'drop-a1a', - 'keep-a', 'keep-c', 'keep-d', ]) From e33dbc584ec7c75223a5b43917b2112508aca9c0 Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Wed, 12 Aug 2026 13:15:15 +0800 Subject: [PATCH 3/3] chore: add changeset --- .changeset/great-pugs-sniff.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/great-pugs-sniff.md diff --git a/.changeset/great-pugs-sniff.md b/.changeset/great-pugs-sniff.md new file mode 100644 index 0000000000..52db854121 --- /dev/null +++ b/.changeset/great-pugs-sniff.md @@ -0,0 +1,5 @@ +--- +"@tanstack/table-core": patch +--- + +Fix `getFilteredRowModel().flatRows` listing sub-rows before their parent in both `filterFromLeafRows` and `filterFromRoot` modes. This restores the parent-first order used by the core, sorted, and paginated row models, and aligns the flattened result with the filtered `rows` tree.