@deephaven/grid 1.27.5-alpha-nodehttp2-config3.276eddc.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/Grid.d.ts +102 -43
  2. package/dist/Grid.d.ts.map +1 -1
  3. package/dist/Grid.js +442 -390
  4. package/dist/Grid.js.map +1 -1
  5. package/dist/GridA11yFallback.d.ts +26 -0
  6. package/dist/GridA11yFallback.d.ts.map +1 -0
  7. package/dist/GridA11yFallback.js +115 -0
  8. package/dist/GridA11yFallback.js.map +1 -0
  9. package/dist/GridA11yUtils.d.ts +89 -0
  10. package/dist/GridA11yUtils.d.ts.map +1 -0
  11. package/dist/GridA11yUtils.js +321 -0
  12. package/dist/GridA11yUtils.js.map +1 -0
  13. package/dist/GridRenderer.d.ts +9 -0
  14. package/dist/GridRenderer.d.ts.map +1 -1
  15. package/dist/GridRenderer.js +187 -39
  16. package/dist/GridRenderer.js.map +1 -1
  17. package/dist/GridRendererTypes.d.ts +3 -4
  18. package/dist/GridRendererTypes.d.ts.map +1 -1
  19. package/dist/GridRendererTypes.js.map +1 -1
  20. package/dist/GridSelectionUtils.d.ts +98 -0
  21. package/dist/GridSelectionUtils.d.ts.map +1 -0
  22. package/dist/GridSelectionUtils.js +180 -0
  23. package/dist/GridSelectionUtils.js.map +1 -0
  24. package/dist/RangedSelection.d.ts +120 -0
  25. package/dist/RangedSelection.d.ts.map +1 -0
  26. package/dist/RangedSelection.js +375 -0
  27. package/dist/RangedSelection.js.map +1 -0
  28. package/dist/Selection.d.ts +221 -0
  29. package/dist/Selection.d.ts.map +1 -0
  30. package/dist/Selection.js +71 -0
  31. package/dist/Selection.js.map +1 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/key-handlers/EditKeyHandler.js +3 -3
  37. package/dist/key-handlers/EditKeyHandler.js.map +1 -1
  38. package/dist/key-handlers/SelectionKeyHandler.d.ts +1 -0
  39. package/dist/key-handlers/SelectionKeyHandler.d.ts.map +1 -1
  40. package/dist/key-handlers/SelectionKeyHandler.js +144 -138
  41. package/dist/key-handlers/SelectionKeyHandler.js.map +1 -1
  42. package/dist/key-handlers/TreeKeyHandler.d.ts.map +1 -1
  43. package/dist/key-handlers/TreeKeyHandler.js +6 -3
  44. package/dist/key-handlers/TreeKeyHandler.js.map +1 -1
  45. package/dist/mouse-handlers/GridRowTreeMouseHandler.d.ts +1 -0
  46. package/dist/mouse-handlers/GridRowTreeMouseHandler.d.ts.map +1 -1
  47. package/dist/mouse-handlers/GridRowTreeMouseHandler.js +7 -0
  48. package/dist/mouse-handlers/GridRowTreeMouseHandler.js.map +1 -1
  49. package/dist/mouse-handlers/GridSelectionMouseHandler.d.ts +0 -9
  50. package/dist/mouse-handlers/GridSelectionMouseHandler.d.ts.map +1 -1
  51. package/dist/mouse-handlers/GridSelectionMouseHandler.js +41 -39
  52. package/dist/mouse-handlers/GridSelectionMouseHandler.js.map +1 -1
  53. package/dist/mouse-handlers/GridTokenMouseHandler.js +4 -1
  54. package/dist/mouse-handlers/GridTokenMouseHandler.js.map +1 -1
  55. package/package.json +3 -3
package/dist/Grid.js CHANGED
@@ -25,6 +25,9 @@ import { isExpandableGridModel } from "./ExpandableGridModel.js";
25
25
  import { assertIsEditableGridModel, isEditableGridModel } from "./EditableGridModel.js";
26
26
  import { assertIsDefined } from "./errors/index.js";
27
27
  import ThemeContext from "./ThemeContext.js";
