From d546765b9e5daf4f13133db29ee86c3a830d863d Mon Sep 17 00:00:00 2001 From: sadman <68962712+sadmann7@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:00:17 +0600 Subject: [PATCH 1/2] fix(data-grid): exclude select column from cmd+a selection --- src/hooks/test/use-data-grid.test.tsx | 88 ++++++++++++++++++- .../bases/base/hooks/use-data-grid.ts | 17 ++-- .../bases/radix/hooks/use-data-grid.ts | 17 ++-- 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/hooks/test/use-data-grid.test.tsx b/src/hooks/test/use-data-grid.test.tsx index b84156fe..e1c0e14b 100644 --- a/src/hooks/test/use-data-grid.test.tsx +++ b/src/hooks/test/use-data-grid.test.tsx @@ -1,7 +1,8 @@ import type { ColumnDef } from "@tanstack/react-table"; import type * as React from "react"; -import { act, renderHook } from "@testing-library/react"; +import { act, render, renderHook } from "@testing-library/react"; +import { useEffect } from "react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { DataGridFeatures } from "@/lib/data-grid-features"; @@ -56,6 +57,13 @@ const testColumns: ColumnDef[] = [ }, ]; +const columnsWithSelect: ColumnDef[] = [ + { id: "select" }, + { id: "name", accessorKey: "name" }, + { id: "trick", accessorKey: "trick" }, + { id: "actions" }, +]; + function createWrapper() { return function Wrapper({ children }: { children: React.ReactNode }) { return <>{children}; @@ -1520,6 +1528,84 @@ describe("useDataGrid", () => { ); }); + it("should select data cells with Cmd+A and skip the select column", () => { + const gridRef: { + current?: ReturnType>; + } = {}; + + function Harness() { + const api = useDataGrid({ + data: testData.slice(0, 2), + columns: columnsWithSelect, + }); + + useEffect(() => { + gridRef.current = api; + }); + + return
; + } + + const { container } = render(); + const grid = gridRef.current; + const gridElement = container.firstElementChild as HTMLElement; + + act(() => { + grid?.tableMeta.onCellClick?.(0, "name"); + }); + + act(() => { + gridElement.dispatchEvent( + new KeyboardEvent("keydown", { + key: "a", + metaKey: true, + bubbles: true, + cancelable: true, + }), + ); + }); + + // 2 rows × 2 data columns. select and actions are not cells. + expect(grid?.tableMeta.selectionState?.selectedCells.size).toBe(4); + expect(grid?.tableMeta.getIsCellSelected?.(0, "name")).toBe(true); + expect(grid?.tableMeta.getIsCellSelected?.(1, "trick")).toBe(true); + expect(grid?.tableMeta.getIsCellSelected?.(0, "select")).toBe(false); + expect(grid?.tableMeta.getIsCellSelected?.(1, "actions")).toBe(false); + expect(grid?.tableMeta.selectionState?.selectionRange).toEqual({ + start: { rowIndex: 0, columnId: "name" }, + end: { rowIndex: 1, columnId: "trick" }, + }); + }); + + it("should not count the select column when selecting rows", () => { + const { result } = renderHook( + () => + useDataGrid({ + data: testData.slice(0, 2), + columns: columnsWithSelect, + getRowId: (row) => row.id, + }), + { wrapper: createWrapper() }, + ); + + act(() => { + result.current.table.toggleAllRowsSelected(true); + }); + + expect(result.current.tableMeta.selectionState?.selectedCells.size).toBe( + 4, + ); + expect(result.current.tableMeta.getIsCellSelected?.(0, "select")).toBe( + false, + ); + expect(result.current.tableMeta.getIsCellSelected?.(0, "actions")).toBe( + false, + ); + expect(result.current.tableMeta.getIsCellSelected?.(1, "name")).toBe( + true, + ); + }); + it("should clear selection when clicking column with enableColumnSelection false", () => { const { result } = renderHook( () => diff --git a/src/registry/bases/base/hooks/use-data-grid.ts b/src/registry/bases/base/hooks/use-data-grid.ts index 9081c530..4e41f5f3 100644 --- a/src/registry/bases/base/hooks/use-data-grid.ts +++ b/src/registry/bases/base/hooks/use-data-grid.ts @@ -486,18 +486,21 @@ function useDataGrid({ const rowCount = rows.length ?? propsRef.current.data.length; for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { - for (const columnId of columnIds) { + for (const columnId of navigableColumnIds) { allCells.add(getCellKey(rowIndex, columnId)); } } - const firstColumnId = columnIds[0]; - const lastColumnId = columnIds[columnIds.length - 1]; + const firstColumnId = navigableColumnIds[0]; + const lastColumnId = navigableColumnIds[navigableColumnIds.length - 1]; store.setState("selectionState", { selectedCells: allCells, selectionRange: - columnIds.length > 0 && rowCount > 0 && firstColumnId && lastColumnId + navigableColumnIds.length > 0 && + rowCount > 0 && + firstColumnId && + lastColumnId ? { start: { rowIndex: 0, columnId: firstColumnId }, end: { rowIndex: rowCount - 1, columnId: lastColumnId }, @@ -505,7 +508,7 @@ function useDataGrid({ : null, isSelecting: false, }); - }, [columnIds, propsRef, store]); + }, [navigableColumnIds, propsRef, store]); const selectColumn = React.useCallback( (columnId: string) => { @@ -2010,7 +2013,7 @@ function useDataGrid({ const rowIndex = rows.findIndex((r) => r.id === rowId); if (rowIndex === -1) continue; - for (const columnId of columnIds) { + for (const columnId of navigableColumnIds) { selectedCells.add(getCellKey(rowIndex, columnId)); } } @@ -2028,7 +2031,7 @@ function useDataGrid({ propsRef.current.onRowSelectionChange?.(updater); }, - [store, columnIds, propsRef], + [store, navigableColumnIds, propsRef], ); const onRowSelect = React.useCallback( diff --git a/src/registry/bases/radix/hooks/use-data-grid.ts b/src/registry/bases/radix/hooks/use-data-grid.ts index 06952d8e..22ccbd2e 100644 --- a/src/registry/bases/radix/hooks/use-data-grid.ts +++ b/src/registry/bases/radix/hooks/use-data-grid.ts @@ -486,18 +486,21 @@ function useDataGrid({ const rowCount = rows.length ?? propsRef.current.data.length; for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { - for (const columnId of columnIds) { + for (const columnId of navigableColumnIds) { allCells.add(getCellKey(rowIndex, columnId)); } } - const firstColumnId = columnIds[0]; - const lastColumnId = columnIds[columnIds.length - 1]; + const firstColumnId = navigableColumnIds[0]; + const lastColumnId = navigableColumnIds[navigableColumnIds.length - 1]; store.setState("selectionState", { selectedCells: allCells, selectionRange: - columnIds.length > 0 && rowCount > 0 && firstColumnId && lastColumnId + navigableColumnIds.length > 0 && + rowCount > 0 && + firstColumnId && + lastColumnId ? { start: { rowIndex: 0, columnId: firstColumnId }, end: { rowIndex: rowCount - 1, columnId: lastColumnId }, @@ -505,7 +508,7 @@ function useDataGrid({ : null, isSelecting: false, }); - }, [columnIds, propsRef, store]); + }, [navigableColumnIds, propsRef, store]); const selectColumn = React.useCallback( (columnId: string) => { @@ -2010,7 +2013,7 @@ function useDataGrid({ const rowIndex = rows.findIndex((r) => r.id === rowId); if (rowIndex === -1) continue; - for (const columnId of columnIds) { + for (const columnId of navigableColumnIds) { selectedCells.add(getCellKey(rowIndex, columnId)); } } @@ -2028,7 +2031,7 @@ function useDataGrid({ propsRef.current.onRowSelectionChange?.(updater); }, - [store, columnIds, propsRef], + [store, navigableColumnIds, propsRef], ); const onRowSelect = React.useCallback( From 88533326294fa309eb98f8d35c4219a7cf9f29e3 Mon Sep 17 00:00:00 2001 From: sadman <68962712+sadmann7@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:11:55 +0600 Subject: [PATCH 2/2] chore: update test --- src/hooks/test/use-data-grid.test.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hooks/test/use-data-grid.test.tsx b/src/hooks/test/use-data-grid.test.tsx index e1c0e14b..2259b942 100644 --- a/src/hooks/test/use-data-grid.test.tsx +++ b/src/hooks/test/use-data-grid.test.tsx @@ -1528,7 +1528,7 @@ describe("useDataGrid", () => { ); }); - it("should select data cells with Cmd+A and skip the select column", () => { + it("should select data cells with Cmd+A and skip non-navigable columns", () => { const gridRef: { current?: ReturnType>; } = {}; @@ -1565,7 +1565,6 @@ describe("useDataGrid", () => { ); }); - // 2 rows × 2 data columns. select and actions are not cells. expect(grid?.tableMeta.selectionState?.selectedCells.size).toBe(4); expect(grid?.tableMeta.getIsCellSelected?.(0, "name")).toBe(true); expect(grid?.tableMeta.getIsCellSelected?.(1, "trick")).toBe(true); @@ -1577,7 +1576,7 @@ describe("useDataGrid", () => { }); }); - it("should not count the select column when selecting rows", () => { + it("should not count non-navigable columns when selecting rows", () => { const { result } = renderHook( () => useDataGrid({