From 98037e06e94ae7dca82144021a272c8f2d02e9e5 Mon Sep 17 00:00:00 2001 From: Prince Jain Date: Wed, 21 Oct 2026 16:00:51 +0530 Subject: [PATCH 1/2] fix(table-core): respect explicit columnVisibility on parent columns (#5770) Check columnVisibility[column.id] in column_getIsVisible before falling back to childColumns.some(...) so explicit visibility state on a parent group column is respected when present. --- .changeset/parent-column-visibility.md | 5 ++++ .../columnVisibilityFeature.utils.ts | 18 ++++++------ .../columnVisibilityFeature.utils.test.ts | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 .changeset/parent-column-visibility.md 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..bf3a56cc8c 100644 --- a/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts +++ b/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts @@ -71,8 +71,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 +89,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..5dc39528ed 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', () => { From 580d1897943657acb3d082fea60df581e56259f3 Mon Sep 17 00:00:00 2001 From: Prince Jain Date: Wed, 21 Oct 2026 16:43:31 +0530 Subject: [PATCH 2/2] keep explicit group visibility entry in sync on toggle --- .../columnVisibilityFeature.utils.ts | 1 + .../columnVisibilityFeature.utils.test.ts | 55 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) 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 bf3a56cc8c..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 }) 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 5dc39528ed..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 @@ -224,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', () => { @@ -250,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', () => {