diff --git a/src/index.tsx b/src/index.tsx index 9e05703c..8ce76ebe 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import './test/theming-test.scss'; import { config, enablePinnedToBodyConfig, disabledInitialFocusLocationConfig, enableAdditionalContentConfig, enableAdditionalContentWithFlexRowConfig, enableSymetric, enableTopLeftResponsiveSticky, enableTopLeftResponsiveStickyPinnedToBody, - enableBottomRightResponsiveSticky, enableBottomRightResponsiveStickyPinnedToBody, enableSpannedCells, disableVirtualScrolling + enableBottomRightResponsiveSticky, enableBottomRightResponsiveStickyPinnedToBody, enableSpannedCells, disableVirtualScrolling, allowExtendPasteRangeConfig } from './test/testEnvConfig'; let component = ; ExtTestGrid.displayName = 'TestGridPro'; switch (window.location.pathname) { + case '/allowExtendPasteRange': + component = ; + ExtTestGrid.displayName = 'TestGridProWithAllowExtendPasteRange'; + break; case '/enableColumnAndRowSelection': component = { + const { cellMatrix } = state; + let updated = false; + + // Determines the current number of rows and columns + const currentRowCount = cellMatrix.rows.length; + const currentColumnCount = cellMatrix.columns.length; + + // Add new columns if needed + if (endColumn >= currentColumnCount) { + const columnsToAdd = endColumn - currentColumnCount + 1; + const prefix = cellMatrix.columns[currentColumnCount - 1]; + for (let i = 0; i < columnsToAdd; i++) { + cellMatrix.columns.push({ + ...prefix, + columnId: currentColumnCount + i, + idx: currentColumnCount + i, + left: prefix.right, + right: prefix.right + prefix.width || CellMatrix.DEFAULT_COLUMN_WIDTH, + }); + cellMatrix.rows.map((_row) => { + _row.cells.push({ + type: "text", + text: "", + }); + return _row; + }); + } + updated = true; + } + + // Add new rows if needed + if (endRow >= currentRowCount) { + const rowsToAdd = endRow - currentRowCount + 1; + const prefix = cellMatrix.rows[currentRowCount - 1]; + for (let i = 0; i < rowsToAdd; i++) { + cellMatrix.rows.push({ + ...prefix, + rowId: currentRowCount + i, + idx: currentRowCount + i, + top: prefix.bottom, + bottom: prefix.bottom + prefix.height || CellMatrix.DEFAULT_ROW_HEIGHT, + }); + } + updated = true; + } + + // If needed, add a new status column that returns only when the grid is actually updated + return updated ? { ...state, cellMatrix } : state; +}; diff --git a/src/lib/Functions/getDerivedStateFromProps.ts b/src/lib/Functions/getDerivedStateFromProps.ts index fcf9c2a5..d21f02f6 100644 --- a/src/lib/Functions/getDerivedStateFromProps.ts +++ b/src/lib/Functions/getDerivedStateFromProps.ts @@ -31,6 +31,8 @@ export function getDerivedStateFromProps( state = stateDeriverWithProps(state)(updateResponsiveSticky) state = stateDeriverWithProps(state)(disableVirtualScrolling) + + state = stateDeriverWithProps(state)(allowExtendPasteRange) if (hasChanged) { state = stateDeriverWithProps(state)(updateCellMatrix); @@ -174,6 +176,13 @@ function disableVirtualScrolling(props: ReactGridProps, state: State): State { } } +function allowExtendPasteRange(props: ReactGridProps, state: State): State { + return { + ...state, + allowExtendPasteRange: !!props.allowExtendPasteRange + } +} + export function appendHighlights(props: ReactGridProps, state: State): State { const highlights = props.highlights?.filter(highlight => state.cellMatrix.rowIndexLookup[highlight.rowId] !== undefined && state.cellMatrix.columnIndexLookup[highlight.columnId] !== undefined diff --git a/src/lib/Functions/pasteData.ts b/src/lib/Functions/pasteData.ts index 9ddaa599..f79189ff 100644 --- a/src/lib/Functions/pasteData.ts +++ b/src/lib/Functions/pasteData.ts @@ -4,6 +4,7 @@ import { Location } from "../Model/InternalModel"; import { tryAppendChangeHavingGroupId } from "./tryAppendChangeHavingGroupId"; import { getActiveSelectedRange } from "./getActiveSelectedRange"; import { newLocation } from "./newLocation"; +import { expandGridIfNeeded } from "./expandGridIfNeeded"; export function pasteData(state: State, rows: Compatible[][]): State { const activeSelectedRange = getActiveSelectedRange(state); @@ -12,7 +13,7 @@ export function pasteData(state: State, rows: Compatible[][]): State { activeSelectedRange.rows.forEach((row) => activeSelectedRange.columns.forEach((column) => { state = tryAppendChangeHavingGroupId( - state, + state, newLocation(row, column), rows[0][0] ) as State; @@ -35,6 +36,22 @@ export function pasteData(state: State, rows: Compatible[][]): State { lastLocation, cell ) as State; + } else { + if (!state.allowExtendPasteRange) return; + const expandGridIfNeededState = expandGridIfNeeded( + state, + rowIdx, + columnIdx + ); + lastLocation = expandGridIfNeededState.cellMatrix.getLocation( + rowIdx, + columnIdx + ); + state = tryAppendChangeHavingGroupId( + expandGridIfNeededState, + lastLocation, + cell + ) as State; } }) ); @@ -42,13 +59,20 @@ export function pasteData(state: State, rows: Compatible[][]): State { return state; } - const newRange = cellMatrix.getRange(activeSelectedRange.first, lastLocation); + const newRange = cellMatrix.getRange( + activeSelectedRange.first, + lastLocation + ); - if (state?.props?.onSelectionChanging && !state.props.onSelectionChanging([newRange])) { + if ( + state?.props?.onSelectionChanging && + !state.props.onSelectionChanging([newRange]) + ) { return state; } - state?.props?.onSelectionChanged && state.props.onSelectionChanged([newRange]); + state?.props?.onSelectionChanged && + state.props.onSelectionChanged([newRange]); return { ...state, diff --git a/src/lib/Model/PublicModel.ts b/src/lib/Model/PublicModel.ts index 751a7cc2..ea86eb98 100644 --- a/src/lib/Model/PublicModel.ts +++ b/src/lib/Model/PublicModel.ts @@ -64,6 +64,8 @@ export interface ReactGridProps { readonly enableGroupIdRender?: boolean; /** Set `true` to disable virtual scrolling (by default `false`) */ readonly disableVirtualScrolling?: boolean; + /** Set `true` to allow pasting cells outside of the grid (by default `false`) */ + readonly allowExtendPasteRange?: boolean; /** * Horizontal breakpoint in percents (%) of ReactGrid scrollable parent element width. * Disables sticky when the sum of the sizes of sticky panes overflows diff --git a/src/lib/Model/State.ts b/src/lib/Model/State.ts index c8898dd4..b4800b08 100644 --- a/src/lib/Model/State.ts +++ b/src/lib/Model/State.ts @@ -50,6 +50,7 @@ export interface State = (props) => { // eslint-disable-next-line rows[0].cells.find((cell) => cell.type === "text" && cell.text); + const handleChanges = (changes: CellChange[]) => { setRows((prevRows) => { + const currentRows = [...prevRows]; + const currentColumns = [...columns]; changes.forEach((change) => { - const changeRowIdx = prevRows.findIndex( - (el) => el.rowId === change.rowId - ); - const changeColumnIdx = columns.findIndex( + let changeColumnIdx = currentColumns.findIndex( (el) => el.columnId === change.columnId ); + // Extension column + if (changeColumnIdx === -1) { + currentColumns.push({ + columnId: change.columnId, + } as Column); + setColumns(currentColumns); + changeColumnIdx = currentColumns.findIndex( + (el) => el.columnId === change.columnId + ); + } + let changeRowIdx = currentRows.findIndex( + (el) => el.rowId === change.rowId + ); + // Extension line + if (changeRowIdx === -1) { + const _cells = currentColumns.map((_c) => { + return { + type: "text", + text: "", + }; + }); + currentRows.push({ + rowId: change.rowId, + cells: _cells as TestGridCells[], + }); + changeRowIdx = currentRows.findIndex( + (el) => el.rowId === change.rowId + ); + } if (change.type === "flag") { // console.log(change.newCell.text); } @@ -338,9 +367,9 @@ export const TestGrid: React.FC = (props) => { if (change.type === "checkbox") { // console.log(change.previousCell.checked); } - prevRows[changeRowIdx].cells[changeColumnIdx] = change.newCell; + currentRows[changeRowIdx].cells[changeColumnIdx] = change.newCell; }); - return [...prevRows]; + return [...currentRows]; }); }; @@ -544,6 +573,7 @@ export const TestGrid: React.FC = (props) => { onSelectionChanged={handleSelectionChanged} onSelectionChanging={handleSelectionChanging} moveRightOnEnter={config.moveRightOnEnter} + allowExtendPasteRange={config.allowExtendPasteRange} />} {config.additionalContent &&
@@ -636,6 +666,9 @@ export const TestGridOptionsSelect: React.FC = () => { + ); diff --git a/src/test/testEnvConfig.ts b/src/test/testEnvConfig.ts index ff27fed5..6561dbc9 100644 --- a/src/test/testEnvConfig.ts +++ b/src/test/testEnvConfig.ts @@ -18,6 +18,7 @@ export const config: TestConfig = { enableGroupIdRender: true, disableVirtualScrolling: false, moveRightOnEnter: true, + allowExtendPasteRange: false, cellHeight: 25, cellWidth: 150, @@ -84,6 +85,11 @@ export const config: TestConfig = { /** * Optional properties to override main config */ +export const allowExtendPasteRangeConfig: TestConfig = { + ...config, + allowExtendPasteRange: true, +} + export const enablePinnedToBodyConfig: TestConfig = { ...config, pinToBody: true, @@ -210,6 +216,7 @@ export interface TestConfig { enableGroupIdRender: boolean; disableVirtualScrolling: boolean; moveRightOnEnter: boolean; + allowExtendPasteRange: boolean; columns: number; rows: number;