CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-data-grid

Excel-like grid component built with React, with editors, keyboard navigation, copy & paste, and the like

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

selection.mddocs/

Selection and Events

Comprehensive selection system supporting single cells, multiple rows, and range selection with keyboard shortcuts and event handling.

Capabilities

Row Selection

Multi-row selection system with various selection methods and interaction patterns.

interface RowSelection {
  /** Enable shift-click for multi-row selection */
  enableShiftSelect?: boolean;
  /** Called when rows are selected */
  onRowsSelected?: (rows: SelectedRow[]) => void;
  /** Called when rows are deselected */
  onRowsDeselected?: (rows: SelectedRow[]) => void;
  /** Show checkbox column for row selection */
  showCheckbox?: boolean;
  /** Method for determining which rows are selected */
  selectBy: SelectionMethod;
}

type SelectionMethod = 
  | { indexes: number[] }
  | { isSelectedKey: string }
  | { keys: { values: any[]; rowKey: string } };

interface SelectedRow {
  rowIdx: number;
  row: any;
}

Row Selection Examples:

import ReactDataGrid from 'react-data-grid';

// Selection by indexes
const IndexBasedSelection = () => {
  const [selectedIndexes, setSelectedIndexes] = useState([]);
  
  const rowSelection = {
    showCheckbox: true,
    enableShiftSelect: true,
    onRowsSelected: (rows) => {
      const newIndexes = [...selectedIndexes, ...rows.map(r => r.rowIdx)];
      setSelectedIndexes(newIndexes);
    },
    onRowsDeselected: (rows) => {
      const deselectedIndexes = rows.map(r => r.rowIdx);
      setSelectedIndexes(selectedIndexes.filter(i => !deselectedIndexes.includes(i)));
    },
    selectBy: {
      indexes: selectedIndexes
    }
  };
  
  return (
    <ReactDataGrid
      columns={columns}
      rowGetter={i => rows[i]}
      rowsCount={rows.length}
      minHeight={400}
      rowSelection={rowSelection}
    />
  );
};

// Selection by row property
const PropertyBasedSelection = () => {
  const [rows, setRows] = useState(initialRows);
  
  const rowSelection = {
    showCheckbox: true,
    onRowsSelected: (selectedRows) => {
      const updatedRows = rows.map(row => {
        const isSelected = selectedRows.some(sr => sr.row.id === row.id);
        return { ...row, isSelected };
      });
      setRows(updatedRows);
    },
    onRowsDeselected: (deselectedRows) => {
      const updatedRows = rows.map(row => {
        const isDeselected = deselectedRows.some(dr => dr.row.id === row.id);
        return isDeselected ? { ...row, isSelected: false } : row;
      });
      setRows(updatedRows);
    },
    selectBy: {
      isSelectedKey: 'isSelected'
    }
  };
  
  return (
    <ReactDataGrid
      columns={columns}
      rowGetter={i => rows[i]}
      rowsCount={rows.length}
      minHeight={400}
      rowSelection={rowSelection}
    />
  );
};

// Selection by unique keys
const KeyBasedSelection = () => {
  const [selectedKeys, setSelectedKeys] = useState([]);
  
  const rowSelection = {
    showCheckbox: true,
    enableShiftSelect: true,
    onRowsSelected: (rows) => {
      const newKeys = [...selectedKeys, ...rows.map(r => r.row.id)];
      setSelectedKeys([...new Set(newKeys)]);
    },
    onRowsDeselected: (rows) => {
      const deselectedKeys = rows.map(r => r.row.id);
      setSelectedKeys(selectedKeys.filter(k => !deselectedKeys.includes(k)));
    },
    selectBy: {
      keys: {
        values: selectedKeys,
        rowKey: 'id'
      }
    }
  };
  
  return (
    <ReactDataGrid
      columns={columns}
      rowGetter={i => rows[i]}
      rowsCount={rows.length}
      minHeight={400}
      rowSelection={rowSelection}
    />
  );
};

