diff --git a/packages/table-core/src/features/column-filtering/filterRowsUtils.ts b/packages/table-core/src/features/column-filtering/filterRowsUtils.ts index e8f3e09336..b060f4c111 100644 --- a/packages/table-core/src/features/column-filtering/filterRowsUtils.ts +++ b/packages/table-core/src/features/column-filtering/filterRowsUtils.ts @@ -51,7 +51,7 @@ function filterRowModelFromLeafs< Partial> = [] // Filter from children up first - for (let row of rowsToFilter) { + for (const row of rowsToFilter) { const newRow = constructRow( table, row.id, @@ -66,27 +66,13 @@ function filterRowModelFromLeafs< if (row.subRows.length && depth < maxDepth) { newRow.subRows = recurseFilterRows(row.subRows, depth + 1) - row = newRow - if (filterRow(row) && !newRow.subRows.length) { - filteredRows.push(row) - newFilteredRowsById[row.id] = row - newFilteredFlatRows.push(row) - continue - } - - if (filterRow(row) || newRow.subRows.length) { - filteredRows.push(row) - newFilteredRowsById[row.id] = row - newFilteredFlatRows.push(row) - continue + if (filterRow(newRow) || newRow.subRows.length) { + filteredRows.push(newRow) } } else { - row = newRow - if (filterRow(row)) { - filteredRows.push(row) - newFilteredRowsById[row.id] = row - newFilteredFlatRows.push(row) + if (filterRow(newRow)) { + filteredRows.push(newRow) } } } @@ -94,8 +80,11 @@ function filterRowModelFromLeafs< return filteredRows } + const rows = recurseFilterRows(rowsToFilter) + addSubRowsToFlatArrays(rows, newFilteredFlatRows, newFilteredRowsById) + return { - rows: recurseFilterRows(rowsToFilter), + rows, flatRows: newFilteredFlatRows, rowsById: newFilteredRowsById, } @@ -123,7 +112,7 @@ function filterRowModelFromRoot< const filteredRows: Array> = [] // Apply the filter to any subRows - for (let row of rowsToFilter) { + for (const row of rowsToFilter) { const pass = filterRow(row) if (pass) { @@ -136,26 +125,35 @@ function filterRowModelFromRoot< row.depth, undefined, row.parentId, - ) + ) as Row & + Partial> + newRow.columnFilters = ( + row as Row & + Partial> + ).columnFilters + + filteredRows.push(newRow) + newFilteredFlatRows.push(newRow) + newFilteredRowsById[newRow.id] = newRow + newRow.subRows = recurseFilterRows(row.subRows, depth + 1) - row = newRow - } + } else { + filteredRows.push(row) + newFilteredFlatRows.push(row) + newFilteredRowsById[row.id] = row - filteredRows.push(row) - newFilteredFlatRows.push(row) - newFilteredRowsById[row.id] = row - - // When maxLeafRowFilterDepth stops the recursion, the kept row's - // subtree stays visible through row.subRows, so those descendants - // must still enter flatRows and rowsById to keep the flat - // representation (and anything derived from it, like facet counts) - // consistent with the visible tree - if (row.subRows.length && depth >= maxDepth) { - addSubRowsToFlatArrays( - row.subRows, - newFilteredFlatRows, - newFilteredRowsById, - ) + // When maxLeafRowFilterDepth stops the recursion, the kept row's + // subtree stays visible through row.subRows, so those descendants + // must still enter flatRows and rowsById to keep the flat + // representation (and anything derived from it, like facet counts) + // consistent with the visible tree + if (row.subRows.length && depth >= maxDepth) { + addSubRowsToFlatArrays( + row.subRows, + newFilteredFlatRows, + newFilteredRowsById, + ) + } } } } 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..06353724c9 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 @@ -188,11 +188,11 @@ describe('createFilteredRowModel', () => { expect(rowNames(model.flatRows)).not.toContain('keep-b1') }) - it('should include cloned rows and exclude dropped rows in flatRows and rowsById', () => { + it('should include cloned rows and exclude dropped rows in flatRows and rowsById in pre-order', () => { const table = makeNestedTable() const model = table.getFilteredRowModel() - expect(rowNames(model.flatRows).sort()).toEqual([ + expect(rowNames(model.flatRows)).toEqual([ 'keep-a', 'keep-a1', 'keep-c', @@ -222,6 +222,20 @@ describe('createFilteredRowModel', () => { expect(rowNames(dropB.subRows)).toEqual(['keep-b1']) }) + it('should flatten rows in pre-order with each parent ahead of its sub-rows', () => { + const table = makeNestedTable({ filterFromLeafRows: true }) + const { flatRows } = table.getFilteredRowModel() + + expect(rowNames(flatRows)).toEqual([ + 'keep-a', + 'keep-a1', + 'drop-b', + 'keep-b1', + 'keep-c', + 'keep-d', + ]) + }) + it('should keep a matching parent that has no matching children', () => { const table = makeNestedTable({ filterFromLeafRows: true }) const { rows } = table.getFilteredRowModel() @@ -254,11 +268,7 @@ describe('createFilteredRowModel', () => { expect(rowNames(rows)).toEqual(['drop-x']) expect(rowNames(rows[0]!.subRows)).toEqual(['drop-x1']) expect(rowNames(rows[0]!.subRows[0]!.subRows)).toEqual(['keep-x1a']) - expect(rowNames(flatRows).sort()).toEqual([ - 'drop-x', - 'drop-x1', - 'keep-x1a', - ]) + expect(rowNames(flatRows)).toEqual(['drop-x', 'drop-x1', 'keep-x1a']) }) it('should prune matching subRows from a matching parent while filtering', () => { @@ -324,13 +334,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 in + // pre-order traversal (parent before children). expect(rowNames(model.flatRows)).toEqual([ + 'keep-a', 'keep-a1', 'drop-a1a', - 'keep-a', 'keep-c', 'keep-d', ]) @@ -716,4 +725,66 @@ describe('createFilteredRowModel', () => { expect(table.getFilteredRowModel()).toBe(table.getPreFilteredRowModel()) }) }) + + describe('pre-order flatRows traversal', () => { + const complexNestedData: Array = [ + { + name: 'parent-1', + subRows: [ + { + name: 'child-1.1', + subRows: [ + { name: 'grandchild-1.1.1' }, + { name: 'grandchild-1.1.2' }, + ], + }, + { name: 'child-1.2' }, + ], + }, + { + name: 'parent-2', + subRows: [{ name: 'child-2.1' }], + }, + ] + + it('flattens rows depth-first with each parent preceding its sub-rows (root filtering)', () => { + const table = constructTable({ + features, + columns: nestedColumns, + data: complexNestedData, + getSubRows: (row) => row.subRows, + initialState: { + columnFilters: [{ id: 'name', value: '1' }], + }, + }) + + expect(rowNames(table.getFilteredRowModel().flatRows)).toEqual([ + 'parent-1', + 'child-1.1', + 'grandchild-1.1.1', + 'grandchild-1.1.2', + 'child-1.2', + ]) + }) + + it('flattens rows depth-first with each parent preceding its sub-rows (leaf filtering)', () => { + const table = constructTable({ + features, + columns: nestedColumns, + data: complexNestedData, + getSubRows: (row) => row.subRows, + filterFromLeafRows: true, + initialState: { + columnFilters: [{ id: 'name', value: '.2' }], + }, + }) + + expect(rowNames(table.getFilteredRowModel().flatRows)).toEqual([ + 'parent-1', + 'child-1.1', + 'grandchild-1.1.2', + 'child-1.2', + ]) + }) + }) })