From 447dffd155f2a423bc70e2a888f1c9a1541d417e Mon Sep 17 00:00:00 2001 From: "hady.elzayady" Date: Sat, 26 Mar 2022 10:06:06 +0200 Subject: [PATCH 1/3] select range by mouse or shift+arrow keys --- src/Cell.tsx | 7 + src/DataGrid.tsx | 161 +++++++++--- src/Row.tsx | 80 +++--- src/types.ts | 22 ++ src/utils/Helpers.ts | 6 + website/Nav.tsx | 3 + website/demos/RangeSelection.tsx | 404 +++++++++++++++++++++++++++++++ website/root.tsx | 2 + 8 files changed, 613 insertions(+), 72 deletions(-) create mode 100644 src/utils/Helpers.ts create mode 100644 website/demos/RangeSelection.tsx diff --git a/src/Cell.tsx b/src/Cell.tsx index c2058eab77..813be957c8 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -33,6 +33,7 @@ function Cell({ onRowDoubleClick, onRowChange, selectCell, + rangeSelectionMode, ...props }: CellRendererProps) { const { ref, tabIndex, onFocus } = useRovingCellRef(isCellSelected); @@ -65,6 +66,11 @@ function Cell({ onRowDoubleClick?.(row, column); } + function onMouseDown(){ + selectCellWrapper(false); + onRowClick?.(row, column); + } + return (
({ onDoubleClick={handleDoubleClick} onContextMenu={handleContextMenu} onFocus={onFocus} + onMouseDown={onMouseDown} {...props} > {!column.rowGroup && ( diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 508ae1c2a6..09c92f781c 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -58,8 +58,10 @@ import type { RowHeightArgs, Maybe, Components, - Direction + Direction, + MultiPasteEvent, CellsRange, MultiCopyEvent } from './types'; +import {isValueInBetween} from "./utils/Helpers"; export interface SelectCellState extends Position { readonly mode: 'SELECT'; @@ -82,6 +84,13 @@ const initialPosition: SelectCellState = { mode: 'SELECT' }; +const initialSelectedRange: CellsRange = { + startRowIdx: -1, + startColumnIdx: -1, + endRowIdx: -1, + endColumnIdx: -1, +}; + export interface DataGridHandle { element: HTMLDivElement | null; scrollToColumn: (colIdx: number) => void; @@ -148,6 +157,10 @@ export interface DataGridProps extends Sha onFill?: Maybe<(event: FillEvent) => R>; onCopy?: Maybe<(event: CopyEvent) => void>; onPaste?: Maybe<(event: PasteEvent) => R>; + onMultiPaste?: Maybe<(event: MultiPasteEvent) => void>; + onMultiCopy?: Maybe<(event: MultiCopyEvent) => void>; + onMultiCopySuccess?: Maybe<(copiedText: string) => void>; + onMultiCopyFail?: Maybe<(copiedText: string) => void>; /** * Event props @@ -168,6 +181,8 @@ export interface DataGridProps extends Sha cellNavigationMode?: Maybe; /** @default true */ enableVirtualization?: Maybe; + /** @default false, set true to enable range selection with copy and paste through clipboard */ + enableRangeSelection?: Maybe /** * Miscellaneous @@ -215,9 +230,13 @@ function DataGrid( onFill, onCopy, onPaste, + onMultiPaste, + onMultiCopy, + // Toggles and modes cellNavigationMode: rawCellNavigationMode, enableVirtualization, + enableRangeSelection, // Miscellaneous components, className, @@ -246,6 +265,7 @@ function DataGrid( const noRowsFallback = components?.noRowsFallback ?? defaultComponents?.noRowsFallback; const cellNavigationMode = rawCellNavigationMode ?? 'NONE'; enableVirtualization ??= true; + enableRangeSelection ??= false; direction ??= 'ltr'; /** @@ -262,6 +282,10 @@ function DataGrid( const [draggedOverRowIdx, setOverRowIdx] = useState(undefined); const [autoResizeColumn, setAutoResizeColumn] = useState | null>(null); + const [selectedRange, setSelectedRange] = useState(initialSelectedRange); + const [copiedRange, setCopiedRange] = useState(null); + const [isMouseRangeSelectionMode, setIsMouseRangeSelectionMode] = useState(false); + /** * refs */ @@ -587,24 +611,51 @@ function DataGrid( } } - switch (event.key) { - case 'Escape': - setCopiedCell(null); - return; - case 'ArrowUp': - case 'ArrowDown': - case 'ArrowLeft': - case 'ArrowRight': - case 'Tab': - case 'Home': - case 'End': - case 'PageUp': - case 'PageDown': - navigate(event); - break; - default: - handleCellInput(event); - break; + if(event.shiftKey){ + switch (event.key) { + case 'ArrowUp': + if (selectedRange.endRowIdx > 0) { + setSelectedRange({...selectedRange, endRowIdx: selectedRange.endRowIdx - 1}) + } + break; + case 'ArrowDown': + if (selectedRange.endRowIdx < rows.length - 1) { + setSelectedRange({...selectedRange, endRowIdx: selectedRange.endRowIdx + 1}) + } + break; + case 'ArrowRight': + if (selectedRange.endColumnIdx < columns.length - 1) { + setSelectedRange({...selectedRange, endColumnIdx: selectedRange.endColumnIdx + 1}) + } + break; + case 'ArrowLeft': + if (selectedRange.endColumnIdx > 0) { + setSelectedRange({...selectedRange, endColumnIdx: selectedRange.endColumnIdx - 1}) + } + break; + default: + break; + } + }else{ + switch ( event.key ) { + case 'Escape': + setCopiedCell ( null ); + return; + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + case 'Tab': + case 'Home': + case 'End': + case 'PageUp': + case 'PageDown': + navigate ( event ); + break; + default: + handleCellInput ( event ); + break; + } } } @@ -638,32 +689,49 @@ function DataGrid( } function handleCopy() { - const { idx, rowIdx } = selectedPosition; - const sourceRow = rawRows[getRawRowIdx(rowIdx)]; - const sourceColumnKey = columns[idx].key; - setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey }); - onCopy?.({ sourceRow, sourceColumnKey }); + if(enableRangeSelection){ + setCopiedRange(selectedRange) + onMultiCopy?.({cellsRange: selectedRange}) + }else{ + const { idx, rowIdx } = selectedPosition; + const sourceRow = rawRows[getRawRowIdx(rowIdx)]; + const sourceColumnKey = columns[idx].key; + setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey }); + onCopy?.({ sourceRow, sourceColumnKey }); + } } function handlePaste() { - if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) { - return; - } + if (enableRangeSelection) { + if (!onMultiPaste || !onRowsChange || copiedRange === null) { + return; + } - const { idx, rowIdx } = selectedPosition; - const targetRow = rawRows[getRawRowIdx(rowIdx)]; + onMultiPaste({ + copiedRange, + targetRange: selectedRange + }) + } else { - const updatedTargetRow = onPaste({ - sourceRow: copiedCell.row, - sourceColumnKey: copiedCell.columnKey, - targetRow, - targetColumnKey: columns[idx].key - }); + if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) { + return; + } - updateRow(rowIdx, updatedTargetRow); + const {idx, rowIdx} = selectedPosition; + const targetRow = rawRows[getRawRowIdx(rowIdx)]; + + const updatedTargetRow = onPaste({ + sourceRow: copiedCell.row, + sourceColumnKey: copiedCell.columnKey, + targetRow, + targetColumnKey: columns[idx].key + }); + + updateRow(rowIdx, updatedTargetRow); + } } - function handleCellInput(event: React.KeyboardEvent) { + function handleCellInput(event: React.KeyboardEvent) { if (!selectedCellIsWithinViewportBounds) return; const row = rows[selectedPosition.rowIdx]; if (isGroupRow(row)) return; @@ -733,6 +801,12 @@ function DataGrid( scrollToCell(position); } else { setSelectedPosition({ ...position, mode: 'SELECT' }); + setSelectedRange({ + startColumnIdx: position.idx, + startRowIdx: position.rowIdx, + endColumnIdx: position.idx, + endRowIdx: position.rowIdx + }) } } @@ -1091,13 +1165,25 @@ function DataGrid( : undefined } selectedCellIdx={selectedRowIdx === rowIdx ? selectedIdx : undefined} + selectedCellsRange={ enableRangeSelection && isValueInBetween(rowIdx, selectedRange?.startRowIdx, selectedRange?.endRowIdx) ? { + startIdx: selectedRange.startColumnIdx, + endIdx: selectedRange.endColumnIdx + } : {startIdx: -1, endIdx: -1}} draggedOverCellIdx={getDraggedOverCellIdx(rowIdx)} setDraggedOverRowIdx={isDragging ? setDraggedOverRowIdx : undefined} lastFrozenColumnIndex={lastFrozenColumnIndex} onRowChange={handleFormatterRowChangeLatest} selectCell={selectViewportCellLatest} + rangeSelectionMode={enableRangeSelection ?? false} selectedCellDragHandle={getDragHandle(rowIdx)} selectedCellEditor={getCellEditor(rowIdx)} + onCellMouseDown={() => setIsMouseRangeSelectionMode(true)} + onCellMouseUp={() => setIsMouseRangeSelectionMode(false)} + onCellMouseEnter={(columnIdx: number) => { + if (isMouseRangeSelectionMode && enableRangeSelection) { + setSelectedRange({...selectedRange, endRowIdx: rowIdx, endColumnIdx: columnIdx}) + } + }} /> ); } @@ -1109,6 +1195,7 @@ function DataGrid( if (selectedPosition.idx > maxColIdx || selectedPosition.rowIdx > maxRowIdx) { setSelectedPosition(initialPosition); setDraggedOverRowIdx(undefined); + setSelectedRange(initialSelectedRange) } let templateRows = `${headerRowHeight}px`; diff --git a/src/Row.tsx b/src/Row.tsx index 32975db33e..b86a99b226 100644 --- a/src/Row.tsx +++ b/src/Row.tsx @@ -7,6 +7,7 @@ import { RowSelectionProvider, useLatestFunc } from './hooks'; import { getColSpan, getRowStyle } from './utils'; import { rowClassname, rowSelectedClassname } from './style'; import type { RowRendererProps } from './types'; +import {isValueInBetween} from "./utils/Helpers"; function Row( { @@ -15,6 +16,7 @@ function Row( gridRowStart, height, selectedCellIdx, + selectedCellsRange, isRowSelected, copiedCellIdx, draggedOverCellIdx, @@ -29,7 +31,11 @@ function Row( setDraggedOverRowIdx, onMouseEnter, onRowChange, + onCellMouseDown, + onCellMouseUp, + onCellMouseEnter, selectCell, + rangeSelectionMode, ...props }: RowRendererProps, ref: React.Ref @@ -44,13 +50,13 @@ function Row( } className = clsx( - rowClassname, - `rdg-row-${rowIdx % 2 === 0 ? 'even' : 'odd'}`, - { - [rowSelectedClassname]: selectedCellIdx === -1 - }, - rowClass?.(row), - className + rowClassname, + `rdg-row-${rowIdx % 2 === 0 ? 'even' : 'odd'}`, + { + [rowSelectedClassname]: selectedCellIdx === -1 + }, + rowClass?.(row), + className ); const cells = []; @@ -63,46 +69,50 @@ function Row( index += colSpan - 1; } - const isCellSelected = selectedCellIdx === idx; + const isCellSelected = selectedCellIdx === idx || (rangeSelectionMode && isValueInBetween(idx, selectedCellsRange?.startIdx, selectedCellsRange?.endIdx)); if (isCellSelected && selectedCellEditor) { cells.push(selectedCellEditor); } else { cells.push( - + onCellMouseDown?.(row, column)} + onMouseUpCapture={() => onCellMouseUp?.(row, column)} + onMouseEnter={() => onCellMouseEnter?.(column.idx)} + rangeSelectionMode={rangeSelectionMode} + /> ); } } return ( - -
- {cells} -
-
+ +
+ {cells} +
+
); } export default memo(forwardRef(Row)) as ( - props: RowRendererProps & RefAttributes + props: RowRendererProps & RefAttributes ) => JSX.Element; diff --git a/src/types.ts b/src/types.ts index 7b1a542769..136b40ac96 100644 --- a/src/types.ts +++ b/src/types.ts @@ -122,6 +122,7 @@ export interface CellRendererProps isCellSelected: boolean; dragHandle: ReactElement> | undefined; onRowChange: (newRow: TRow) => void; + rangeSelectionMode: boolean; } export interface RowRendererProps @@ -130,6 +131,7 @@ export interface RowRendererProps row: TRow; rowIdx: number; selectedCellIdx: number | undefined; + selectedCellsRange: { startIdx: number, endIdx: number }; copiedCellIdx: number | undefined; draggedOverCellIdx: number | undefined; lastFrozenColumnIndex: number; @@ -138,9 +140,13 @@ export interface RowRendererProps height: number; selectedCellEditor: ReactElement> | undefined; selectedCellDragHandle: ReactElement> | undefined; + rangeSelectionMode: boolean; onRowChange: (rowIdx: number, newRow: TRow) => void; onRowClick: Maybe<(row: TRow, column: CalculatedColumn) => void>; onRowDoubleClick: Maybe<(row: TRow, column: CalculatedColumn) => void>; + onCellMouseDown: Maybe<(row: TRow, column: CalculatedColumn) => void>; + onCellMouseUp: Maybe<(row: TRow, column: CalculatedColumn) => void>; + onCellMouseEnter: Maybe<(columnIdx: number) => void>; rowClass: Maybe<(row: TRow) => Maybe>; setDraggedOverRowIdx: ((overRowIdx: number) => void) | undefined; selectCell: ( @@ -179,6 +185,22 @@ export interface PasteEvent { targetRow: TRow; } +export interface MultiPasteEvent { + copiedRange: CellsRange; + targetRange: CellsRange +} + +export interface CellsRange { + startRowIdx: number + startColumnIdx: number + endRowIdx: number + endColumnIdx: number +} + +export interface MultiCopyEvent { + cellsRange: CellsRange +} + export interface GroupRow { readonly childRows: readonly TRow[]; readonly id: string; diff --git a/src/utils/Helpers.ts b/src/utils/Helpers.ts new file mode 100644 index 0000000000..2ede6b8066 --- /dev/null +++ b/src/utils/Helpers.ts @@ -0,0 +1,6 @@ +export const isValueInBetween = (value: number, num1: number, num2: number) => { + if (num1 >= num2) { + return value <= num1 && value >= num2 + } + return value >= num1 && value <= num2 +} diff --git a/website/Nav.tsx b/website/Nav.tsx index e4806b3903..6aaa57f18d 100644 --- a/website/Nav.tsx +++ b/website/Nav.tsx @@ -81,6 +81,9 @@ export default function Nav({ direction, onDirectionChange }: Props) { All Features + + Range Selection + Cell Navigation diff --git a/website/demos/RangeSelection.tsx b/website/demos/RangeSelection.tsx new file mode 100644 index 0000000000..f129c9068c --- /dev/null +++ b/website/demos/RangeSelection.tsx @@ -0,0 +1,404 @@ +import { useState, useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import { css } from '@linaria/core'; +import faker from 'faker'; + +import DataGrid, {SelectColumn, TextEditor, SelectCellFormatter} from '../../src'; +import type { Column, SortColumn } from '../../src'; +import { exportToCsv, exportToXlsx, exportToPdf } from './exportUtils'; +import { textEditorClassname } from '../../src/editors/TextEditor'; +import type { Props } from './types'; +import type {Direction} from '../../src/types'; + +const toolbarClassname = css` + text-align: end; + margin-block-end: 8px; +`; + +const dialogContainerClassname = css` + position: absolute; + inset: 0; + display: flex; + place-items: center; + background: rgba(0, 0, 0, 0.1); + + > dialog { + width: 300px; + > input { + width: 100%; + } + + > menu { + text-align: end; + } + } +`; + +const dateFormatter = new Intl.DateTimeFormat(navigator.language); +const currencyFormatter = new Intl.NumberFormat(navigator.language, { + style: 'currency', + currency: 'eur' +}); + +function TimestampFormatter({ timestamp }: { timestamp: number }) { + return <>{dateFormatter.format(timestamp)}; +} + +function CurrencyFormatter({ value }: { value: number }) { + return <>{currencyFormatter.format(value)}; +} + +interface SummaryRow { + id: string; + totalCount: number; + yesCount: number; +} + +interface Row { + id: number; + title: string; + client: string; + area: string; + country: string; + contact: string; + assignee: string; + progress: number; + startTimestamp: number; + endTimestamp: number; + budget: number; + transaction: string; + account: string; + version: string; + available: boolean; +} + +function getColumns(countries: string[], direction: Direction): readonly Column[] { + return [ + SelectColumn, + { + key: 'id', + name: 'ID', + width: 60, + frozen: true, + resizable: false, + summaryFormatter() { + return Total; + } + }, + { + key: 'title', + name: 'Task', + width: 120, + frozen: true, + editor: TextEditor, + summaryFormatter({ row }) { + return <>{row.totalCount} records; + } + }, + { + key: 'client', + name: 'Client', + width: 220, + editor: TextEditor + }, + { + key: 'area', + name: 'Area', + width: 120, + editor: TextEditor + }, + { + key: 'country', + name: 'Country', + width: 180, + editor: (p) => ( + + ), + editorOptions: { + editOnClick: true + } + }, + { + key: 'contact', + name: 'Contact', + width: 160, + editor: TextEditor + }, + { + key: 'assignee', + name: 'Assignee', + width: 150, + editor: TextEditor + }, + { + key: 'progress', + name: 'Completion', + width: 110, + formatter(props) { + const value = props.row.progress; + return ( + <> + {Math.round(value)}% + + ); + }, + editor({ row, onRowChange, onClose }) { + return createPortal( +
{ + if (event.key === 'Escape') { + onClose(); + } + }} + > + + onRowChange({ ...row, progress: e.target.valueAsNumber })} + /> + + + + + +
, + document.body + ); + }, + editorOptions: { + renderFormatter: true + } + }, + { + key: 'startTimestamp', + name: 'Start date', + width: 100, + formatter(props) { + return ; + } + }, + { + key: 'endTimestamp', + name: 'Deadline', + width: 100, + formatter(props) { + return ; + } + }, + { + key: 'budget', + name: 'Budget', + width: 100, + formatter(props) { + return ; + } + }, + { + key: 'transaction', + name: 'Transaction type' + }, + { + key: 'account', + name: 'Account', + width: 150 + }, + { + key: 'version', + name: 'Version', + editor: TextEditor + }, + { + key: 'available', + name: 'Available', + width: 80, + formatter({ row, onRowChange, isCellSelected }) { + return ( + { + onRowChange({ ...row, available: !row.available }); + }} + isCellSelected={isCellSelected} + /> + ); + }, + summaryFormatter({ row: { yesCount, totalCount } }) { + return <>{`${Math.floor((100 * yesCount) / totalCount)}% ✔️`}; + } + } + ]; +} + +function rowKeyGetter(row: Row) { + return row.id; +} + +function createRows(): readonly Row[] { + const now = Date.now(); + const rows: Row[] = []; + + for (let i = 0; i < 1000; i++) { + rows.push({ + id: i, + title: `Task #${i + 1}`, + client: faker.company.companyName(), + area: faker.name.jobArea(), + country: faker.address.country(), + contact: faker.internet.exampleEmail(), + assignee: faker.name.findName(), + progress: Math.random() * 100, + startTimestamp: now - Math.round(Math.random() * 1e10), + endTimestamp: now + Math.round(Math.random() * 1e10), + budget: 500 + Math.random() * 10500, + transaction: faker.finance.transactionType(), + account: faker.finance.iban(), + version: faker.system.semver(), + available: Math.random() > 0.5 + }); + } + + return rows; +} + +type Comparator = (a: Row, b: Row) => number; +function getComparator(sortColumn: string): Comparator { + switch (sortColumn) { + case 'assignee': + case 'title': + case 'client': + case 'area': + case 'country': + case 'contact': + case 'transaction': + case 'account': + case 'version': + return (a, b) => { + return a[sortColumn].localeCompare(b[sortColumn]); + }; + case 'available': + return (a, b) => { + return a[sortColumn] === b[sortColumn] ? 0 : a[sortColumn] ? 1 : -1; + }; + case 'id': + case 'progress': + case 'startTimestamp': + case 'endTimestamp': + case 'budget': + return (a, b) => { + return a[sortColumn] - b[sortColumn]; + }; + default: + throw new Error(`unsupported sortColumn: "${sortColumn}"`); + } +} + +export default function RangeSelection({ direction }: Props) { + const [rows, setRows] = useState(createRows); + const [sortColumns, setSortColumns] = useState([]); + const [selectedRows, setSelectedRows] = useState>(() => new Set()); + + const countries = useMemo(() => { + return [...new Set(rows.map((r) => r.country))].sort(new Intl.Collator().compare); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const columns = useMemo(() => getColumns(countries, direction), [countries, direction]); + + const summaryRows = useMemo(() => { + const summaryRow: SummaryRow = { + id: 'total_0', + totalCount: rows.length, + yesCount: rows.filter((r) => r.available).length + }; + return [summaryRow]; + }, [rows]); + + const sortedRows = useMemo((): readonly Row[] => { + if (sortColumns.length === 0) return rows; + + return [...rows].sort((a, b) => { + for (const sort of sortColumns) { + const comparator = getComparator(sort.columnKey); + const compResult = comparator(a, b); + if (compResult !== 0) { + return sort.direction === 'ASC' ? compResult : -compResult; + } + } + return 0; + }); + }, [rows, sortColumns]); + + const gridElement = ( + + ); + + return ( + <> +
+ exportToCsv(gridElement, 'CommonFeatures.csv')}> + Export to CSV + + exportToXlsx(gridElement, 'CommonFeatures.xlsx')}> + Export to XSLX + + exportToPdf(gridElement, 'CommonFeatures.pdf')}> + Export to PDF + +
+ {gridElement} + + ); +} + +function ExportButton({ + onExport, + children +}: { + onExport: () => Promise; + children: React.ReactChild; +}) { + const [exporting, setExporting] = useState(false); + return ( + + ); +} diff --git a/website/root.tsx b/website/root.tsx index d6aa134de4..655716e3ac 100644 --- a/website/root.tsx +++ b/website/root.tsx @@ -24,6 +24,7 @@ import RowsReordering from './demos/RowsReordering'; import ScrollToRow from './demos/ScrollToRow'; import TreeView from './demos/TreeView'; import VariableRowHeight from './demos/VariableRowHeight'; +import RangeSelection from "./demos/RangeSelection"; css` @at-root { @@ -112,6 +113,7 @@ function Root() { } /> } /> + } /> From 954cda1afcdc265b1fde6d5e8ef52eeef91d6c76 Mon Sep 17 00:00:00 2001 From: "hady.elzayady" Date: Sat, 26 Mar 2022 11:06:35 +0200 Subject: [PATCH 2/3] handle copy and paste with rane selection --- src/DataGrid.tsx | 2 +- website/demos/AllFeatures.tsx | 1 - website/demos/RangeSelection.tsx | 36 +++++++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 09c92f781c..c42ace7da8 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -575,7 +575,7 @@ function DataGrid( if ( selectedCellIsWithinViewportBounds && - (onPaste != null || onCopy != null) && + (onPaste != null || onCopy != null || onMultiCopy != null || onMultiPaste != null) && isCtrlKeyHeldDown(event) && !isGroupRow(rows[rowIdx]) && selectedPosition.mode === 'SELECT' diff --git a/website/demos/AllFeatures.tsx b/website/demos/AllFeatures.tsx index 967c1b06a1..289863e5f7 100644 --- a/website/demos/AllFeatures.tsx +++ b/website/demos/AllFeatures.tsx @@ -194,7 +194,6 @@ export default function AllFeatures({ direction }: Props) { ) { return targetRow; } - return { ...targetRow, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; } diff --git a/website/demos/RangeSelection.tsx b/website/demos/RangeSelection.tsx index f129c9068c..6455b881a4 100644 --- a/website/demos/RangeSelection.tsx +++ b/website/demos/RangeSelection.tsx @@ -8,7 +8,7 @@ import type { Column, SortColumn } from '../../src'; import { exportToCsv, exportToXlsx, exportToPdf } from './exportUtils'; import { textEditorClassname } from '../../src/editors/TextEditor'; import type { Props } from './types'; -import type {Direction} from '../../src/types'; +import type {Direction, MultiPasteEvent} from '../../src/types'; const toolbarClassname = css` text-align: end; @@ -342,6 +342,39 @@ export default function RangeSelection({ direction }: Props) { }); }, [rows, sortColumns]); + + function getRangeSize(start:number, end:number){ + return Math.abs(start-end) + } + + function handleMultiPaste(pasteEvent: MultiPasteEvent) { + const sourceRange = pasteEvent.copiedRange + const destinationRange = pasteEvent.targetRange + if(getRangeSize(sourceRange.endRowIdx,sourceRange.startRowIdx) !== getRangeSize(destinationRange.endRowIdx, destinationRange.startRowIdx) || + getRangeSize(sourceRange.startColumnIdx, sourceRange.endColumnIdx) !== getRangeSize(destinationRange.startColumnIdx, destinationRange.endColumnIdx) + ){ + return; + } + + const newRows = [...rows] + const sourceStartRow = Math.min(sourceRange.startRowIdx, sourceRange.endRowIdx) + const sourceStartCol = Math.min(sourceRange.startColumnIdx, sourceRange.endColumnIdx) + const destinationStartRow = Math.min(destinationRange.startRowIdx, destinationRange.endRowIdx) + const destinationStartCol = Math.min(destinationRange.startColumnIdx, destinationRange.endColumnIdx) + + debugger + for (let i=0; i<= getRangeSize(sourceRange.startRowIdx, sourceRange.endRowIdx); i++){ + for (let j=0; j <= getRangeSize(sourceRange.startColumnIdx, sourceRange.endColumnIdx); j++){ + const sourceColumnKey = columns[sourceStartCol + j].key + const destinationColumnKey = columns[destinationStartCol + j].key + // @ts-ignore + newRows[destinationStartRow + i][destinationColumnKey] = newRows[sourceStartRow + i][sourceColumnKey] + } + } + + setRows(newRows) + } + const gridElement = ( ); From a73277ceda74f9f241e5d08ea46ce3ff6b58d7c7 Mon Sep 17 00:00:00 2001 From: "hady.elzayady" Date: Sat, 26 Mar 2022 11:14:45 +0200 Subject: [PATCH 3/3] onmousedown for rangeselectio mode only --- src/Cell.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Cell.tsx b/src/Cell.tsx index 813be957c8..53bb27c6bd 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -67,8 +67,10 @@ function Cell({ } function onMouseDown(){ - selectCellWrapper(false); - onRowClick?.(row, column); + if(rangeSelectionMode){ + selectCellWrapper(false); + onRowClick?.(row, column); + } } return (