Cell Selection

Single cell and range selection for data manipulation and navigation.

interface CellSelection {
  /** Enable cell selection functionality */
  enableCellSelect?: boolean;
  /** Called when a cell is selected */
  onCellSelected?: (position: Position) => void;
  /** Called when a cell is deselected */
  onCellDeSelected?: (position: Position) => void;
  /** Cell navigation mode */
  cellNavigationMode?: 'none' | 'loopOverRow' | 'changeRow';
}

interface Position {
  /** Column index */
  idx: number;
  /** Row index */
  rowIdx: number;
}

Cell Selection Example:

const CellSelectionGrid = () => {
  const [selectedCell, setSelectedCell] = useState(null);
  
  const handleCellSelected = (position) => {
    setSelectedCell(position);
    console.log('Cell selected:', position);
  };
  
  const handleCellDeSelected = (position) => {
    setSelectedCell(null);
    console.log('Cell deselected:', position);
  };
  
  return (
    <ReactDataGrid
      columns={columns}
      rowGetter={i => rows[i]}
      rowsCount={rows.length}
      minHeight={400}
      enableCellSelect={true}
      cellNavigationMode="changeRow"
      onCellSelected={handleCellSelected}
      onCellDeSelected={handleCellDeSelected}
    />
  );
};

Cell Range Selection

Multi-cell range selection for advanced data operations.

interface CellRangeSelection {
  /** Called when range selection begins */
  onStart?: (selectedRange: SelectionRange) => void;
  /** Called during range selection updates */
  onUpdate?: (selectedRange: SelectionRange) => void;
  /** Called when range selection is completed */
  onComplete?: (selectedRange: SelectionRange) => void;
}

interface SelectionRange {
  /** Top-left corner of selection */
  topLeft: Position;
  /** Bottom-right corner of selection */
  bottomRight: Position;
}

Range Selection Example:

const RangeSelectionGrid = () => {
  const [selectionRange, setSelectionRange] = useState(null);
  
  const cellRangeSelection = {
    onStart: (range) => {
      console.log('Range selection started:', range);
      setSelectionRange(range);
    },
    onUpdate: (range) => {
      console.log('Range selection updated:', range);
      setSelectionRange(range);
    },
    onComplete: (range) => {
      console.log('Range selection completed:', range);
      // Perform operations on selected range
      const selectedData = extractRangeData(range);
      console.log('Selected data:', selectedData);
    }
  };
  
  const extractRangeData = (range) => {
    const data = [];
    for (let rowIdx = range.topLeft.rowIdx; rowIdx <= range.bottomRight.rowIdx; rowIdx++) {
      const row = rows[rowIdx];
      const rowData = {};
      for (let colIdx = range.topLeft.idx; colIdx <= range.bottomRight.idx; colIdx++) {
        const column = columns[colIdx];
        rowData[column.key] = row[column.key];
      }
      data.push(rowData);
    }
    return data;
  };
  
  return (
    <ReactDataGrid
      columns={columns}
      rowGetter={i => rows[i]}
      rowsCount={rows.length}
      minHeight={400}
      enableCellSelect={true}
      cellRangeSelection={cellRangeSelection}
    />
  );
};

Event Handling

Comprehensive event system for handling user interactions and grid events.

interface GridEvents {
  // Row Events
  /** Called when a row is clicked */
  onRowClick?: (rowIdx: number, row: any, column: Column) => void;
  /** Called when a row is double-clicked */
  onRowDoubleClick?: (rowIdx: number, row: any, column: Column) => void;
  /** Called when a row is selected */
  onRowSelect?: (rowIdx: number, row: any) => void;
  
  // Cell Events
  /** Called when a cell is clicked */
  onCellClick?: (position: Position, value: any, row: any) => void;
  /** Called when a cell is double-clicked */
  onCellDoubleClick?: (position: Position, value: any, row: any) => void;
  /** Called when a cell context menu is triggered */
  onCellContextMenu?: (position: Position, value: any, row: any) => void;
  /** Called when a cell is expanded */
  onCellExpand?: (args: CellExpandArgs) => void;
  