28
+ import { RangedSelection, assertIsRangedSelection, isRangedSelection, selectionToRanges } from "./RangedSelection.js";
29
+ import GridA11yFallback from "./GridA11yFallback.js";
30
+ /** Selection-related slice of `GridState`. Returned by gesture entry points. */
28
31
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
29
32
  /**
30
33
  * High performance, extendible, themeable grid component.
@@ -89,6 +92,8 @@ class Grid extends PureComponent {
89
92
  _defineProperty(this, "prevMetrics", void 0);
90
93
  _defineProperty(this, "metrics", void 0);
91
94
  _defineProperty(this, "renderState", void 0);
95
+ // Listeners for when the grid draws its canvas.
96
+ _defineProperty(this, "drawListeners", void 0);
92
97
  // Track the cursor that is currently added to the document
93
98
  // Add to document so that when dragging the cursor stays, even if mouse leaves the canvas
94
99
  // Note: on document, not body so that cursor styling can be combined with
@@ -118,7 +123,9 @@ class Grid extends PureComponent {
118
123
  this.handleMouseUp = this.handleMouseUp.bind(this);
119
124
  this.handleResize = this.handleResize.bind(this);
120
125
  this.handleWheel = this.handleWheel.bind(this);
126
+ this.registerDrawListener = this.registerDrawListener.bind(this);
121
127
  this.getSelectedRanges = this.getSelectedRanges.bind(this);
128
+ this.getModel = this.getModel.bind(this);
122
129
  var isStuckToBottom = props.isStuckToBottom,
123
130
  isStuckToRight = props.isStuckToRight,
124
131
  metricCalculator = props.metricCalculator,
@@ -134,7 +141,8 @@ class Grid extends PureComponent {
134
141
  this.animationFrame = null;
135
142
  this.prevMetrics = null;
136
143
  this.metrics = null;
137
- this.renderState = {};
144
+ this.renderState = null;
145
+ this.drawListeners = new Set();
138
146
 
139
147
  // Track the cursor that is currently added to the document
140
148
  // Add to document so that when dragging the cursor stays, even if mouse leaves the canvas
@@ -147,6 +155,10 @@ class Grid extends PureComponent {
147
155
  // specify handler ordering, such that any extensions can insert handlers in between
148
156
  this.keyHandlers = [new EditKeyHandler(400), new PasteKeyHandler(450), new SelectionKeyHandler(500), new TreeKeyHandler(900)];
149
157
  this.mouseHandlers = [new GridRowSeparatorMouseHandler(100), new GridColumnSeparatorMouseHandler(200), new GridRowMoveMouseHandler(300), new GridColumnMoveMouseHandler(400), new EditMouseHandler(450), new GridVerticalScrollBarMouseHandler(500), new GridHorizontalScrollBarMouseHandler(600), new GridScrollBarCornerMouseHandler(700), new GridRowTreeMouseHandler(800), new GridTokenMouseHandler(875), new GridSelectionMouseHandler(900)];
158
+
159
+ // Selections are immutable, so a single instance can back both selection
160
+ // and lastSelection until the first commit produces a fresh committed value.
161
+ var emptySelection = props.createEmptySelection(this.getModel);
150
162
  this.state = {
151
163
  // Top/left visible cell in the grid. Note that it's visible row/column index, not the model index (ie. if columns are re-ordered)
152
164
  top: 0,
@@ -173,18 +185,13 @@ class Grid extends PureComponent {
173
185
  // Move operations the user has performed on this grids columns/rows
174
186
  movedColumns,
175
187
  movedRows,
176
- // Cursor (highlighted cell) location and active selected range
177
- cursorRow: null,
178
- cursorColumn: null,
179
- selectionStartRow: null,
180
- selectionStartColumn: null,
181
- selectionEndRow: null,
182
- selectionEndColumn: null,
183
188
  // Currently selected ranges and previously selected ranges
184
189
  // Store the previously selected ranges to determine if the new selection should
185
190
  // deselect again (if it's the same range)
186
- selectedRanges: EMPTY_ARRAY,
187
- lastSelectedRanges: EMPTY_ARRAY,
191
+ selection: emptySelection,
192
+ lastSelection: emptySelection,
193
+ selectedRanges: [],
194
+ gestureMode: null,
188
195
  // The mouse cursor style to use when hovering over the grid element
189
196
  cursor: null,
190
197
  // { column: number, row: number, selectionRange: [number, number], value: string | null, isQuickEdit?: boolean }
@@ -247,7 +254,8 @@ class Grid extends PureComponent {
247
254
  onMovedRowsChanged = _this$props2.onMovedRowsChanged,
248
255
  onMoveRowComplete = _this$props2.onMoveRowComplete,
249
256
  renderer = _this$props2.renderer,
250
- metricCalculator = _this$props2.metricCalculator;
257
+ metricCalculator = _this$props2.metricCalculator,
258
+ createEmptySelection = _this$props2.createEmptySelection;
251
259
  var prevIsStickyBottom = prevProps.isStickyBottom,
252
260
  prevIsStickyRight = prevProps.isStickyRight,
253
261
  prevPropMovedColumns = prevProps.movedColumns,
@@ -294,6 +302,12 @@ class Grid extends PureComponent {
294
302
  if (metricCalculator !== prevProps.metricCalculator) {
295
303
  this.metricCalculator = metricCalculator !== null && metricCalculator !== void 0 ? metricCalculator : new GridMetricCalculator();
296
304
  }
305
+ if (prevProps.createEmptySelection !== createEmptySelection) {
306
+ var empty = createEmptySelection(this.getModel);
307
+ stateUpdates.selection = empty;
308
+ stateUpdates.lastSelection = empty;
309
+ stateUpdates.selectedRanges = EMPTY_ARRAY;
310
+ }
297
311
  var updatedState = _objectSpread(_objectSpread({}, this.state), stateUpdates);
298
312
  this.updateMetrics(updatedState);
299
313
  this.requestUpdateCanvas();
@@ -425,12 +439,18 @@ class Grid extends PureComponent {
425
439
  if (isExpandableGridModel(model) && model.isRowExpandable(modelRow)) {
426
440
  model.setRowExpanded(modelRow, !model.isRowExpanded(modelRow), expandDescendants);
427
441
  }
428
- this.clearSelectedRanges();
429
- this.commitSelection(); // Need to commit before moving in case we're selecting same row again
430
- this.moveCursorToPosition(0, row);
431
- this.commitSelection();
432
- this.setState({
433
- isStuckToBottom: false
442
+ // Programmatic install so we bypass gesture-driven deselect-on-reclick
443
+ // when the caller re-toggles the currently-selected row.
444
+ var theme = this.props.theme;
445
+ var range = theme.autoSelectRow === true ? new GridRange(null, row, null, row) : GridRange.makeCell(0, row);
446
+ this.setState(state => {
447
+ var newSel = state.selection.clear().withCommittedRanges([range]).withCursor(row, 0).withSelectionEnd(row, 0);
448
+ return {
449
+ selection: newSel,
450
+ lastSelection: newSel,
451
+ selectedRanges: selectionToRanges(newSel),
452
+ isStuckToBottom: false
453
+ };
434
454
  });
435
455
  }
436
456
  getStickyScrollPosition(isStickyBottom, isStickyRight) {
@@ -471,32 +491,36 @@ class Grid extends PureComponent {
471
491
  var model = this.props.model;
472
492
  var columnCount = model.columnCount,
473
493
  rowCount = model.rowCount;
474
- var _this$state2 = this.state,
475
- cursorRow = _this$state2.cursorRow,
476
- cursorColumn = _this$state2.cursorColumn,
477
- selectedRanges = _this$state2.selectedRanges;
478
- this.setState({
479
- selectedRanges: gridRanges,
480
- lastSelectedRanges: selectedRanges
481
- });
482
- if (gridRanges.length > 0) {
483
- var range = GridRange.boundedRange(gridRanges[0], columnCount, rowCount);
484
- var newCursorRow = cursorRow;
485
- var newCursorColumn = cursorColumn;
486
- if (!range.containsCell(cursorColumn, cursorRow)) {
487
- var _range$startCell = range.startCell();
488
- newCursorRow = _range$startCell.row;
489
- newCursorColumn = _range$startCell.column;
494
+ this.setState(state => {
495
+ var _state$selection = state.selection,
496
+ cursorRow = _state$selection.cursorRow,
497
+ cursorColumn = _state$selection.cursorColumn;
498
+ var selectionEndRow = null;
499
+ var selectionEndColumn = null;
500
+ var anchorRow = null;
501
+ var anchorColumn = null;
502
+ if (gridRanges.length > 0) {
503
+ var range = GridRange.boundedRange(gridRanges[0], columnCount, rowCount);
504
+ if (!range.containsCell(cursorColumn, cursorRow)) {
505
+ var _range$startCell = range.startCell();
506
+ cursorRow = _range$startCell.row;
507
+ cursorColumn = _range$startCell.column;
508
+ }
509
+ anchorColumn = range.startColumn;
510
+ anchorRow = range.startRow;
511
+ selectionEndColumn = range.endColumn;
512
+ selectionEndRow = range.endRow;
490
513
  }
491
- this.setState({
492
- selectionStartColumn: range.startColumn,
493
- selectionStartRow: range.startRow,
494
- selectionEndColumn: range.endColumn,
495
- selectionEndRow: range.endRow,
496
- cursorColumn: newCursorColumn,
497
- cursorRow: newCursorRow
498
- });
499
- }
514
+ var selection = state.selection.withCommittedRanges(gridRanges, {
515
+ row: anchorRow,
516
+ column: anchorColumn
517
+ }).withCursor(cursorRow, cursorColumn).withSelectionEnd(selectionEndRow, selectionEndColumn);
518
+ return {
519
+ selection,
520
+ selectedRanges: selectionToRanges(selection),
521
+ lastSelection: state.selection
522
+ };
523
+ });
500
524
  }
501
525
  initContext() {
502
526
  var canvas = this.canvas;
@@ -568,9 +592,9 @@ class Grid extends PureComponent {
568
592
  }
569
593
  updateScrollBounds() {
570
594
  if (!this.metrics) throw new Error('metrics not set');
571
- var _this$state3 = this.state,
572
- left = _this$state3.left,
573
- top = _this$state3.top;
595
+ var _this$state2 = this.state,
596
+ left = _this$state2.left,
597
+ top = _this$state2.top;
574
598
  var _this$metrics4 = this.metrics,
575
599
  lastLeft = _this$metrics4.lastLeft,
576
600
  lastTop = _this$metrics4.lastTop;
@@ -634,11 +658,13 @@ class Grid extends PureComponent {
634
658
  * @param prevState The previous grid state
635
659
  */
