From 67a2916062880d3ea386d87628e6440cfa1137d1 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 17 Oct 2026 16:25:51 +0200 Subject: [PATCH 1/2] fix(Grid): copy selected columns from the current rowStart --- .changeset/fix-grid-copy-row-start.md | 5 ++ src/components/Grid/Grid.test.tsx | 25 +++++++++ src/components/Grid/Grid.tsx | 2 + src/components/Grid/copyGridElements.test.tsx | 52 +++++++++++++++++++ src/components/Grid/copyGridElements.ts | 8 +-- 5 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-grid-copy-row-start.md diff --git a/.changeset/fix-grid-copy-row-start.md b/.changeset/fix-grid-copy-row-start.md new file mode 100644 index 000000000..bd6bb01f6 --- /dev/null +++ b/.changeset/fix-grid-copy-row-start.md @@ -0,0 +1,5 @@ +--- +'@clickhouse/click-ui': patch +--- + +Copy selected Grid columns from the current row offset. diff --git a/src/components/Grid/Grid.test.tsx b/src/components/Grid/Grid.test.tsx index a6807acde..2706daaa0 100644 --- a/src/components/Grid/Grid.test.tsx +++ b/src/components/Grid/Grid.test.tsx @@ -3,6 +3,12 @@ import type { CellProps, GridProps } from '@/components/Grid/types'; import { renderCUI } from '@/utils/test-utils'; import { SelectionFocus } from './types'; import { ReactNode } from 'react'; +import { fireEvent } from '@testing-library/dom'; +import copyGridElements from './copyGridElements'; + +vi.mock('./copyGridElements', () => ({ + default: vi.fn().mockResolvedValue(undefined), +})); const Cell: CellProps = ({ type, rowIndex, columnIndex, isScrolling, ...props }) => { let content = `${rowIndex} ${columnIndex} - ${type}`; @@ -145,4 +151,23 @@ describe('Grid', () => { const computedHeight = window.getComputedStyle(cell).height; expect(computedHeight).toBe('100%'); }); + + it('passes rowStart to the default copy handler', () => { + vi.mocked(copyGridElements).mockClear(); + const { getByTestId } = renderGrid({ + rowStart: 100, + rowCount: 3, + columnCount: 2, + focus: { row: 100, column: 0 }, + }); + + fireEvent.keyDown(getByTestId('grid-outer-element'), { + key: 'c', + ctrlKey: true, + }); + + expect(copyGridElements).toHaveBeenCalledWith( + expect.objectContaining({ rowStart: 100, rowCount: 3, columnCount: 2 }) + ); + }); }); diff --git a/src/components/Grid/Grid.tsx b/src/components/Grid/Grid.tsx index 6cc8e98a6..ace19d792 100644 --- a/src/components/Grid/Grid.tsx +++ b/src/components/Grid/Grid.tsx @@ -158,6 +158,7 @@ export const Grid = forwardRef( focus: focusProp ?? focus, rowCount, columnCount, + rowStart, outerRef: outerRef, }); @@ -192,6 +193,7 @@ export const Grid = forwardRef( focus, focusProp, rowCount, + rowStart, selection, showToast, onCopyCallback, diff --git a/src/components/Grid/copyGridElements.test.tsx b/src/components/Grid/copyGridElements.test.tsx index fe2e56069..415fed8c2 100644 --- a/src/components/Grid/copyGridElements.test.tsx +++ b/src/components/Grid/copyGridElements.test.tsx @@ -66,4 +66,56 @@ describe('copyGridElements', () => { [10, 0], ]); }); + + it('copies selected columns using the current rowStart', async () => { + const outerRef = { current: document.createElement('div') }; + + await copyGridElements({ + cell: Cell, + selection: { + type: 'columns', + columns: new Set([2, 0]), + anchorColumn: 2, + }, + rowCount: 3, + columnCount: 4, + rowStart: 100, + focus: { row: 100, column: 0 }, + outerRef, + }); + + expect(renderedCells).toEqual([ + [100, 0], + [100, 2], + [101, 0], + [101, 2], + [102, 0], + [102, 2], + ]); + }); + + it('keeps column indexes zero-based when copying rows with rowStart', async () => { + const outerRef = { current: document.createElement('div') }; + + await copyGridElements({ + cell: Cell, + selection: { + type: 'rows', + rows: new Set([101, 100]), + anchorRow: 101, + }, + rowCount: 3, + columnCount: 2, + rowStart: 100, + focus: { row: 100, column: 0 }, + outerRef, + }); + + expect(renderedCells).toEqual([ + [100, 0], + [100, 1], + [101, 0], + [101, 1], + ]); + }); }); diff --git a/src/components/Grid/copyGridElements.ts b/src/components/Grid/copyGridElements.ts index 8f2e22386..d653402ac 100644 --- a/src/components/Grid/copyGridElements.ts +++ b/src/components/Grid/copyGridElements.ts @@ -8,7 +8,7 @@ interface CopyGridElementsProps { selection: SelectedRegion; rowCount: number; columnCount: number; - pageStart?: number; + rowStart?: number; focus: SelectionFocus; outerRef: RefObject; } @@ -53,7 +53,7 @@ const copyGridElements = async ({ selection, rowCount, columnCount, - pageStart = 0, + rowStart = 0, focus, outerRef, }: CopyGridElementsProps): Promise => { @@ -84,7 +84,7 @@ const copyGridElements = async ({ }); break; case 'columns': - Array.from({ length: rowCount }, (_, index) => pageStart + index).forEach( + Array.from({ length: rowCount }, (_, index) => rowStart + index).forEach( rowIndex => { const columnList = [...selection.columns].sort((a, b) => a - b); columnListLoop(tbody, columnList, cell, rowIndex); @@ -97,7 +97,7 @@ const copyGridElements = async ({ .forEach(rowIndex => { const columnList = Array.from( { length: columnCount }, - (_, index) => pageStart + index + (_, index) => index ); columnListLoop(tbody, columnList, cell, rowIndex); }); From 6f6b15a7451d8f54e39df4b7732aae7b5514b21e Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Wed, 21 Oct 2026 10:52:13 +0200 Subject: [PATCH 2/2] style(Grid): apply Prettier formatting to copy helper --- src/components/Grid/copyGridElements.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/Grid/copyGridElements.ts b/src/components/Grid/copyGridElements.ts index d653402ac..78d67091a 100644 --- a/src/components/Grid/copyGridElements.ts +++ b/src/components/Grid/copyGridElements.ts @@ -95,10 +95,7 @@ const copyGridElements = async ({ [...selection.rows] .sort((a, b) => a - b) .forEach(rowIndex => { - const columnList = Array.from( - { length: columnCount }, - (_, index) => index - ); + const columnList = Array.from({ length: columnCount }, (_, index) => index); columnListLoop(tbody, columnList, cell, rowIndex); }); break;