  // Keyboard Events
  /** Called on key down events */
  onGridKeyDown?: (event: GridKeyboardEvent) => void;
  /** Called on key up events */
  onGridKeyUp?: (event: GridKeyboardEvent) => void;
  
  // Data Events
  /** Called when grid data is updated */
  onGridRowsUpdated?: (event: GridRowsUpdatedEvent) => void;
  /** Called when grid is sorted */
  onGridSort?: (sortColumn: string, sortDirection: SortDirection) => void;
  /** Called when grid is filtered */
  onFilter?: (filter: Filter) => void;
  
  // Layout Events
  /** Called when grid is scrolled */
  onScroll?: (scrollState: ScrollState) => void;
  /** Called when a column is resized */
  onColumnResize?: (idx: number, width: number) => void;
}

interface GridKeyboardEvent {
  /** Row index */
  rowIdx: number;
  /** Column index */
  idx: number;
  /** Key that was pressed */
  key: string;
  /** Original keyboard event */
  originalEvent: KeyboardEvent;
}

interface CellExpandArgs {
  /** Row index */
  rowIdx: number;
  /** Column index */
  idx: number;
  /** Row data */
  rowData: any;
  /** Expand arguments */
  expandArgs: any;
}

Comprehensive Event Handling Example:

const EventHandlingGrid = () => {
  const [eventLog, setEventLog] = useState([]);
  
  const addEvent = (eventType, data) => {
    const event = {
      timestamp: new Date().toISOString(),
      type: eventType,
      data
    };
    setEventLog(prev => [event, ...prev.slice(0, 9)]); // Keep last 10 events
  };
  
  const handleRowClick = (rowIdx, row, column) => {
    addEvent('Row Click', { rowIdx, row: row.name, column: column.name });
  };
  
  const handleRowDoubleClick = (rowIdx, row, column) => {
    addEvent('Row Double Click', { rowIdx, row: row.name });
  };
  
  const handleCellClick = (position, value, row) => {
    addEvent('Cell Click', { position, value, row: row.name });
  };
  
  const handleKeyDown = (event) => {
    addEvent('Key Down', { 
      key: event.key, 
      position: { rowIdx: event.rowIdx, idx: event.idx }
    });
  };
  
  const handleGridSort = (sortColumn, sortDirection) => {
    addEvent('Sort', { sortColumn, sortDirection });
  };
  
  const handleScroll = (scrollState) => {
    addEvent('Scroll', { 
      scrollTop: scrollState.scrollTop, 
      scrollLeft: scrollState.scrollLeft 
    });
  };
  
  return (
    <div>
      <ReactDataGrid
        columns={columns}
        rowGetter={i => rows[i]}
        rowsCount={rows.length}
        minHeight={400}
        enableCellSelect={true}
        onRowClick={handleRowClick}
        onRowDoubleClick={handleRowDoubleClick}
        onCellClick={handleCellClick}
        onGridKeyDown={handleKeyDown}
        onGridSort={handleGridSort}
        onScroll={handleScroll}
      />
      
      {/* Event Log Display */}
      <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f8f9fa' }}>
        <h4>Event Log:</h4>
        {eventLog.map((event, index) => (
          <div key={index} style={{ fontSize: '12px', marginBottom: '4px' }}>
            <strong>{event.type}</strong> - {event.timestamp} - 
            {JSON.stringify(event.data)}
          </div>
        ))}
      </div>
    </div>
  );
};

Keyboard Navigation

Built-in keyboard shortcuts for navigation and interaction.

interface KeyboardNavigation {
  /** Cell navigation behavior */
  cellNavigationMode?: 'none' | 'loopOverRow' | 'changeRow';
  /** Enable automatic cell focus */
  enableCellAutoFocus?: boolean;
}