636
660
  checkSelectionChange(prevState) {
637
- var oldSelectedRanges = prevState.selectedRanges;
638
- var selectedRanges = this.state.selectedRanges;
639
- if (selectedRanges !== oldSelectedRanges) {
640
- var onSelectionChanged = this.props.onSelectionChanged;
641
- onSelectionChanged(selectedRanges);
661
+ var selection = this.state.selection;
662
+ if (selection !== prevState.selection) {
663
+ var _this$props4 = this.props,
664
+ onSelectionChanged = _this$props4.onSelectionChanged,
665
+ onSelectionChange = _this$props4.onSelectionChange;
666
+ onSelectionChanged(selectionToRanges(selection));
667
+ onSelectionChange(selection);
642
668
  }
643
669
  }
644
670
 
@@ -647,20 +673,19 @@ class Grid extends PureComponent {
647
673
  * @returns True if the selection is valid, false if the selection was invalid and has been reset
648
674
  */
649
675
  validateSelection() {
650
- var model = this.props.model;
651
- var selectedRanges = this.state.selectedRanges;
652
- var columnCount = model.columnCount,
653
- rowCount = model.rowCount;
654
- for (var i = 0; i < selectedRanges.length; i += 1) {
655
- var range = selectedRanges[i];
656
- if (range.endColumn != null && range.endColumn >= columnCount || range.endRow != null && range.endRow >= rowCount) {
657
- // Just clear the selection rather than trying to trim it.
658
- this.setState({
659
- selectedRanges: [],
660
- lastSelectedRanges: []
661
- });
662
- return false;
663
- }
676
+ var createEmptySelection = this.props.createEmptySelection;
677
+ var selection = this.state.selection;
678
+
679
+ // Validate that a RangedSelection is within the bounds of the grid model
680
+ if (isRangedSelection(selection) && !selection.isInBounds()) {
681
+ // Just clear the selection rather than trying to trim it.
682
+ var empty = createEmptySelection(this.getModel);
683
+ this.setState({
684
+ selection: empty,
685
+ lastSelection: empty,
686
+ selectedRanges: EMPTY_ARRAY
687
+ });
688
+ return false;
664
689
  }
665
690
  return true;
666
691
  }
@@ -669,186 +694,304 @@ class Grid extends PureComponent {
669
694
  * Clears all selected ranges
670
695
  */
671
696
  clearSelectedRanges() {
672
- var selectedRanges = this.state.selectedRanges;
673
- this.setState({
674
- selectedRanges: EMPTY_ARRAY,
675
- lastSelectedRanges: selectedRanges
676
- });
697
+ this.setState(state => ({
698
+ selection: state.selection.clear(),
699
+ lastSelection: state.selection,
700
+ selectedRanges: EMPTY_ARRAY
701
+ }));
677
702
  }
678
703
 
679
704
  /** Clears all but the last selected range */
680
705
  trimSelectedRanges() {
681
- var selectedRanges = this.state.selectedRanges;
682
- if (selectedRanges.length > 0) {
683
- this.setState({
684
- selectedRanges: selectedRanges.slice(selectedRanges.length - 1)
685
- });
686
- }
706
+ var selection = this.state.selection;
707
+ var trimmed = selection.trimmed();
708
+ this.setState({
709
+ selection: trimmed,
710
+ selectedRanges: selectionToRanges(trimmed)
711
+ });
687
712
  }
688
713
 
689
- /** Gets the selected ranges */
714
+ /** Sets the selection directly, bypassing mouse/keyboard gesture state. */
715
+ setSelection(selection) {
716
+ // Sync lastSelection so the next gesture compares against the just-installed
717
+ // committed state; otherwise a pending → resolved swap would leave the next
718
+ // ctrl+click's toggle referencing the pending selection's empty keys.
719
+ this.setState({
720
+ selection,
721
+ lastSelection: selection,
722
+ selectedRanges: selectionToRanges(selection)
723
+ });
724
+ }
725
+
726
+ /** Gets the current selection */
727
+ getSelection() {
728
+ var selection = this.state.selection;
729
+ return selection;
730
+ }
731
+
732
+ /** @deprecated Use getSelection() instead */
690
733
  getSelectedRanges() {
691
- var selectedRanges = this.state.selectedRanges;
692
- return selectedRanges;
734
+ var selection = this.state.selection;
735
+ // toRanges() is RangedSelection-only; returns [] for keyed selections
736
+ return selectionToRanges(selection);
693
737
  }
694
738
 
695
739
  /**
696
- * Begin a selection operation at the provided location
697
- * @param column Column where the selection is beginning
698
- * @param row Row where the selection is beginning
740
+ * Queries the current grid model.
741
+ * @returns The current GridModel instance.
699
742
  */
700
- beginSelection(column, row) {
701
- this.setState({
702
- selectionStartColumn: column,
703
- selectionStartRow: row,
704
- selectionEndColumn: column,
705
- selectionEndRow: row,
706
- cursorColumn: column,
707
- cursorRow: row
708
- });
743
+ getModel() {
744
+ var model = this.props.model;
745
+ return model;
746
+ }
747
+
748
+ /**
749
+ * Extend + commit a gesture at `cursor` and return the resulting partial
750
+ * state. Shared by mouse click-and-release and keyboard commits.
751
+ *
752
+ * @param selection Pre-gesture selection to fold against. Cursor landing
753
+ * reads its extended (pre-commit) form so `KeyedSelection`'s overlay is
754
+ * still available.
755
+ * @param cursor Gesture target cell.
756
+ * @param mode Modifier-derived gesture mode.
757
+ * @param opts.moveCursor Snap the cursor to `cursor` (default true).
758
+ * Pass false for keyboard extend / maximize / add so the cursor stays put.
759
+ * @param opts.allowDeselect Whether committing on top of the same
760
+ * single-row / single-cell selection should deselect (mouse affordance,
761
+ * default true). Keyboard callers pass false so arrowing onto an
762
+ * already-selected cell just moves the cursor.
763
+ * @param opts.settle Whether the commit should finalize (deselect check,
764
+ * hole-punch, consolidation). Mouse-down passes false so a drag can grow
765
+ * the last range before mouse-up finalizes.
766
+ */
767
+ applyGestureAt(selection, cursor, mode) {
768
+ var _theme$autoSelectRow, _theme$autoSelectColu;
769
+ var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
770
+ var theme = this.props.theme;
771
+ var _opts$moveCursor = opts.moveCursor,
772
+ moveCursor = _opts$moveCursor === void 0 ? true : _opts$moveCursor,
773
+ _opts$allowDeselect = opts.allowDeselect,
774
+ allowDeselect = _opts$allowDeselect === void 0 ? true : _opts$allowDeselect,
775
+ _opts$settle = opts.settle,
776
+ settle = _opts$settle === void 0 ? true : _opts$settle;
777
+ var lastSelection = selection;
778
+ var extendOpts = {
779
+ mode,
780
+ autoSelectRow: (_theme$autoSelectRow = theme.autoSelectRow) !== null && _theme$autoSelectRow !== void 0 ? _theme$autoSelectRow : false,
781
+ autoSelectColumn: (_theme$autoSelectColu = theme.autoSelectColumn) !== null && _theme$autoSelectColu !== void 0 ? _theme$autoSelectColu : false
782
+ };
783
+ var extended = selection.withGestureExtend(cursor, extendOpts);
784
+ // `replace` snaps the cursor to the gesture; extend/maximize/add keep it put.
785
+ var cursorTarget = moveCursor ? cursor : {
786
+ row: selection.cursorRow,
787
+ column: selection.cursorColumn
788
+ };
789
+ var settled = extended.commitGesture(lastSelection, {
790
+ autoSelectRow: extendOpts.autoSelectRow,
791
+ cursor: cursorTarget,
792
+ allowDeselect,
793
+ settle
794
+ }).withSelectionEnd(cursor.row, cursor.column);
795
+ return {
796
+ selection: settled,
797
+ lastSelection,
798
+ selectedRanges: selectionToRanges(settled)
799
+ };
800
+ }
801
+
802
+ /**
803
+ * Start a mouse-driven selection gesture at `cursor` with the given
804
+ * modifier-derived `mode`. Extends the selection per `mode` semantics
805
+ * and commits immediately so a click without drag settles the state.
806
+ */
807
+ handleMouseSelectStart(cursor, mode) {
808
+ this.setState(state => _objectSpread(_objectSpread({}, this.applyGestureAt(state.selection, cursor, mode, {
809
+ settle: false
810
+ })), {}, {
811
+ gestureMode: mode
812
+ }));
709
813
  }
710
814
 
711
815
  /**
712
- * Moves the selection to the cell specified
713
- * @param column The column index to move the cursor to
714
- * @param row The row index to move the cursor to
715
- * @param extendSelection Whether to extend the current selection (eg. holding Shift)
716
- * @param maximizePreviousRange When true, maximize/add to the previous range only, ignoring where the selection was started.
816
+ * Extend the current mouse selection gesture to `cursor`. Transient
817
+ * overlay only; the settled commit runs on `handleMouseSelectEnd`.
717
818
  */
718
- moveSelection(column, row) {
719
- var extendSelection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
720
- var maximizePreviousRange = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
819
+ handleMouseSelectDrag(cursor) {
820
+ var theme = this.props.theme;
721
821
  this.setState(state => {
722
- var selectedRanges = state.selectedRanges,
723
- selectionStartRow = state.selectionStartRow,
724
- selectionStartColumn = state.selectionStartColumn;
725
- var theme = this.props.theme;
726
- var autoSelectRow = theme.autoSelectRow,
727
- autoSelectColumn = theme.autoSelectColumn;
728
- if (extendSelection && selectedRanges.length > 0) {
729
- var lastSelectedRange = selectedRanges[selectedRanges.length - 1];
730
- var left = null;
731
- var top = null;
732
- var right = null;
733
- var bottom = null;
734
- if (maximizePreviousRange) {
735
- var _lastSelectedRange$st, _lastSelectedRange$st2, _lastSelectedRange$en, _lastSelectedRange$en2;
736
- left = autoSelectRow !== undefined && autoSelectRow ? null : Math.min(column !== null && column !== void 0 ? column : 0, (_lastSelectedRange$st = lastSelectedRange.startColumn) !== null && _lastSelectedRange$st !== void 0 ? _lastSelectedRange$st : 0);
737
- top = autoSelectColumn !== undefined && autoSelectColumn ? null : Math.min(row !== null && row !== void 0 ? row : 0, (_lastSelectedRange$st2 = lastSelectedRange.startRow) !== null && _lastSelectedRange$st2 !== void 0 ? _lastSelectedRange$st2 : 0);
738
- right = autoSelectRow !== undefined && autoSelectRow ? null : Math.max(column !== null && column !== void 0 ? column : 0, (_lastSelectedRange$en = lastSelectedRange.endColumn) !== null && _lastSelectedRange$en !== void 0 ? _lastSelectedRange$en : 0);
739
- bottom = autoSelectColumn !== undefined && autoSelectColumn ? null : Math.max(row !== null && row !== void 0 ? row : 0, (_lastSelectedRange$en2 = lastSelectedRange.endRow) !== null && _lastSelectedRange$en2 !== void 0 ? _lastSelectedRange$en2 : 0);
740
- } else {
741
- left = lastSelectedRange.startColumn;
742
- top = lastSelectedRange.startRow;
743
- if (selectionStartColumn != null || selectionStartRow != null) {
744
- if (autoSelectRow === undefined || !autoSelectRow) {
745
- left = selectionStartColumn;
746
- }
747
- if (autoSelectColumn === undefined || !autoSelectColumn) {
748
- top = selectionStartRow;
749
- }
750
- }
751
- right = autoSelectRow !== undefined && autoSelectRow ? null : column;
752
- bottom = autoSelectColumn !== undefined && autoSelectColumn ? null : row;
753
- }
754
- var selectedRange = GridRange.makeNormalized(left, top, right, bottom);
755
- if (lastSelectedRange.equals(selectedRange)) {
756
- return null;
757
- }
758
- var _newRanges = [...selectedRanges];
759
- _newRanges[_newRanges.length - 1] = selectedRange;
760
- return {
761
- selectedRanges: _newRanges,
762
- selectionEndColumn: column,
763
- selectionEndRow: row
764
- };
765
- }
766
- var newRanges = [...selectedRanges];
767
- var selectedColumn = autoSelectRow !== undefined && autoSelectRow ? null : column;
768
- var selectedRow = autoSelectColumn !== undefined && autoSelectColumn ? null : row;
769
- newRanges.push(GridRange.makeNormalized(selectedColumn, selectedRow, selectedColumn, selectedRow));
822
+ var _theme$autoSelectRow2, _theme$autoSelectColu2;
823
+ // Additive gestures (ctrl+click, ctrl+shift+click) grow the last range
824
+ // via `maximize` so the prior committed selection survives the drag.
825
+ // Replace and shift-extend continue as `extend`, trimming to the last
826
+ // range.
827
+ var isAdditive = state.gestureMode === 'add' || state.gestureMode === 'maximize';
828
+ var opts = {
829
+ mode: isAdditive ? 'maximize' : 'extend',
830
+ autoSelectRow: (_theme$autoSelectRow2 = theme.autoSelectRow) !== null && _theme$autoSelectRow2 !== void 0 ? _theme$autoSelectRow2 : false,
831
+ autoSelectColumn: (_theme$autoSelectColu2 = theme.autoSelectColumn) !== null && _theme$autoSelectColu2 !== void 0 ? _theme$autoSelectColu2 : false
832
+ };
833
+ var extended = state.selection.withGestureExtend(cursor, opts).withSelectionEnd(cursor.row, cursor.column);
770
834
  return {
771
- selectedRanges: newRanges,
772
- selectionEndColumn: column,
773
- selectionEndRow: row
835
+ selection: extended,
836
+ selectedRanges: selectionToRanges(extended)
774
837
  };
775
838
  });
839
+ this.moveViewToCell(cursor.column, cursor.row);
776
840
  }
777
841
 
778
- /**
779
- * Commits the last selected range to the selected ranges.
780
- * First checks if the last range is completely contained within another range, and if it
781
- * is then it blows those ranges apart.
782
- * Then it consolidates all the selected ranges, reducing them.
783
- */
784
- commitSelection() {
842
+ /** Settle the current mouse selection gesture. Called on mouse-up. */
843
+ handleMouseSelectEnd() {
844
+ var theme = this.props.theme;
785
845
  this.setState(state => {
786
- var theme = this.props.theme;
787
- var autoSelectRow = theme.autoSelectRow;
788
- var selectedRanges = state.selectedRanges,
789
- lastSelectedRanges = state.lastSelectedRanges,
790
- cursorRow = state.cursorRow,
791
- cursorColumn = state.cursorColumn;
792
- if (selectedRanges.length === 1 && (autoSelectRow !== undefined && autoSelectRow ? GridRange.rowCount(selectedRanges) === 1 : GridRange.cellCount(selectedRanges) === 1) && GridRange.rangeArraysEqual(selectedRanges, lastSelectedRanges)) {
793
- // If it's the exact same single selection, then deselect.
794
- // For if we click on one cell multiple times.
795
- return {
796
- selectedRanges: EMPTY_ARRAY,
797
- lastSelectedRanges: EMPTY_ARRAY,
798
- cursorColumn: null,
799
- cursorRow: null
800
- };
801
- }
802
- var newSelectedRanges = selectedRanges.slice();
803
- if (newSelectedRanges.length > 1) {
804
- // Check if the latest selection is entirely within a previously selected range
805
- // If that's the case, then deselect that section instead
806
- var lastRange = newSelectedRanges[newSelectedRanges.length - 1];
807
- for (var i = 0; i < newSelectedRanges.length - 1; i += 1) {
808
- var selectedRange = newSelectedRanges[i];
809
- if (selectedRange.contains(lastRange)) {
810
- // We found a match, now remove the two matching ranges, and add back
811
- // the remainder of the two
812
- var remainder = selectedRange.subtract(lastRange);
813
- newSelectedRanges.pop();
814
- newSelectedRanges.splice(i, 1);
815
- newSelectedRanges = newSelectedRanges.concat(remainder);
816
- break;
817
- }
818
- }
819
- newSelectedRanges = GridRange.consolidate(newSelectedRanges);
820
- }
821
- var newCursorColumn = cursorColumn;
822
- var newCursorRow = cursorRow;
823
- if (!GridRange.containsCell(newSelectedRanges, cursorColumn, cursorRow)) {
824
- var model = this.props.model;
825
- var columnCount = model.columnCount,
826
- rowCount = model.rowCount;
827
- var nextCursor = GridRange.nextCell(GridRange.boundedRanges(selectedRanges, columnCount, rowCount));
828
- if (nextCursor != null) {
829
- newCursorColumn = nextCursor.column;
830
- newCursorRow = nextCursor.row;
831
- } else {
832
- newCursorColumn = null;
833
- newCursorRow = null;
846
+ var _theme$autoSelectRow3;
847
+ var selection = state.selection,
848
+ lastSelection = state.lastSelection;
849
+ var settled = selection.commitGesture(lastSelection, {
850
+ autoSelectRow: (_theme$autoSelectRow3 = theme.autoSelectRow) !== null && _theme$autoSelectRow3 !== void 0 ? _theme$autoSelectRow3 : false,
851
+ // Mouse-up commit preserves the current cursor position.
852
+ cursor: {
853
+ row: selection.cursorRow,
854
+ column: selection.cursorColumn
834
855
  }
835
- }
836
- if (newSelectedRanges.length === 0) {
837
- newCursorColumn = null;
838
- newCursorRow = null;
839
- }
840
- var selectionChanged = newSelectedRanges.length !== selectedRanges.length || newSelectedRanges.some((range, index) => !range.equals(selectedRanges[index]));
856
+ });
857
+ if (settled === selection) return null;
841
858
  return {
842
- cursorRow: newCursorRow,
843
- cursorColumn: newCursorColumn,
844
- // The onSelectionChanged callback has already been called with the selectedRanges at this point.
845
- // If the selection is not changed (e.g., the user is adding via ctrl+click and not removing),
846
- // there is no need to change and trigger the callback again.
847
- selectedRanges: selectionChanged ? newSelectedRanges : selectedRanges,
848
- lastSelectedRanges: selectedRanges
859
+ selection: settled,
860
+ lastSelection: settled,
861
+ selectedRanges: selectionToRanges(settled)
862
+ };
863
+ });
864
+ // Always clear the in-flight gesture mode on mouse-up. Kept as a
865
+ // separate setState so the settled-update above stays in a single Pick
866
+ // shape that React's typing narrows cleanly.
867
+ this.setState(_ref5 => {
868
+ var gestureMode = _ref5.gestureMode;
869
+ return gestureMode == null ? null : {
870
+ gestureMode: null
849
871
  };
850
872
  });
851
873
  }
874
+
875
+ /**
876
+ * Apply a keyboard-driven selection gesture at `cursor` with `mode`, then
877
+ * optionally scroll the viewport to bring the cursor into view. Shared
878
+ * entry point for arrow keys, Home/End, and page-key movements.
879
+ *
880
+ * Only `replace` moves the cursor to `cursor`; extend/maximize/add
881
+ * preserve the existing cursor and update `selectionEnd` only. This
882
+ * matches long-standing Shift+Arrow semantics where the cursor stays put
883
+ * as the selection grows.
884
+ */
885
+ handleKeySelectAt(cursor, mode) {
886
+ var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
887
+ var _opts$keepCursorInVie = opts.keepCursorInView,
888
+ keepCursorInView = _opts$keepCursorInVie === void 0 ? true : _opts$keepCursorInVie;
889
+ var moveCursor = mode === 'replace';
890
+ this.setState(state => this.applyGestureAt(state.selection, cursor, mode, {
891
+ moveCursor,
892
+ allowDeselect: false
893
+ }));
894
+ if (keepCursorInView && cursor.column != null && cursor.row != null) {
895
+ this.moveViewToCell(cursor.column, cursor.row);
896
+ }
897
+ }
898
+
899
+ /**
900
+ * Listen for the grid finishing a draw of its canvas. Called immediately with
901
+ * the most recent draw if the grid has already drawn.
902
+ * @param listener Called with the state that was drawn
903
+ * @returns A function to stop listening
904
+ */
905
+ registerDrawListener(listener) {
906
+ this.drawListeners.add(listener);
907
+ if (this.renderState !== null) {
908
+ listener(this.renderState);
909
+ }
910
+ return () => {
911
+ this.drawListeners.delete(listener);
912
+ };
913
+ }
914
+
915
+ /**
916
+ * Advance the cursor in `direction` through the current selection. Used
917
+ * by Tab/Enter — cycles within the selected ranges when there are
918
+ * multiple, wraps at grid edges when there is only a single cell.
919
+ * When the resulting cursor falls outside the current selection (e.g.
920
+ * from an initial empty state), installs a fresh single-cell selection.
921
+ */
922
+ handleKeyAdvanceCursor(direction) {
923
+ var selection = this.state.selection;
924
+ var next = selection.getNextCursorInDirection({
925
+ column: selection.cursorColumn,
926
+ row: selection.cursorRow
927
+ }, direction);
928
+ if (next == null) return;
929
+ var column = next.column,
930
+ row = next.row;
931
+ if (column == null || row == null) return;
932
+ if (selection.isCellSelected(column, row)) {
933
+ this.setState(state => ({
934
+ selection: state.selection.withCursor(row, column)
935
+ }));
936
+ } else {
937
+ this.setState(state => this.applyGestureAt(state.selection, {
938
+ column,
939
+ row
940
+ }, 'replace', {
941
+ allowDeselect: false
942
+ }));
943
+ }
944
+ this.moveViewToCell(column, row);
945
+ }
946
+
947
+ /**
948
+ * Page-move gesture. `direction` is +1 (page-down) or -1 (page-up). Reads
949
+ * viewport metrics to compute the target cell, applies the gesture without
950
+ * auto-scrolling, then pins the viewport top so the cursor lands where it
951
+ * was on screen before.
952
+ */
953
+ handleKeyPage(mode, direction) {
954
+ var selection = this.state.selection;
955
+ var cursorColumn = selection.cursorColumn,
956
+ selectionEndRow = selection.selectionEndRow;
957
+ if (selectionEndRow == null) return;
958
+ var row = selectionEndRow;
959
+ var metricState = this.getMetricState();
960
+ var _this$metricCalculato = this.metricCalculator.getMetrics(metricState),
961
+ bottomVisible = _this$metricCalculato.bottomVisible,
962
+ topVisible = _this$metricCalculato.topVisible,
963
+ hasHorizontalBar = _this$metricCalculato.hasHorizontalBar,
964
+ rowCount = _this$metricCalculato.rowCount,
965
+ lastTop = _this$metricCalculato.lastTop;
966
+ var pageSize = bottomVisible - topVisible + (hasHorizontalBar ? 0 : 1);
967
+ var target = clamp(row + direction * pageSize, 0, rowCount - 1);
968
+ var viewportPosition = clamp(target - (row - topVisible), 0, lastTop);
969
+ this.handleKeySelectAt({
970
+ column: cursorColumn,
971
+ row: target
972
+ }, mode, {
973
+ keepCursorInView: false
974
+ });
975
+ this.setViewState({
976
+ top: viewportPosition
977
+ });
978
+ }
979
+ handleKeyPageUp(mode) {
980
+ this.handleKeyPage(mode, -1);
981
+ }
982
+ handleKeyPageDown(mode) {
983
+ this.handleKeyPage(mode, 1);
984
+ }
985
+
986
+ /**
987
+ * Move the cursor to a cell without touching the selection.
988
+ */
989
+ handleKeyMoveCursor(column, row) {
990
+ this.setState(state => ({
991
+ selection: state.selection.withCursor(row, column).withSelectionEnd(row, column)
992
+ }));
993
+ this.moveViewToCell(column, row);
994
+ }
852
995
  setFocusRow(focusedRow) {
853
996
  var _userRowHeights$get;
854
997
  if (!this.metrics || !this.prevMetrics) {
@@ -864,123 +1007,31 @@ class Grid extends PureComponent {
864
1007
  var halfViewportHeight = Math.round(tableHeight / 2) + ((_userRowHeights$get = userRowHeights.get(focusedRow)) !== null && _userRowHeights$get !== void 0 ? _userRowHeights$get : rowHeight);
865
1008
  var metricState = this.getMetricState();
866
1009
  var newTop = this.metricCalculator.getLastTop(metricState, focusedRow + 1, halfViewportHeight);
867
- this.setState({
868
- top: Math.min(lastTop, newTop),
869
- selectedRanges: [new GridRange(null, focusedRow, null, focusedRow)],
870
- isStuckToBottom: false
1010
+ this.setState(state => {
1011
+ var cursorColumn = state.selection.cursorColumn;
1012
+ var newSel = state.selection.withCommittedRanges([new GridRange(null, focusedRow, null, focusedRow)]).withCursor(focusedRow, cursorColumn).withSelectionEnd(focusedRow, cursorColumn);
1013
+ return {
1014
+ top: Math.min(lastTop, newTop),
1015
+ selection: newSel,
1016
+ lastSelection: newSel,
1017
+ selectedRanges: selectionToRanges(newSel),
1018
+ isStuckToBottom: false
1019
+ };
871
1020
  });
872
- var cursorColumn = this.state.cursorColumn;
873
- this.moveCursorToPosition(cursorColumn, focusedRow, false, false);
874
1021
  }
875
1022
 
876
1023
  /**
877
1024
  * Set the selection to the entire grid
878
1025
  */
879
1026
  selectAll() {
880
- var _this$props4 = this.props,
881
- model = _this$props4.model,
882
- theme = _this$props4.theme;
883
- var autoSelectRow = theme.autoSelectRow,
884
- autoSelectColumn = theme.autoSelectColumn;
885
- var top = autoSelectColumn !== undefined && autoSelectColumn ? null : 0;
886
- var bottom = autoSelectColumn !== undefined && autoSelectColumn ? null : model.rowCount - 1;
887
- var left = autoSelectRow !== undefined && autoSelectRow ? null : 0;
888
- var right = autoSelectRow !== undefined && autoSelectRow ? null : model.columnCount - 1;
889
- this.setSelectedRanges([new GridRange(left, top, right, bottom)]);
890
- }
891
-
892
- /**
893
- * Move the cursor in relation to the current cursor position
894
- * @param deltaColumn Number of columns to move the cursor
895
- * @param deltaRow Number of rows to move the cursor
896
- * @param extendSelection True if the current selection should be extended, false to start a new selection
897
- */
898
- moveCursor(deltaColumn, deltaRow, extendSelection) {
899
- var _this$state4 = this.state,
900
- cursorRow = _this$state4.cursorRow,
901
- cursorColumn = _this$state4.cursorColumn,
902
- selectionEndColumn = _this$state4.selectionEndColumn,
903
- selectionEndRow = _this$state4.selectionEndRow;
904
- var column = extendSelection ? selectionEndColumn : cursorColumn;
905
- var row = extendSelection ? selectionEndRow : cursorRow;
906
- if (row === null || column === null) {
907
- var _this$state5 = this.state,
908
- left = _this$state5.left,
909
- top = _this$state5.top;
910
- this.moveCursorToPosition(left, top, extendSelection);
911
- } else {
912
- var model = this.props.model;
913
- var columnCount = model.columnCount,
914
- rowCount = model.rowCount;
915
- var _left = clamp(column + deltaColumn, 0, columnCount - 1);
916
- var _top = clamp(row + deltaRow, 0, rowCount - 1);
917
- this.moveCursorToPosition(_left, _top, extendSelection);
918
- }
919
- }
920
-
921
- /**
922
- * Move the cursor in the provided selection direction
923
- * @param direction The direction to move the cursor in
924
- */
925
- moveCursorInDirection() {
926
- var direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : GridRange.SELECTION_DIRECTION.DOWN;
927
- var model = this.props.model;
928
- var columnCount = model.columnCount,
929
- rowCount = model.rowCount;
930
- var _this$state6 = this.state,
931
- cursorRow = _this$state6.cursorRow,
932
- cursorColumn = _this$state6.cursorColumn,
933
- selectedRanges = _this$state6.selectedRanges;
934
- var ranges = selectedRanges.length > 0 ? selectedRanges : [GridRange.makeCell(cursorColumn, cursorRow)];
935
- var nextCursor = null;
936
- if (ranges.length === 1 && GridRange.cellCount(ranges) === 1) {
937
- var _gridRange$nextCell;
938
- // If we only have one cell selected, we want to update the cursor and we want to update the selected cells
939
- var gridRange = new GridRange(0, 0, columnCount - 1, rowCount - 1);
940
- nextCursor = (_gridRange$nextCell = gridRange.nextCell(cursorColumn, cursorRow, direction)) !== null && _gridRange$nextCell !== void 0 ? _gridRange$nextCell : gridRange.startCell(direction);
941
- } else {
942
- nextCursor = GridRange.nextCell(GridRange.boundedRanges(ranges, columnCount, rowCount), cursorColumn, cursorRow, direction);
943
- }
944
- if (nextCursor != null) {
945
- var _nextCursor = nextCursor,
946
- column = _nextCursor.column,
947
- row = _nextCursor.row;
948
- this.setState({
949
- cursorColumn: column,
950
- cursorRow: row
951
- });
952
- if (!GridRange.containsCell(selectedRanges, column, row)) {
953
- this.setState({
954
- selectedRanges: [GridRange.makeCell(column, row)],
955
- selectionStartColumn: column,
956
- selectionStartRow: row,
957
- selectionEndColumn: column,
958
- selectionEndRow: row
959
- });
960
- }
961
- this.moveViewToCell(nextCursor.column, nextCursor.row);
962
- }
963
- }
964
-
965
- /**
966
- * Move a cursor to the specified position in the grid.
967
- * @param column The column index to move the cursor to
968
- * @param row The row index to move the cursor to
969
- * @param extendSelection Whether to extend the current selection (eg. holding Shift)
970
- * @param keepCursorInView Whether to move the viewport so that the cursor is in view
971
- * @param maximizePreviousRange With this and `extendSelection` true, it will maximize/add to the previous range only, ignoring where the selection was started
972
- */
973
- moveCursorToPosition(column, row) {
974
- var extendSelection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
975
- var keepCursorInView = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
976
- var maximizePreviousRange = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
977
- if (!extendSelection) {
978
- this.beginSelection(column, row);
979
- }
980
- this.moveSelection(column, row, extendSelection, maximizePreviousRange);
981
- if (keepCursorInView) {
982
- this.moveViewToCell(column, row);
983
- }
1027
+ this.setState(state => {
1028
+ var newSelection = state.selection.selectAll();
1029
+ return {
1030
+ selection: newSelection,
1031
+ lastSelection: newSelection,
1032
+ selectedRanges: selectionToRanges(newSelection)
1033
+ };
1034
+ });
984
1035
  }
985
1036
 
986
1037
  /**
@@ -998,11 +1049,11 @@ class Grid extends PureComponent {
998
1049
  topVisible = _this$metrics7.topVisible,
999
1050
  leftVisible = _this$metrics7.leftVisible;
1000
1051
  var metricState = this.getMetricState(this.state);
1001
- var _this$state7 = this.state,
1002
- top = _this$state7.top,
1003
- left = _this$state7.left,
1004
- topOffset = _this$state7.topOffset,
1005
- leftOffset = _this$state7.leftOffset;
1052
+ var _this$state3 = this.state,
1053
+ top = _this$state3.top,
1054
+ left = _this$state3.left,
1055
+ topOffset = _this$state3.topOffset,
1056
+ leftOffset = _this$state3.leftOffset;
1006
1057
  if (row != null && !GridUtils.isFloatingRow(row, this.metrics)) {
1007
1058
  if (row < topVisible) {
1008
1059
  top = metricCalculator.getTopForTopVisible(metricState, row);
@@ -1088,11 +1139,10 @@ class Grid extends PureComponent {
1088
1139
  value: value !== undefined ? value : model.editValueForCell(modelColumn, modelRow),
1089
1140
  isQuickEdit
1090
1141
  };
1091
- this.setState({
1142
+ this.setState(state => ({
1092
1143
  editingCell: cell,
1093
- cursorColumn: column,
1094
- cursorRow: row
1095
- });
1144
+ selection: state.selection.withCursor(row, column)
1145
+ }));
1096
1146
  this.moveViewToCell(column, row);
1097
1147
  }
1098
1148
 
@@ -1120,16 +1170,19 @@ class Grid extends PureComponent {
1120
1170
  var _this = this;
1121
1171
  return _asyncToGenerator(function* () {
1122
1172
  var model = _this.props.model;
1123
- var _this$state8 = _this.state,
1124
- movedColumns = _this$state8.movedColumns,
1125
- movedRows = _this$state8.movedRows,
1126
- selectedRanges = _this$state8.selectedRanges;
1173
+ var _this$state4 = _this.state,
1174
+ movedColumns = _this$state4.movedColumns,
1175
+ movedRows = _this$state4.movedRows,
1176
+ selection = _this$state4.selection;
1177
+ // pasteValue is only reachable for editable (non-keyed) tables
1178
+ assertIsRangedSelection(selection);
1179
+ var selectedRanges = selection.toRanges();
1127
1180
  try {
1128
1181
  assertIsEditableGridModel(model);
1129
1182
  if (!model.isEditable || !selectedRanges.every(range => model.isEditableRange(range))) {
1130
1183
  throw new PasteError("Can't paste in to read-only area.");
1131
1184
  }
1132
- if (selectedRanges.length <= 0) {
1185
+ if (selection.isEmpty()) {
1133
1186
  throw new PasteError('Select an area to paste to.');
1134
1187
  }
1135
1188
  if (typeof value === 'string') {
@@ -1223,9 +1276,9 @@ class Grid extends PureComponent {
1223
1276
  var _this$props7 = this.props,
1224
1277
  model = _this$props7.model,
1225
1278
  onError = _this$props7.onError;
1226
- var _this$state9 = this.state,
1227
- movedColumns = _this$state9.movedColumns,
1228
- movedRows = _this$state9.movedRows;
1279
+ var _this$state5 = this.state,
1280
+ movedColumns = _this$state5.movedColumns,
1281
+ movedRows = _this$state5.movedRows;
1229
1282
  var modelRanges = GridUtils.getModelRanges(ranges, movedColumns, movedRows);
1230
1283
  if (isEditableGridModel(model)) {
1231
1284
  model.setValueForRanges(modelRanges, value).catch(err => {
@@ -1241,17 +1294,8 @@ class Grid extends PureComponent {
1241
1294
  * @returns True if the cell is in the current selection, false otherwise
1242
1295
  */
1243
1296
  isSelected(row, column) {
1244
- var selectedRanges = this.state.selectedRanges;
1245
- for (var i = 0; i < selectedRanges.length; i += 1) {
1246
- var _selectedRange$endRow, _selectedRange$endCol;
1247
- var selectedRange = selectedRanges[i];
1248
- var rowSelected = selectedRange.startRow === null || selectedRange.startRow <= row && row <= ((_selectedRange$endRow = selectedRange.endRow) !== null && _selectedRange$endRow !== void 0 ? _selectedRange$endRow : 0);
1249
- var columnSelected = selectedRange.startColumn === null || selectedRange.startColumn <= column && column <= ((_selectedRange$endCol = selectedRange.endColumn) !== null && _selectedRange$endCol !== void 0 ? _selectedRange$endCol : 0);
1250
- if (rowSelected && columnSelected) {
1251
- return true;
1252
- }
1253
- }
1254
- return false;
1297
+ var selection = this.state.selection;
1298
+ return selection.isCellSelected(column, row);
1255
1299
  }
1256
1300
  addDocumentCursor() {
1257
1301
  var cursor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
@@ -1303,9 +1347,13 @@ class Grid extends PureComponent {
1303
1347
  var renderer = this.renderer,
1304
1348
  context = this.canvasContext,
1305
1349
  renderState = this.renderState;
1350
+ assertNotNull(renderState);
1306
1351
  context.save();
1307
1352
  renderer.drawCanvas(renderState);
1308
1353
  context.restore();
1354
+ this.drawListeners.forEach(listener => {
1355
+ listener(renderState);
1356
+ });
1309
1357
  }
1310
1358
 
1311
1359
  /**
@@ -1505,9 +1553,9 @@ class Grid extends PureComponent {
1505
1553
  this.forceUpdate();
1506
1554
  }
1507
1555
  forceUpdate(callback) {
1508
- this.setState(_ref5 => {
1509
- var _ref5$updateRevision = _ref5.updateRevision,
1510
- updateRevision = _ref5$updateRevision === void 0 ? 0 : _ref5$updateRevision;
1556
+ this.setState(_ref6 => {
1557
+ var _ref6$updateRevision = _ref6.updateRevision,
1558
+ updateRevision = _ref6$updateRevision === void 0 ? 0 : _ref6$updateRevision;
1511
1559
  return {
1512
1560
  updateRevision: (updateRevision + 1) % Number.MAX_SAFE_INTEGER
1513
1561
  };
@@ -1684,8 +1732,8 @@ class Grid extends PureComponent {
1684
1732
  * @param value New value set
1685
1733
  */
1686
1734
  handleEditCellChange(value) {
1687
- this.setState(_ref6 => {
1688
- var editingCell = _ref6.editingCell;
1735
+ this.setState(_ref7 => {
1736
+ var editingCell = _ref7.editingCell;
1689
1737
  try {
1690
1738
  assertIsDefined(editingCell);
1691
1739
  return {
@@ -1708,14 +1756,14 @@ class Grid extends PureComponent {
1708
1756
  * @param options Options for committing
1709
1757
  */
1710
1758
  handleEditCellCommit(value) {
1711
- var _ref7 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
1712
- _ref7$direction = _ref7.direction,
1713
- direction = _ref7$direction === void 0 ? SELECTION_DIRECTION.DOWN : _ref7$direction,
1714
- _ref7$fillRange = _ref7.fillRange,
1715
- fillRange = _ref7$fillRange === void 0 ? false : _ref7$fillRange;
1716
- var _this$state0 = this.state,
1717
- editingCell = _this$state0.editingCell,
1718
- selectedRanges = _this$state0.selectedRanges;
1759
+ var _ref8 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
1760
+ _ref8$direction = _ref8.direction,
1761
+ direction = _ref8$direction === void 0 ? SELECTION_DIRECTION.DOWN : _ref8$direction,
1762
+ _ref8$fillRange = _ref8.fillRange,
1763
+ fillRange = _ref8$fillRange === void 0 ? false : _ref8$fillRange;
1764
+ var _this$state6 = this.state,
1765
+ editingCell = _this$state6.editingCell,
1766
+ selection = _this$state6.selection;
1719
1767
  if (!editingCell) throw new Error('editingCell not set');
1720
1768
  var column = editingCell.column,
1721
1769
  row = editingCell.row;
@@ -1730,12 +1778,14 @@ class Grid extends PureComponent {
1730
1778
  return;
1731
1779
  }
1732
1780
  if (fillRange) {
1733
- this.setValueForRanges(selectedRanges, value);
1781
+ // fillRange is only reachable for editable (non-keyed) tables
1782
+ assertIsRangedSelection(selection);
1783
+ this.setValueForRanges(selection.toRanges(), value);
1734
1784
  } else {
1735
1785
  this.setValueForCell(column, row, value);
1736
1786
  }
1737
1787
  if (direction !== null) {
1738
- this.moveCursorInDirection(direction);
1788
+ this.handleKeyAdvanceCursor(direction);
1739
1789
  }
1740
1790
  this.setState({
1741
1791
  editingCell: null
@@ -1851,21 +1901,20 @@ class Grid extends PureComponent {
1851
1901
  updateRenderState() {
1852
1902
  if (!this.canvas) throw new Error('canvas is not set');
1853
1903
  if (!this.canvasContext) throw new Error('context not set');
1854
- var _this$state1 = this.state,
1855
- cursorColumn = _this$state1.cursorColumn,
1856
- cursorRow = _this$state1.cursorRow,
1857
- draggingColumn = _this$state1.draggingColumn,
1858
- draggingColumnSeparator = _this$state1.draggingColumnSeparator,
1859
- draggingRow = _this$state1.draggingRow,
1860
- draggingRowOffset = _this$state1.draggingRowOffset,
1861
- draggingRowSeparator = _this$state1.draggingRowSeparator,
1862
- editingCell = _this$state1.editingCell,
1863
- isDraggingHorizontalScrollBar = _this$state1.isDraggingHorizontalScrollBar,
1864
- isDraggingVerticalScrollBar = _this$state1.isDraggingVerticalScrollBar,
1865
- isDragging = _this$state1.isDragging,
1866
- mouseX = _this$state1.mouseX,
1867
- mouseY = _this$state1.mouseY,
1868
- selectedRanges = _this$state1.selectedRanges;
1904
+ var _this$state7 = this.state,
1905
+ draggingColumn = _this$state7.draggingColumn,
1906
+ draggingColumnSeparator = _this$state7.draggingColumnSeparator,
1907
+ draggingRow = _this$state7.draggingRow,
1908
+ draggingRowOffset = _this$state7.draggingRowOffset,
1909
+ draggingRowSeparator = _this$state7.draggingRowSeparator,
1910
+ editingCell = _this$state7.editingCell,
1911
+ gestureMode = _this$state7.gestureMode,
1912
+ isDraggingHorizontalScrollBar = _this$state7.isDraggingHorizontalScrollBar,
1913
+ isDraggingVerticalScrollBar = _this$state7.isDraggingVerticalScrollBar,
1914
+ isDragging = _this$state7.isDragging,
1915
+ mouseX = _this$state7.mouseX,
1916
+ mouseY = _this$state7.mouseY,
1917
+ selection = _this$state7.selection;
1869
1918
  var _this$props8 = this.props,
1870
1919
  model = _this$props8.model,
1871
1920
  stateOverride = _this$props8.stateOverride;
@@ -1884,7 +1933,7 @@ class Grid extends PureComponent {
1884
1933
  metrics,
1885
1934
  mouseX,
1886
1935
  mouseY,
1887
- selectedRanges,
1936
+ selection,
1888
1937
  draggingColumn,
1889
1938
  draggingColumnSeparator,
1890
1939
  draggingRow,
@@ -1894,8 +1943,7 @@ class Grid extends PureComponent {
1894
1943
  isDraggingHorizontalScrollBar,
1895
1944
  isDraggingVerticalScrollBar,
1896
1945
  isDragging,
1897
- cursorColumn,
1898
- cursorRow
1946
+ gestureMode
1899
1947
  }, stateOverride);
1900
1948
  return this.renderState;
1901
1949
  }
@@ -1919,7 +1967,9 @@ class Grid extends PureComponent {
1919
1967
  onMouseMove: this.handleMouseMove,
1920
1968
  onMouseLeave: this.handleMouseLeave,
1921
1969
  tabIndex: 0,
1922
- children: "Your browser does not support HTML canvas. Update your browser?"
1970
+ children: /*#__PURE__*/_jsx(GridA11yFallback, {
1971
+ registerDrawListener: this.registerDrawListener
1972
+ })
1923
1973
  }), this.renderInputField(), children]
1924
1974
  });
1925
1975
  }
@@ -1939,6 +1989,7 @@ _defineProperty(Grid, "defaultProps", {
1939
1989
  movedRows: EMPTY_ARRAY,
1940
1990
  onError: () => undefined,
1941
1991
  onSelectionChanged: () => undefined,
1992
+ onSelectionChange: _selection => undefined,
1942
1993
  onMovedColumnsChanged: moveOperations => undefined,
1943
1994
  onMoveColumnComplete: () => undefined,
1944
1995
  onMovedRowsChanged: () => undefined,
@@ -1954,7 +2005,8 @@ _defineProperty(Grid, "defaultProps", {
1954
2005
  theme: {
1955
2006
  autoSelectColumn: false,
1956
2007
  autoSelectRow: false
1957
- }
2008
+ },
2009
+ createEmptySelection: RangedSelection.empty
1958
2010
  });
1959
2011
  // use same constant as chrome source for windows
1960
2012
  // https://github.com/chromium/chromium/blob/973af9d461b6b5dc60208c8d3d66adc27e53da78/ui/events/blink/web_input_event_builders_win.cc#L285