diff --git a/packages/grid/src/Grid.tsx b/packages/grid/src/Grid.tsx index 8d32ed7a3f..b5568526b0 100644 --- a/packages/grid/src/Grid.tsx +++ b/packages/grid/src/Grid.tsx @@ -1326,6 +1326,7 @@ class Grid extends PureComponent { const { columnCount, rowCount } = model; let ranges = selectedRanges; // If each cell is a single selection, we need to update the selection to map to the newly pasted data + // Check for if ( ranges.every( range => @@ -1347,16 +1348,6 @@ class Grid extends PureComponent { this.setSelectedRanges(ranges); } - if ( - !ranges.every( - range => - GridRange.rowCount([range]) === tableHeight && - GridRange.columnCount([range]) === tableWidth - ) - ) { - throw new PasteError('Copy and paste area are not same size.'); - } - const edits: EditOperation[] = []; ranges.forEach(range => { for (let x = 0; x < tableWidth; x += 1) { diff --git a/packages/grid/src/GridMetricCalculator.ts b/packages/grid/src/GridMetricCalculator.ts index 1cfac1f455..b929819701 100644 --- a/packages/grid/src/GridMetricCalculator.ts +++ b/packages/grid/src/GridMetricCalculator.ts @@ -762,9 +762,13 @@ export class GridMetricCalculator { visibleWidth: number = this.getVisibleWidth(state) ): VisibleIndex { const { model } = state; - const { columnCount } = model; + const { columnCount, floatingRightColumnCount } = model; + + if (columnCount === 0) { + return 0; + } - let lastLeft = columnCount - 1; + let lastLeft = Math.max(0, columnCount - floatingRightColumnCount - 1); if (right != null) { lastLeft = right; } diff --git a/packages/iris-grid/src/IrisGridModel.ts b/packages/iris-grid/src/IrisGridModel.ts index a597c84693..b0027b8e36 100644 --- a/packages/iris-grid/src/IrisGridModel.ts +++ b/packages/iris-grid/src/IrisGridModel.ts @@ -23,6 +23,7 @@ import type { import { Formatter } from '@deephaven/jsapi-utils'; type RowIndex = ModelIndex; +type ColumnName = string; type IrisGridModelEventNames = typeof IrisGridModel.EVENT[keyof typeof IrisGridModel.EVENT]; @@ -30,6 +31,9 @@ type IrisGridModelEventMap = { [E in IrisGridModelEventNames]: Event; }; +const EMPTY_ARRAY: never[] = []; +const EMPTY_SET: Set = new Set(); + /** * Abstract class that extends the GridModel to have more functionality, like filtering and sorting. * For use from IrisGrid. @@ -134,12 +138,12 @@ abstract class IrisGridModel< /** List of column movements defined by the model. Used as initial movements for IrisGrid */ get movedColumns(): MoveOperation[] { - return []; + return EMPTY_ARRAY; } /** List of row movements defined by the model. Used as initial movements for IrisGrid */ get movedRows(): MoveOperation[] { - return []; + return EMPTY_ARRAY; } /** @@ -262,21 +266,28 @@ abstract class IrisGridModel< * @returns Names of columns which should be locked to the front, but not floating */ get frontColumns(): string[] { - return []; + return EMPTY_ARRAY; } /** * @returns Names of columns which should be locked to the back, but not floating */ get backColumns(): string[] { - return []; + return EMPTY_ARRAY; + } + + /** + * @returns Names of key columns + */ + get keyColumnSet(): Set { + return EMPTY_SET; } /** * @returns Names of columns which should be frozen to the front and floating */ get frozenColumns(): string[] { - return []; + return EMPTY_ARRAY; } /** diff --git a/packages/iris-grid/src/IrisGridTableModel.js b/packages/iris-grid/src/IrisGridTableModel.js index ceacd2642d..ebfd30de58 100644 --- a/packages/iris-grid/src/IrisGridTableModel.js +++ b/packages/iris-grid/src/IrisGridTableModel.js @@ -15,6 +15,7 @@ const log = Log.module('IrisGridTableModel'); const SET_VIEWPORT_THROTTLE = 150; const APPLY_VIEWPORT_THROTTLE = 0; +const EMPTY_ARRAY = Object.freeze([]); /** * Model for a grid showing an iris data table @@ -544,8 +545,16 @@ class IrisGridTableModel extends IrisGridModel { return this.getMemoizedColumnMap(this.table.columns); } + getMemoizedKeyColumnSet = memoize( + inputTableKeys => new Set(inputTableKeys ?? EMPTY_ARRAY) + ); + + get keyColumnSet() { + return this.getMemoizedKeyColumnSet(this.inputTable?.keys); + } + getMemoizedFrontColumns = memoize( - layoutHintsFrontColumns => layoutHintsFrontColumns ?? [] + layoutHintsFrontColumns => layoutHintsFrontColumns ?? EMPTY_ARRAY ); get frontColumns() { @@ -553,7 +562,7 @@ class IrisGridTableModel extends IrisGridModel { } getMemoizedBackColumns = memoize( - layoutHintsBackColumns => layoutHintsBackColumns ?? [] + layoutHintsBackColumns => layoutHintsBackColumns ?? EMPTY_ARRAY ); get backColumns() { @@ -562,7 +571,7 @@ class IrisGridTableModel extends IrisGridModel { getMemoizedFrozenColumns = memoize( (layoutHintsFrozenColumns, userFrozenColumns) => - userFrozenColumns ?? layoutHintsFrozenColumns ?? [] + userFrozenColumns ?? layoutHintsFrozenColumns ?? EMPTY_ARRAY ); get frozenColumns() { @@ -581,7 +590,7 @@ class IrisGridTableModel extends IrisGridModel { } get groupedColumns() { - return []; + return EMPTY_ARRAY; } get description() { @@ -654,7 +663,7 @@ class IrisGridTableModel extends IrisGridModel { */ pendingRow(y) { const pendingRow = y - this.floatingTopRowCount - this.table.size; - if (pendingRow >= 0 && pendingRow < this.pendingNewRowCount) { + if (pendingRow >= 0) { return pendingRow; } @@ -1252,7 +1261,7 @@ class IrisGridTableModel extends IrisGridModel { } isKeyColumn(x) { - return x < (this.inputTable?.keyColumns.length ?? 0); + return this.keyColumnSet.has(this.columns[x].name); } isRowMovable() { @@ -1260,18 +1269,40 @@ class IrisGridTableModel extends IrisGridModel { } isEditableRange(range) { - return ( - this.inputTable != null && - GridRange.isBounded(range) && - ((this.isPendingRow(range.startRow) && this.isPendingRow(range.endRow)) || - (range.startColumn >= this.inputTable.keyColumns.length && - range.endColumn >= this.inputTable.keyColumns.length)) && - range.startRow >= this.floatingTopRowCount && - range.startRow < - this.floatingTopRowCount + this.table.size + this.pendingRowCount && - range.endRow < - this.floatingTopRowCount + this.table.size + this.pendingRowCount - ); + // Make sure we have an input table and a valid range + if ( + this.inputTable == null || + range.startRow == null || + range.endRow == null + ) { + return false; + } + + // Check that the edit is in the editable range + // If an input table has keyed columns, the non-key columns are always editable + // If an input table does not have key columns, it is append only and existing rows cannot be editable + // Pending rows are always editable + const isPendingRange = + this.isPendingRow(range.startRow) && this.isPendingRow(range.endRow); + + let isKeyColumnInRange = false; + + // Check if any of the columns in grid range are key columns + const bound = range.endColumn ?? this.table.size; + for (let column = range.startColumn; column <= bound; column += 1) { + if (this.isKeyColumn(column)) { + isKeyColumnInRange = true; + break; + } + } + + if ( + !(isPendingRange || (this.keyColumnSet.size !== 0 && !isKeyColumnInRange)) + ) { + return false; + } + + return true; } isDeletableRange(range) {