From d4f9bfd640176d88eb336914d45d7d6355632b3a Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sun, 14 Jun 2026 16:52:16 +0600 Subject: [PATCH] fix(data-grid): fix range pasting --- src/hooks/use-data-grid.ts | 64 +++++++++++++++++++++++++++++++++----- src/lib/data-grid.ts | 2 +- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/hooks/use-data-grid.ts b/src/hooks/use-data-grid.ts index a489bc74c..288e95295 100644 --- a/src/hooks/use-data-grid.ts +++ b/src/hooks/use-data-grid.ts @@ -404,11 +404,21 @@ function useDataGrid({ } } - const newData: TData[] = new Array(currentData.length); - - for (let i = 0; i < currentData.length; i++) { + // Determine the full range of indices to cover — including any rows + // that were just added by onRowsAdd and are in the table but may not + // yet be reflected in propsRef.current.data (stale closure timing). + const maxUpdateIndex = + rowUpdatesMap.size > 0 + ? Math.max(...Array.from(rowUpdatesMap.keys())) + : -1; + const dataLength = Math.max(currentData.length, maxUpdateIndex + 1); + + const newData: TData[] = new Array(dataLength); + + for (let i = 0; i < dataLength; i++) { const updates = rowUpdatesMap.get(i); - const existingRow = currentData[i]; + // Fall back to the table's row data for rows not yet in currentData + const existingRow = currentData[i] ?? rows?.[i]?.original; if (existingRow == null) continue; @@ -718,13 +728,51 @@ function useDataGrid({ if (!clipboardText) return; } - const pastedData = parseTsv(clipboardText, navigableColumnIds.length); + const rawPastedData = parseTsv( + clipboardText, + navigableColumnIds.length, + ); + + // When clipboard has exactly one cell and the user has multiple cells + // selected, fill the entire selection with that single value. + const selectionCells = currentState.selectionState.selectedCells; + const isSingleCellClipboard = + rawPastedData.length === 1 && (rawPastedData[0]?.length ?? 0) === 1; - const startRowIndex = currentState.focusedCell.rowIndex; - const startColIndex = navigableColumnIds.indexOf( + let pastedData = rawPastedData; + let startRowIndex = currentState.focusedCell.rowIndex; + let startColIndex = navigableColumnIds.indexOf( currentState.focusedCell.columnId, ); + if (isSingleCellClipboard && selectionCells.size > 1) { + const singleValue = rawPastedData[0]?.[0] ?? ""; + let minRow = Infinity; + let maxRow = -Infinity; + let minColIdx = Infinity; + let maxColIdx = -Infinity; + + for (const cellKey of selectionCells) { + const { rowIndex, columnId } = parseCellKey(cellKey); + const colIdx = navigableColumnIds.indexOf(columnId); + if (colIdx === -1) continue; + minRow = Math.min(minRow, rowIndex); + maxRow = Math.max(maxRow, rowIndex); + minColIdx = Math.min(minColIdx, colIdx); + maxColIdx = Math.max(maxColIdx, colIdx); + } + + if (minRow !== Infinity) { + startRowIndex = minRow; + startColIndex = minColIdx; + const numRows = maxRow - minRow + 1; + const numCols = maxColIdx - minColIdx + 1; + pastedData = Array.from({ length: numRows }, () => + Array.from({ length: numCols }, () => singleValue), + ); + } + } + if (startColIndex === -1) return; const rowCount = rows.length ?? propsRef.current.data.length; @@ -855,7 +903,7 @@ function useDataGrid({ case "select": { const options = cellOpts?.options ?? []; if (!pastedValue) { - processedValue = ""; + processedValue = null; } else { const matched = matchSelectOption(pastedValue, options); if (matched) processedValue = matched; diff --git a/src/lib/data-grid.ts b/src/lib/data-grid.ts index 1c83aff14..0a84142e2 100644 --- a/src/lib/data-grid.ts +++ b/src/lib/data-grid.ts @@ -327,7 +327,7 @@ export function parseTsv( return rows; } - const lines = text.split("\n"); + const lines = text.split("\n").map((l) => l.replace(/\r$/, "")); let maxTabCount = 0; for (const line of lines) { const n = countTabs(line);