Keyboard Shortcuts:

  • Arrow Keys: Navigate between cells
  • Tab: Move to next cell (with loopOverRow mode)
  • Shift+Tab: Move to previous cell
  • Enter: Start editing or confirm edit
  • Escape: Cancel editing
  • Ctrl+C: Copy selected cell(s)
  • Ctrl+V: Paste copied content
  • Delete: Clear selected cell content
  • Home: Move to first column in row
  • End: Move to last column in row
  • Page Up/Down: Scroll grid up/down
  • Ctrl+Home: Move to first cell
  • Ctrl+End: Move to last cell

Custom Keyboard Handling:

const CustomKeyboardGrid = () => {
  const handleKeyDown = (event) => {
    const { key, rowIdx, idx } = event;
    
    switch (key) {
      case 'F2':
        // Custom: Start editing with F2
        console.log('Start editing cell:', { rowIdx, idx });
        event.originalEvent.preventDefault();
        break;
        
      case 'Delete':
        // Custom: Clear cell content
        console.log('Clear cell:', { rowIdx, idx });
        // Implement clear logic
        break;
        
      case 'Insert':
        // Custom: Insert new row
        console.log('Insert new row at:', rowIdx);
        // Implement insert logic
        break;
        
      default:
        // Let grid handle other keys
        break;
    }
  };
  
  return (
    <ReactDataGrid
      columns={columns}
      rowGetter={i => rows[i]}
      rowsCount={rows.length}
      minHeight={400}
      enableCellSelect={true}
      cellNavigationMode="changeRow"
      onGridKeyDown={handleKeyDown}
    />
  );
};

Selection State Management

Managing selection state and integrating with application state.

const SelectionStateGrid = () => {
  const [rows, setRows] = useState(initialRows);
  const [selectedRowIds, setSelectedRowIds] = useState([]);
  const [selectedCell, setSelectedCell] = useState(null);
  
  // Row selection handlers
  const handleRowsSelected = (selectedRows) => {
    const newIds = selectedRows.map(r => r.row.id);
    setSelectedRowIds(prev => [...new Set([...prev, ...newIds])]);
  };
  
  const handleRowsDeselected = (deselectedRows) => {
    const deselectedIds = deselectedRows.map(r => r.row.id);
    setSelectedRowIds(prev => prev.filter(id => !deselectedIds.includes(id)));
  };
  
  // Cell selection handlers
  const handleCellSelected = (position) => {
    setSelectedCell(position);
  };
  
  // Programmatic selection methods
  const selectAllRows = () => {
    const allIds = rows.map(row => row.id);
    setSelectedRowIds(allIds);
  };
  
  const clearSelection = () => {
    setSelectedRowIds([]);
    setSelectedCell(null);
  };
  
  const selectRowById = (id) => {
    if (!selectedRowIds.includes(id)) {
      setSelectedRowIds(prev => [...prev, id]);
    }
  };
  
  return (
    <div>
      {/* Selection Controls */}
      <div style={{ marginBottom: '10px' }}>
        <button onClick={selectAllRows}>Select All</button>
        <button onClick={clearSelection}>Clear Selection</button>
        <span style={{ marginLeft: '20px' }}>
          Selected: {selectedRowIds.length} rows
        </span>
      </div>
      
      <ReactDataGrid
        columns={columns}
        rowGetter={i => rows[i]}
        rowsCount={rows.length}
        minHeight={400}
        enableCellSelect={true}
        rowSelection={{
          showCheckbox: true,
          enableShiftSelect: true,
          onRowsSelected: handleRowsSelected,
          onRowsDeselected: handleRowsDeselected,
          selectBy: {
            keys: {
              values: selectedRowIds,
              rowKey: 'id'
            }
          }
        }}
        onCellSelected={handleCellSelected}
      />
    </div>
  );
};

Install with Tessl CLI

npx tessl i tessl/npm-react-data-grid@6.1.1

docs

components.md

core-grid.md

editors.md

formatters.md

index.md

selection.md

utilities.md

tile.json