diff --git a/.changeset/parent-column-visibility.md b/.changeset/parent-column-visibility.md new file mode 100644 index 0000000000..40bb0b654e --- /dev/null +++ b/.changeset/parent-column-visibility.md @@ -0,0 +1,5 @@ +--- +'@tanstack/table-core': patch +--- + +Respect explicit `columnVisibility` entries on parent group columns before falling back to child column visibility in `column_getIsVisible`. diff --git a/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts b/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts index abbad038e3..6a515b48a3 100644 --- a/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts +++ b/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts @@ -61,6 +61,7 @@ export function column_toggleVisibility< next[leafColumn.id] = nextVisible } } + next[column.id] = nextVisible return next }) @@ -71,8 +72,9 @@ export function column_toggleVisibility< * Checks whether this column is visible. * * Leaf columns read `state.columnVisibility[column.id]`, where missing entries - * default to visible. Parent columns are visible when at least one child column - * is visible. + * default to visible. Parent columns use `state.columnVisibility[column.id]` when + * explicitly set, and otherwise are visible when at least one child column is + * visible. * * @example * ```ts @@ -88,16 +90,15 @@ export function column_getIsVisible< if (!columnVisibility) return true const childColumns = column.columns - if (childColumns.length) { - return childColumns.some((childColumn) => - callMemoOrStaticFn(childColumn, 'getIsVisible', column_getIsVisible), - ) - } - return ( (hasOwn(columnVisibility, column.id) ? columnVisibility[column.id] - : undefined) ?? true + : undefined) ?? + (childColumns.length + ? childColumns.some((childColumn) => + callMemoOrStaticFn(childColumn, 'getIsVisible', column_getIsVisible), + ) + : true) ) } diff --git a/packages/table-core/tests/unit/features/column-visibility/columnVisibilityFeature.utils.test.ts b/packages/table-core/tests/unit/features/column-visibility/columnVisibilityFeature.utils.test.ts index 8ca1c31c40..5f476cca59 100644 --- a/packages/table-core/tests/unit/features/column-visibility/columnVisibilityFeature.utils.test.ts +++ b/packages/table-core/tests/unit/features/column-visibility/columnVisibilityFeature.utils.test.ts @@ -103,6 +103,34 @@ describe('columnVisibilityFeature.utils', () => { expect(result).toBe(true) }) + + it('should return false when parent column visibility is explicitly set to false even if child columns are visible', () => { + const columns: Array> = [ + { + id: 'name', + header: 'Name', + columns: [ + { accessorKey: 'firstName', id: 'firstName' }, + { accessorKey: 'lastName', id: 'lastName' }, + ], + }, + ] + const table = constructTable({ + features, + data: generateTestData(1), + columns, + initialState: { + columnVisibility: { + name: false, + }, + }, + }) + const parentColumn = table.getAllColumns()[0]! + + const result = column_getIsVisible(parentColumn) + + expect(result).toBe(false) + }) }) describe('column_getCanHide', () => { @@ -196,8 +224,57 @@ describe('columnVisibilityFeature.utils', () => { column_toggleVisibility(groupColumn, false) const result = getUpdaterResult(onColumnVisibilityChange, {}) - expect(result).toEqual({ firstName: false }) - expect(result).not.toHaveProperty('name') + expect(result).toEqual({ firstName: false, name: false }) + }) + + it('should update an explicit parent entry when toggling a group column', () => { + const onColumnVisibilityChange = vi.fn() + const columns: Array> = [ + { + id: 'name', + columns: [ + { accessorKey: 'firstName', id: 'firstName' }, + { accessorKey: 'lastName', id: 'lastName' }, + ], + }, + ] + const table = constructTable({ + features, + data: generateTestData(1), + columns, + onColumnVisibilityChange, + initialState: { + columnVisibility: { name: true, firstName: false, lastName: false }, + }, + }) + const groupColumn = table.getColumn('name')! + + column_toggleVisibility(groupColumn, false) + + let result = getUpdaterResult(onColumnVisibilityChange, { + name: true, + firstName: false, + lastName: false, + }) + expect(result).toEqual({ + name: false, + firstName: false, + lastName: false, + }) + + onColumnVisibilityChange.mockClear() + column_toggleVisibility(groupColumn, true) + + result = getUpdaterResult(onColumnVisibilityChange, { + name: false, + firstName: false, + lastName: false, + }) + expect(result).toEqual({ + name: true, + firstName: true, + lastName: true, + }) }) it('should infer group visibility toggles from its leaf columns', () => { @@ -222,7 +299,7 @@ describe('columnVisibilityFeature.utils', () => { column_toggleVisibility(groupColumn) const result = getUpdaterResult(onColumnVisibilityChange, {}) - expect(result).toEqual({ firstName: false, lastName: false }) + expect(result).toEqual({ firstName: false, lastName: false, name: false }) }) it('should not toggle when column cannot be hidden', () => {