Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions public/r/data-grid.json

Large diffs are not rendered by default.

72 changes: 57 additions & 15 deletions src/hooks/use-data-grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,18 @@ function useDataGrid<TData>({
}
}

const newData: TData[] = new Array(currentData.length);
const maxUpdateIndex =
rowUpdatesMap.size > 0
? Math.max(...Array.from(rowUpdatesMap.keys()))
: -1;
const dataLength = Math.max(currentData.length, maxUpdateIndex + 1);

for (let i = 0; i < currentData.length; i++) {
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;

Expand Down Expand Up @@ -718,13 +725,50 @@ function useDataGrid<TData>({
if (!clipboardText) return;
}

const pastedData = parseTsv(clipboardText, navigableColumnIds.length);
const rawPastedData = parseTsv(
clipboardText,
navigableColumnIds.length,
);

// Fill entire selection when clipboard has a single value and multiple cells are selected
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),
);
}
Comment thread
sadmann7 marked this conversation as resolved.
}

if (startColIndex === -1) return;

const rowCount = rows.length ?? propsRef.current.data.length;
Expand Down Expand Up @@ -855,7 +899,7 @@ function useDataGrid<TData>({
case "select": {
const options = cellOpts?.options ?? [];
if (!pastedValue) {
processedValue = "";
processedValue = null;
} else {
Comment thread
sadmann7 marked this conversation as resolved.
const matched = matchSelectOption(pastedValue, options);
if (matched) processedValue = matched;
Expand Down Expand Up @@ -1085,7 +1129,7 @@ function useDataGrid<TData>({
);

// Release focus guard after delay to allow async data re-renders to settle.
// 300ms accounts for db sync and virtualized cell mounting.
// 300ms accounts for db sync and virtualized cell mounting
const releaseFocusGuard = React.useCallback((immediate = false) => {
if (immediate) {
focusGuardRef.current = false;
Expand Down Expand Up @@ -1361,7 +1405,7 @@ function useDataGrid<TData>({
});
}
} else {
// Fallback: use direct scroll calculation when virtualizer is not available
// Use direct scroll calculation when virtualizer is not available
const rowHeightValue = getRowHeightValue(rowHeight);
const estimatedScrollTop = newRowIndex * rowHeightValue;
container.scrollTop = estimatedScrollTop;
Expand Down Expand Up @@ -1646,7 +1690,6 @@ function useDataGrid<TData>({
);

// Compute search match data for targeted row re-renders
// Maps rowIndex -> Set of columnIds that have matches in that row
const searchMatchesByRow = React.useMemo(() => {
if (searchMatches.length === 0) return null;
const rowMap = new Map<number, Set<string>>();
Expand Down Expand Up @@ -2048,8 +2091,8 @@ function useDataGrid<TData>({

const defaultColumn: Partial<ColumnDef<TData>> = React.useMemo(
() => ({
// Note: cell is rendered directly in DataGridRow to bypass flexRender's
// unstable cell.getContext() (see TanStack Table issue #4794)
// Cell is rendered directly in DataGridRow to bypass flexRender's
// unstable cell.getContext() (https://github.com/TanStack/table/issues/4794)
minSize: MIN_COLUMN_SIZE,
maxSize: MAX_COLUMN_SIZE,
}),
Expand Down Expand Up @@ -2373,10 +2416,10 @@ function useDataGrid<TData>({

if (!isFullyVisible) {
if (rowRect.top < viewportTop) {
// Row is partially hidden by header - scroll up
// Scroll up as row is partially hidden by header
container.scrollTop -= viewportTop - rowRect.top;
} else if (rowRect.bottom > viewportBottom) {
// Row is partially hidden by footer - scroll down
// Scroll down as row is partially hidden by footer
container.scrollTop += rowRect.bottom - viewportBottom;
}
}
Expand Down Expand Up @@ -2428,7 +2471,6 @@ function useDataGrid<TData>({

onSelectionClear();

// Trust the returned rowIndex from the callback
// onScrollToRow will handle retries if the row isn't rendered yet
const targetRowIndex = result.rowIndex ?? initialRowCount;
const targetColumnId = result.columnId;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/data-grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export function parseTsv(
return rows;
}

const lines = text.split("\n");
const lines = text.split("\n").map((l) => l.replace(/\r$/, ""));
Comment thread
sadmann7 marked this conversation as resolved.
let maxTabCount = 0;
for (const line of lines) {
const n = countTabs(line);
Expand Down
Loading