@deephaven/grid 0.43.0 → 0.44.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 (115) hide show
  1. package/dist/CellInputField.css +23 -0
  2. package/dist/CellInputField.css.map +1 -0
  3. package/dist/CellInputField.js +174 -0
  4. package/dist/CellInputField.js.map +1 -0
  5. package/dist/CellRenderer.js +64 -0
  6. package/dist/CellRenderer.js.map +1 -0
  7. package/dist/ColumnHeaderGroup.js +2 -0
  8. package/dist/ColumnHeaderGroup.js.map +1 -0
  9. package/dist/DataBarCellRenderer.js +404 -0
  10. package/dist/DataBarCellRenderer.js.map +1 -0
  11. package/dist/DataBarGridModel.js +27 -0
  12. package/dist/DataBarGridModel.js.map +1 -0
  13. package/dist/EditableGridModel.js +14 -0
  14. package/dist/EditableGridModel.js.map +1 -0
  15. package/dist/EventHandlerResult.js +2 -0
  16. package/dist/EventHandlerResult.js.map +1 -0
  17. package/dist/ExpandableGridModel.js +8 -0
  18. package/dist/ExpandableGridModel.js.map +1 -0
  19. package/dist/Grid.css +45 -0
  20. package/dist/Grid.css.map +1 -0
  21. package/dist/Grid.js +1947 -0
  22. package/dist/Grid.js.map +1 -0
  23. package/dist/GridAxisRange.js +17 -0
  24. package/dist/GridAxisRange.js.map +1 -0
  25. package/dist/GridColorUtils.js +146 -0
  26. package/dist/GridColorUtils.js.map +1 -0
  27. package/dist/GridMetricCalculator.js +1500 -0
  28. package/dist/GridMetricCalculator.js.map +1 -0
  29. package/dist/GridMetrics.js +2 -0
  30. package/dist/GridMetrics.js.map +1 -0
  31. package/dist/GridModel.js +193 -0
  32. package/dist/GridModel.js.map +1 -0
  33. package/dist/GridMouseHandler.js +57 -0
  34. package/dist/GridMouseHandler.js.map +1 -0
  35. package/dist/GridRange.js +684 -0
  36. package/dist/GridRange.js.map +1 -0
  37. package/dist/GridRenderer.js +2038 -0
  38. package/dist/GridRenderer.js.map +1 -0
  39. package/dist/GridRendererTypes.js +3 -0
  40. package/dist/GridRendererTypes.js.map +1 -0
  41. package/dist/GridTestUtils.js +16 -0
  42. package/dist/GridTestUtils.js.map +1 -0
  43. package/dist/GridTheme.js +100 -0
  44. package/dist/GridTheme.js.map +1 -0
  45. package/dist/GridUtils.js +1198 -0
  46. package/dist/GridUtils.js.map +1 -0
  47. package/dist/KeyHandler.js +36 -0
  48. package/dist/KeyHandler.js.map +1 -0
  49. package/dist/MockDataBarGridModel.js +119 -0
  50. package/dist/MockDataBarGridModel.js.map +1 -0
  51. package/dist/MockGridData.js +5 -0
  52. package/dist/MockGridData.js.map +1 -0
  53. package/dist/MockGridModel.js +122 -0
  54. package/dist/MockGridModel.js.map +1 -0
  55. package/dist/MockTreeGridModel.js +193 -0
  56. package/dist/MockTreeGridModel.js.map +1 -0
  57. package/dist/StaticDataGridModel.js +40 -0
  58. package/dist/StaticDataGridModel.js.map +1 -0
  59. package/dist/TextCellRenderer.js +210 -0
  60. package/dist/TextCellRenderer.js.map +1 -0
  61. package/dist/ThemeContext.js +4 -0
  62. package/dist/ThemeContext.js.map +1 -0
  63. package/dist/TokenBoxCellRenderer.js +4 -0
  64. package/dist/TokenBoxCellRenderer.js.map +1 -0
  65. package/dist/ViewportDataGridModel.js +43 -0
  66. package/dist/ViewportDataGridModel.js.map +1 -0
  67. package/dist/errors/AssertionError.js +11 -0
  68. package/dist/errors/AssertionError.js.map +1 -0
  69. package/dist/errors/PasteError.js +11 -0
  70. package/dist/errors/PasteError.js.map +1 -0
  71. package/dist/errors/assertIsDefined.js +8 -0
  72. package/dist/errors/assertIsDefined.js.map +1 -0
  73. package/dist/errors/index.js +4 -0
  74. package/dist/errors/index.js.map +1 -0
  75. package/dist/index.js +32 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/key-handlers/EditKeyHandler.js +46 -0
  78. package/dist/key-handlers/EditKeyHandler.js.map +1 -0
  79. package/dist/key-handlers/PasteKeyHandler.js +124 -0
  80. package/dist/key-handlers/PasteKeyHandler.js.map +1 -0
  81. package/dist/key-handlers/SelectionKeyHandler.js +272 -0
  82. package/dist/key-handlers/SelectionKeyHandler.js.map +1 -0
  83. package/dist/key-handlers/TreeKeyHandler.js +45 -0
  84. package/dist/key-handlers/TreeKeyHandler.js.map +1 -0
  85. package/dist/key-handlers/index.js +5 -0
  86. package/dist/key-handlers/index.js.map +1 -0
  87. package/dist/memoizeClear.js +33 -0
  88. package/dist/memoizeClear.js.map +1 -0
  89. package/dist/mouse-handlers/EditMouseHandler.js +25 -0
  90. package/dist/mouse-handlers/EditMouseHandler.js.map +1 -0
  91. package/dist/mouse-handlers/GridColumnMoveMouseHandler.js +504 -0
  92. package/dist/mouse-handlers/GridColumnMoveMouseHandler.js.map +1 -0
  93. package/dist/mouse-handlers/GridColumnSeparatorMouseHandler.js +67 -0
  94. package/dist/mouse-handlers/GridColumnSeparatorMouseHandler.js.map +1 -0
  95. package/dist/mouse-handlers/GridHorizontalScrollBarMouseHandler.js +164 -0
  96. package/dist/mouse-handlers/GridHorizontalScrollBarMouseHandler.js.map +1 -0
  97. package/dist/mouse-handlers/GridRowMoveMouseHandler.js +139 -0
  98. package/dist/mouse-handlers/GridRowMoveMouseHandler.js.map +1 -0
  99. package/dist/mouse-handlers/GridRowSeparatorMouseHandler.js +54 -0
  100. package/dist/mouse-handlers/GridRowSeparatorMouseHandler.js.map +1 -0
  101. package/dist/mouse-handlers/GridRowTreeMouseHandler.js +58 -0
  102. package/dist/mouse-handlers/GridRowTreeMouseHandler.js.map +1 -0
  103. package/dist/mouse-handlers/GridScrollBarCornerMouseHandler.js +39 -0
  104. package/dist/mouse-handlers/GridScrollBarCornerMouseHandler.js.map +1 -0
  105. package/dist/mouse-handlers/GridSelectionMouseHandler.js +223 -0
  106. package/dist/mouse-handlers/GridSelectionMouseHandler.js.map +1 -0
  107. package/dist/mouse-handlers/GridSeparatorMouseHandler.js +213 -0
  108. package/dist/mouse-handlers/GridSeparatorMouseHandler.js.map +1 -0
  109. package/dist/mouse-handlers/GridTokenMouseHandler.js +161 -0
  110. package/dist/mouse-handlers/GridTokenMouseHandler.js.map +1 -0
  111. package/dist/mouse-handlers/GridVerticalScrollBarMouseHandler.js +165 -0
  112. package/dist/mouse-handlers/GridVerticalScrollBarMouseHandler.js.map +1 -0
  113. package/dist/mouse-handlers/index.js +13 -0
  114. package/dist/mouse-handlers/index.js.map +1 -0
  115. package/package.json +3 -3
package/dist/Grid.js ADDED
@@ -0,0 +1,1947 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
2
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
3
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
4
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
5
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
7
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
8
+ /* eslint react/no-did-update-set-state: "off" */
9
+ import React, { PureComponent } from 'react';
10
+ import classNames from 'classnames';
11
+ import memoize from 'memoize-one';
12
+ import clamp from 'lodash.clamp';
13
+ import { assertNotNull, EMPTY_ARRAY } from '@deephaven/utils';
14
+ import GridMetricCalculator from "./GridMetricCalculator.js";
15
+ import GridTheme from "./GridTheme.js";
16
+ import GridRange, { SELECTION_DIRECTION } from "./GridRange.js";
17
+ import GridRenderer from "./GridRenderer.js";
18
+ import GridUtils, { isLinkToken } from "./GridUtils.js";
19
+ import { GridSelectionMouseHandler, GridColumnMoveMouseHandler, GridColumnSeparatorMouseHandler, GridHorizontalScrollBarMouseHandler, GridRowMoveMouseHandler, GridRowSeparatorMouseHandler, GridRowTreeMouseHandler, GridScrollBarCornerMouseHandler, GridVerticalScrollBarMouseHandler, EditMouseHandler, GridTokenMouseHandler } from "./mouse-handlers/index.js";
20
+ import "./Grid.css";
21
+ import { EditKeyHandler, PasteKeyHandler, SelectionKeyHandler, TreeKeyHandler } from "./key-handlers/index.js";
22
+ import CellInputField from "./CellInputField.js";
23
+ import PasteError from "./errors/PasteError.js";
24
+ import { isExpandableGridModel } from "./ExpandableGridModel.js";
25
+ import { assertIsEditableGridModel, isEditableGridModel } from "./EditableGridModel.js";
26
+ import { assertIsDefined } from "./errors/index.js";
27
+ import ThemeContext from "./ThemeContext.js";
28
+ /**
29
+ * High performance, extendible, themeable grid component.
30
+ * Architectured to be fast and handle billions of rows/columns by default.
31
+ * The base model does not provide support for sorting, filtering, etc.
32
+ * To get that functionality, extend GridModel/GridRenderer, and add onClick/onContextMenu handlers to implement your own sort.
33
+ *
34
+ * Extend GridModel with your own data model to provide the data for the grid.
35
+ * Extend GridTheme to change the appearance if the grid. See GridTheme for all the settable values.
36
+ * Extend GridMetricCalculator to provide different metrics for the grid, such as column sizing, header sizing, etc.
37
+ * Extend GridRenderer to have complete control over the rendering process. Can override just one method such as drawColumnHeader, or override the whole drawCanvas process.
38
+ *
39
+ * Add an onViewChanged callback to page in/out data as user moves around the grid
40
+ * Can also add onClick and onContextMenu handlers to add custom functionality and menus.
41
+ */
42
+ class Grid extends PureComponent {
43
+ // use same constant as chrome source for windows
44
+ // https://github.com/chromium/chromium/blob/973af9d461b6b5dc60208c8d3d66adc27e53da78/ui/events/blink/web_input_event_builders_win.cc#L285
45
+
46
+ /**
47
+ * On some devices there may be different scaling required for high DPI. Get the scale required for the canvas.
48
+ * @param context The canvas context
49
+ * @returns The scale to use
50
+ */
51
+ static getScale(context) {
52
+ var _ref, _ref2, _ref3, _ref4, _legacyContext$webkit;
53
+ var devicePixelRatio = window.devicePixelRatio || 1;
54
+
55
+ // backingStorePixelRatio is deprecated, but check it just in case
56
+ var legacyContext = context;
57
+ var backingStorePixelRatio = // Not worth converting to a massive if structure
58
+ // Converting would reduce readability and maintainability
59
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
60
+ (_ref = (_ref2 = (_ref3 = (_ref4 = (_legacyContext$webkit = legacyContext.webkitBackingStorePixelRatio) !== null && _legacyContext$webkit !== void 0 ? _legacyContext$webkit : legacyContext.mozBackingStorePixelRatio) !== null && _ref4 !== void 0 ? _ref4 : legacyContext.msBackingStorePixelRatio) !== null && _ref3 !== void 0 ? _ref3 : legacyContext.oBackingStorePixelRatio) !== null && _ref2 !== void 0 ? _ref2 : legacyContext.backingStorePixelRatio) !== null && _ref !== void 0 ? _ref : 1;
61
+ return devicePixelRatio / backingStorePixelRatio;
62
+ }
63
+
64
+ /**
65
+ * Get the class name from the cursor provided
66
+ * @param cursor The grid cursor to use
67
+ * @returns Class name with the grid-cursor prefix or null if no cursor provided
68
+ */
69
+ static getCursorClassName(cursor) {
70
+ return cursor != null && cursor !== '' ? "grid-cursor-".concat(cursor) : null;
71
+ }
72
+
73
+ // Need to disable react/sort-comp so I can put the fields here
74
+ /* eslint-disable react/sort-comp */
75
+
76
+ /* eslint-enable react/sort-comp */
77
+
78
+ constructor(props) {
79
+ super(props);
80
+ _defineProperty(this, "renderer", void 0);
81
+ _defineProperty(this, "metricCalculator", void 0);
82
+ _defineProperty(this, "canvas", void 0);
83
+ _defineProperty(this, "canvasContext", void 0);
84
+ _defineProperty(this, "animationFrame", void 0);
85
+ _defineProperty(this, "prevMetrics", void 0);
86
+ _defineProperty(this, "metrics", void 0);
87
+ _defineProperty(this, "renderState", void 0);
88
+ _defineProperty(this, "documentCursor", void 0);
89
+ _defineProperty(this, "dragTimer", void 0);
90
+ _defineProperty(this, "keyHandlers", void 0);
91
+ _defineProperty(this, "mouseHandlers", void 0);
92
+ _defineProperty(this, "getCachedKeyHandlers", memoize(keyHandlers => [...keyHandlers, ...this.keyHandlers].sort((a, b) => a.order - b.order)));
93
+ _defineProperty(this, "getCachedMouseHandlers", memoize(mouseHandlers => [...mouseHandlers, ...this.mouseHandlers].sort((a, b) => a.order - b.order)));
94
+ this.handleClick = this.handleClick.bind(this);
95
+ this.handleContextMenu = this.handleContextMenu.bind(this);
96
+ this.handleEditCellCancel = this.handleEditCellCancel.bind(this);
97
+ this.handleEditCellChange = this.handleEditCellChange.bind(this);
98
+ this.handleEditCellCommit = this.handleEditCellCommit.bind(this);
99
+ this.handleDoubleClick = this.handleDoubleClick.bind(this);
100
+ this.handleKeyDown = this.handleKeyDown.bind(this);
101
+ this.handleMouseDown = this.handleMouseDown.bind(this);
102
+ this.handleMouseDrag = this.handleMouseDrag.bind(this);
103
+ this.handleMouseMove = this.handleMouseMove.bind(this);
104
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
105
+ this.handleMouseUp = this.handleMouseUp.bind(this);
106
+ this.handleResize = this.handleResize.bind(this);
107
+ this.handleWheel = this.handleWheel.bind(this);
108
+ var {
109
+ isStuckToBottom,
110
+ isStuckToRight,
111
+ metricCalculator,
112
+ movedColumns,
113
+ movedRows,
114
+ renderer
115
+ } = props;
116
+ this.renderer = renderer || new GridRenderer();
117
+ this.metricCalculator = metricCalculator || new GridMetricCalculator();
118
+ this.canvas = null;
119
+ this.canvasContext = null;
120
+ this.animationFrame = null;
121
+ this.prevMetrics = null;
122
+ this.metrics = null;
123
+ this.renderState = {};
124
+
125
+ // Track the cursor that is currently added to the document
126
+ // Add to document so that when dragging the cursor stays, even if mouse leaves the canvas
127
+ // Note: on document, not body so that cursor styling can be combined with
128
+ // blocked pointer events that would otherwise prevent cursor styling from showing
129
+ this.documentCursor = null;
130
+ this.dragTimer = null;
131
+
132
+ // specify handler ordering, such that any extensions can insert handlers in between
133
+ this.keyHandlers = [new EditKeyHandler(400), new PasteKeyHandler(450), new SelectionKeyHandler(500), new TreeKeyHandler(900)];
134
+ 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(825), new GridSelectionMouseHandler(900)];
135
+ this.state = {
136
+ // 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)
137
+ top: 0,
138
+ left: 0,
139
+ // The scroll offset of the top/left cell. 0,0 means the cell is at the top left
140
+ // Should be less than the width of the column
141
+ topOffset: 0,
142
+ leftOffset: 0,
143
+ // current column/row that user is dragging
144
+ draggingColumn: null,
145
+ draggingRow: null,
146
+ // Offset when dragging a row
147
+ draggingRowOffset: null,
148
+ // When drawing header separators for resizing
149
+ draggingColumnSeparator: null,
150
+ draggingRowSeparator: null,
151
+ isDraggingHorizontalScrollBar: false,
152
+ isDraggingVerticalScrollBar: false,
153
+ // Anything is performing a drag, for blocking hover rendering
154
+ isDragging: false,
155
+ // The coordinates of the mouse. May be outside of the canvas
156
+ mouseX: null,
157
+ mouseY: null,
158
+ // Move operations the user has performed on this grids columns/rows
159
+ movedColumns,
160
+ movedRows,
161
+ // Cursor (highlighted cell) location and active selected range
162
+ cursorRow: null,
163
+ cursorColumn: null,
164
+ selectionStartRow: null,
165
+ selectionStartColumn: null,
166
+ selectionEndRow: null,
167
+ selectionEndColumn: null,
168
+ // Currently selected ranges and previously selected ranges
169
+ // Store the previously selected ranges to determine if the new selection should
170
+ // deselect again (if it's the same range)
171
+ selectedRanges: EMPTY_ARRAY,
172
+ lastSelectedRanges: EMPTY_ARRAY,
173
+ // The mouse cursor style to use when hovering over the grid element
174
+ cursor: null,
175
+ // { column: number, row: number, selectionRange: [number, number], value: string | null, isQuickEdit?: boolean }
176
+ // The cell that is currently being edited
177
+ editingCell: null,
178
+ isStuckToBottom,
179
+ isStuckToRight
180
+ };
181
+ }
182
+ componentDidMount() {
183
+ var _this$canvas;
184
+ this.initContext();
185
+
186
+ // Need to explicitly add wheel event to canvas so we can preventDefault/avoid passive listener issue
187
+ // Otherwise React attaches listener at doc level and you can't prevent default
188
+ // https://github.com/facebook/react/issues/14856
189
+ (_this$canvas = this.canvas) === null || _this$canvas === void 0 ? void 0 : _this$canvas.addEventListener('wheel', this.handleWheel, {
190
+ passive: false
191
+ });
192
+ window.addEventListener('resize', this.handleResize);
193
+ this.updateCanvas();
194
+
195
+ // apply on mount, so that it works with a static model
196
+ // https://github.com/deephaven/web-client-ui/issues/581
197
+ var {
198
+ isStuckToBottom,
199
+ isStuckToRight
200
+ } = this.props;
201
+ if (isStuckToBottom) {
202
+ this.scrollToBottom();
203
+ }
204
+ if (isStuckToRight) {
205
+ this.scrollToRight();
206
+ }
207
+ }
208
+ componentDidUpdate(prevProps, prevState) {
209
+ var {
210
+ isStickyBottom,
211
+ isStickyRight,
212
+ movedColumns,
213
+ movedRows,
214
+ onMovedColumnsChanged,
215
+ onMoveColumnComplete,
216
+ onMovedRowsChanged,
217
+ onMoveRowComplete
218
+ } = this.props;
219
+ var {
220
+ isStickyBottom: prevIsStickyBottom,
221
+ isStickyRight: prevIsStickyRight,
222
+ movedColumns: prevPropMovedColumns,
223
+ movedRows: prevPropMovedRows
224
+ } = prevProps;
225
+ var {
226
+ movedColumns: prevStateMovedColumns,
227
+ movedRows: prevStateMovedRows
228
+ } = prevState;
229
+ var {
230
+ draggingColumn,
231
+ draggingRow,
232
+ movedColumns: currentStateMovedColumns,
233
+ movedRows: currentStateMovedRows
234
+ } = this.state;
235
+ if (prevPropMovedColumns !== movedColumns) {
236
+ this.setState({
237
+ movedColumns
238
+ });
239
+ }
240
+ if (prevPropMovedRows !== movedRows) {
241
+ this.setState({
242
+ movedRows
243
+ });
244
+ }
245
+ if (prevStateMovedColumns !== currentStateMovedColumns) {
246
+ onMovedColumnsChanged(currentStateMovedColumns);
247
+ }
248
+ if (prevState.draggingColumn != null && draggingColumn == null) {
249
+ onMoveColumnComplete(currentStateMovedColumns);
250
+ }
251
+ if (prevStateMovedRows !== currentStateMovedRows) {
252
+ onMovedRowsChanged(currentStateMovedRows);
253
+ }
254
+ if (prevState.draggingRow != null && draggingRow == null) {
255
+ onMoveRowComplete(currentStateMovedRows);
256
+ }
257
+ if (isStickyBottom !== prevIsStickyBottom) {
258
+ this.setState({
259
+ isStuckToBottom: false
260
+ });
261
+ }
262
+ if (isStickyRight !== prevIsStickyRight) {
263
+ this.setState({
264
+ isStuckToRight: false
265
+ });
266
+ }
267
+ this.updateMetrics();
268
+ this.requestUpdateCanvas();
269
+ this.checkStickyScroll();
270
+ if (this.validateSelection()) {
271
+ this.checkSelectionChange(prevState);
272
+ }
273
+ }
274
+ componentWillUnmount() {
275
+ var _this$canvas2;
276
+ if (this.animationFrame != null) {
277
+ cancelAnimationFrame(this.animationFrame);
278
+ }
279
+ (_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 ? void 0 : _this$canvas2.removeEventListener('wheel', this.handleWheel);
280
+ window.removeEventListener('mousemove', this.handleMouseDrag, true);
281
+ window.removeEventListener('mouseup', this.handleMouseUp, true);
282
+ window.removeEventListener('resize', this.handleResize);
283
+ this.stopDragTimer();
284
+ }
285
+ getTheme() {
286
+ var {
287
+ theme
288
+ } = this.props;
289
+ return Grid.getTheme(this.context, theme);
290
+ }
291
+ getGridPointFromEvent(event) {
292
+ assertIsDefined(this.canvas);
293
+ var rect = this.canvas.getBoundingClientRect();
294
+ var x = event.clientX - rect.left;
295
+ var y = event.clientY - rect.top;
296
+ return this.getGridPointFromXY(x, y);
297
+ }
298
+ getGridPointFromXY(x, y) {
299
+ if (!this.metrics) throw new Error('metrics must be set');
300
+ return GridUtils.getGridPointFromXY(x, y, this.metrics);
301
+ }
302
+ getMetricState() {
303
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state;
304
+ var theme = this.getTheme();
305
+ var {
306
+ model,
307
+ stateOverride
308
+ } = this.props;
309
+ if (!this.canvasContext || !this.canvas) {
310
+ throw new Error('Canvas and context must be defined to get metrics');
311
+ }
312
+ var context = this.canvasContext;
313
+ var width = this.canvas.clientWidth;
314
+ var height = this.canvas.clientHeight;
315
+ var {
316
+ left,
317
+ top,
318
+ leftOffset,
319
+ topOffset,
320
+ movedColumns,
321
+ movedRows,
322
+ isDraggingHorizontalScrollBar,
323
+ isDraggingVerticalScrollBar,
324
+ draggingColumn
325
+ } = state;
326
+ return _objectSpread({
327
+ left,
328
+ top,
329
+ leftOffset,
330
+ topOffset,
331
+ width,
332
+ height,
333
+ context,
334
+ theme,
335
+ model,
336
+ movedColumns,
337
+ movedRows,
338
+ isDraggingHorizontalScrollBar,
339
+ isDraggingVerticalScrollBar,
340
+ draggingColumn
341
+ }, stateOverride);
342
+ }
343
+ getKeyHandlers() {
344
+ var {
345
+ keyHandlers
346
+ } = this.props;
347
+ return this.getCachedKeyHandlers(keyHandlers);
348
+ }
349
+ getMouseHandlers() {
350
+ var {
351
+ mouseHandlers
352
+ } = this.props;
353
+ return this.getCachedMouseHandlers(mouseHandlers);
354
+ }
355
+
356
+ /**
357
+ * Translate from the provided visible index to the model index
358
+ * @param columnIndex The column index to get the model for
359
+ * @returns The model index
360
+ */
361
+ getModelColumn(columnIndex) {
362
+ var _this$metrics, _this$metrics$modelCo;
363
+ var modelIndex = (_this$metrics = this.metrics) === null || _this$metrics === void 0 ? void 0 : (_this$metrics$modelCo = _this$metrics.modelColumns) === null || _this$metrics$modelCo === void 0 ? void 0 : _this$metrics$modelCo.get(columnIndex);
364
+ if (modelIndex === undefined) {
365
+ throw new Error("Unable to get ModelIndex for column ".concat(columnIndex));
366
+ }
367
+ return modelIndex;
368
+ }
369
+
370
+ /**
371
+ * Translate from the provided visible index to the model index
372
+ * @param rowIndex The row index to get the model for
373
+ * @returns The model index
374
+ */
375
+ getModelRow(rowIndex) {
376
+ var _this$metrics2, _this$metrics2$modelR;
377
+ var modelIndex = (_this$metrics2 = this.metrics) === null || _this$metrics2 === void 0 ? void 0 : (_this$metrics2$modelR = _this$metrics2.modelRows) === null || _this$metrics2$modelR === void 0 ? void 0 : _this$metrics2$modelR.get(rowIndex);
378
+ if (modelIndex === undefined) {
379
+ throw new Error("Unable to get ModelIndex for row ".concat(rowIndex));
380
+ }
381
+ return modelIndex;
382
+ }
383
+
384
+ /**
385
+ * Toggle a row between expanded and collapsed states
386
+ * @param row The row to toggle expansion for
387
+ * @param expandDescendants True if nested rows should be expanded, false otherwise
388
+ */
389
+ toggleRowExpanded(row) {
390
+ var expandDescendants = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
391
+ var modelRow = this.getModelRow(row);
392
+ var {
393
+ model
394
+ } = this.props;
395
+ // We only want to set expansion if the row is expandable
396
+ // If it's not, still move the cursor to that position, as it may be outside of the current viewport and we don't know if it's expandable yet
397
+ if (isExpandableGridModel(model) && model.isRowExpandable(modelRow)) {
398
+ model.setRowExpanded(modelRow, !model.isRowExpanded(modelRow), expandDescendants);
399
+ }
400
+ this.clearSelectedRanges();
401
+ this.commitSelection(); // Need to commit before moving in case we're selecting same row again
402
+ this.moveCursorToPosition(0, row);
403
+ this.commitSelection();
404
+ this.setState({
405
+ isStuckToBottom: false
406
+ });
407
+ }
408
+
409
+ /**
410
+ * Scrolls to bottom, if not already at bottom
411
+ */
412
+ scrollToBottom() {
413
+ if (!this.metrics) return;
414
+ var {
415
+ bottomVisible,
416
+ rowCount,
417
+ top,
418
+ lastTop
419
+ } = this.metrics;
420
+ if (bottomVisible < rowCount - 1 && bottomVisible > 0 || top > lastTop) {
421
+ this.setState({
422
+ top: lastTop
423
+ });
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Scrolls to right, if not already at right
429
+ */
430
+ scrollToRight() {
431
+ if (!this.metrics) return;
432
+ var {
433
+ rightVisible,
434
+ columnCount,
435
+ left,
436
+ lastLeft
437
+ } = this.metrics;
438
+ if (rightVisible < columnCount - 1 && rightVisible > 0 || left > lastLeft) {
439
+ this.setState({
440
+ left: lastLeft
441
+ });
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Allows the selected ranges to be set programatically
447
+ * Will update the cursor and selection start/end based on the new ranges
448
+ * @param gridRanges The new selected ranges to set
449
+ */
450
+ setSelectedRanges(gridRanges) {
451
+ var {
452
+ model
453
+ } = this.props;
454
+ var {
455
+ columnCount,
456
+ rowCount
457
+ } = model;
458
+ var {
459
+ cursorRow,
460
+ cursorColumn,
461
+ selectedRanges
462
+ } = this.state;
463
+ this.setState({
464
+ selectedRanges: gridRanges,
465
+ lastSelectedRanges: selectedRanges
466
+ });
467
+ if (gridRanges.length > 0) {
468
+ var range = GridRange.boundedRange(gridRanges[0], columnCount, rowCount);
469
+ var newCursorRow = cursorRow;
470
+ var newCursorColumn = cursorColumn;
471
+ if (!range.containsCell(cursorColumn, cursorRow)) {
472
+ ({
473
+ row: newCursorRow,
474
+ column: newCursorColumn
475
+ } = range.startCell());
476
+ }
477
+ this.setState({
478
+ selectionStartColumn: range.startColumn,
479
+ selectionStartRow: range.startRow,
480
+ selectionEndColumn: range.endColumn,
481
+ selectionEndRow: range.endRow,
482
+ cursorColumn: newCursorColumn,
483
+ cursorRow: newCursorRow
484
+ });
485
+ }
486
+ }
487
+ initContext() {
488
+ var {
489
+ canvas
490
+ } = this;
491
+ var {
492
+ canvasOptions
493
+ } = this.props;
494
+ if (!canvas) throw new Error('Canvas not set');
495
+ this.canvasContext = canvas.getContext('2d', canvasOptions);
496
+ }
497
+ requestUpdateCanvas() {
498
+ if (this.animationFrame != null) {
499
+ return;
500
+ }
501
+ this.animationFrame = requestAnimationFrame(() => {
502
+ this.animationFrame = null;
503
+ this.updateCanvas();
504
+ });
505
+ }
506
+ updateCanvas() {
507
+ this.updateCanvasScale();
508
+ this.updateMetrics();
509
+ this.updateRenderState();
510
+ var {
511
+ canvasContext,
512
+ metrics,
513
+ renderState
514
+ } = this;
515
+ assertNotNull(canvasContext);
516
+ assertNotNull(metrics);
517
+ assertNotNull(renderState);
518
+ this.renderer.configureContext(canvasContext, renderState);
519
+ var {
520
+ onViewChanged
521
+ } = this.props;
522
+ onViewChanged(metrics);
523
+ this.drawCanvas(metrics);
524
+ }
525
+ updateCanvasScale() {
526
+ var {
527
+ canvas,
528
+ canvasContext
529
+ } = this;
530
+ if (!canvas) throw new Error('canvas not set');
531
+ if (!canvasContext) throw new Error('canvasContext not set');
532
+ if (!canvas.parentElement) throw new Error('Canvas has no parent element');
533
+ var scale = Grid.getScale(canvasContext);
534
+ // the parent wrapper has 100% width/height, and is used for determining size
535
+ // we don't want to stretch the canvas to 100%, to avoid fractional pixels.
536
+ // A wrapper element must be used for sizing, and canvas size must be
537
+ // set manually to a floored value in css and a scaled value in width/height
538
+ var rect = canvas.parentElement.getBoundingClientRect();
539
+ var width = Math.floor(rect.width);
540
+ var height = Math.floor(rect.height);
541
+ canvas.style.width = "".concat(width, "px");
542
+ canvas.style.height = "".concat(height, "px");
543
+ canvas.width = width * scale;
544
+ canvas.height = height * scale;
545
+ canvasContext.scale(scale, scale);
546
+ }
547
+ updateScrollBounds() {
548
+ if (!this.metrics) throw new Error('metrics not set');
549
+ var {
550
+ left,
551
+ top
552
+ } = this.state;
553
+ var {
554
+ lastLeft,
555
+ lastTop
556
+ } = this.metrics;
557
+ if (left > lastLeft) {
558
+ this.setState({
559
+ left: lastLeft,
560
+ leftOffset: 0
561
+ });
562
+ }
563
+ if (top > lastTop) {
564
+ this.setState({
565
+ top: lastTop,
566
+ topOffset: 0
567
+ });
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Compares the current metrics with the previous metrics to see if we need to scroll when it is stuck to the bottom or the right
573
+ */
574
+ checkStickyScroll() {
575
+ if (!this.metrics) {
576
+ return;
577
+ }
578
+ if (this.prevMetrics) {
579
+ var {
580
+ rowCount,
581
+ columnCount,
582
+ height,
583
+ width
584
+ } = this.metrics;
585
+ var {
586
+ rowCount: prevRowCount,
587
+ columnCount: prevColumnCount,
588
+ height: prevHeight,
589
+ width: prevWidth
590
+ } = this.prevMetrics;
591
+ if (prevRowCount !== rowCount || height !== prevHeight) {
592
+ var {
593
+ isStuckToBottom
594
+ } = this.state;
595
+ if (isStuckToBottom) {
596
+ this.scrollToBottom();
597
+ }
598
+ }
599
+ if (prevColumnCount !== columnCount || width !== prevWidth) {
600
+ var {
601
+ isStuckToRight
602
+ } = this.state;
603
+ if (isStuckToRight) {
604
+ this.scrollToRight();
605
+ }
606
+ }
607
+ }
608
+ this.prevMetrics = this.metrics;
609
+ }
610
+ updateMetrics() {
611
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state;
612
+ this.prevMetrics = this.metrics;
613
+ var {
614
+ metricCalculator
615
+ } = this;
616
+ var metricState = this.getMetricState(state);
617
+ this.metrics = metricCalculator.getMetrics(metricState);
618
+ this.updateScrollBounds();
619
+ return this.metrics;
620
+ }
621
+
622
+ /**
623
+ * Check if the selection state has changed, and call the onSelectionChanged callback if they have
624
+ * @param prevState The previous grid state
625
+ */
626
+ checkSelectionChange(prevState) {
627
+ var {
628
+ selectedRanges: oldSelectedRanges
629
+ } = prevState;
630
+ var {
631
+ selectedRanges
632
+ } = this.state;
633
+ if (selectedRanges !== oldSelectedRanges) {
634
+ var {
635
+ onSelectionChanged
636
+ } = this.props;
637
+ onSelectionChanged(selectedRanges);
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Validate the current selection, and reset if it is invalid
643
+ * @returns True if the selection is valid, false if the selection was invalid and has been reset
644
+ */
645
+ validateSelection() {
646
+ var {
647
+ model
648
+ } = this.props;
649
+ var {
650
+ selectedRanges
651
+ } = this.state;
652
+ var {
653
+ columnCount,
654
+ rowCount
655
+ } = model;
656
+ for (var i = 0; i < selectedRanges.length; i += 1) {
657
+ var range = selectedRanges[i];
658
+ if (range.endColumn != null && range.endColumn >= columnCount || range.endRow != null && range.endRow >= rowCount) {
659
+ // Just clear the selection rather than trying to trim it.
660
+ this.setState({
661
+ selectedRanges: [],
662
+ lastSelectedRanges: []
663
+ });
664
+ return false;
665
+ }
666
+ }
667
+ return true;
668
+ }
669
+
670
+ /**
671
+ * Clears all selected ranges
672
+ */
673
+ clearSelectedRanges() {
674
+ var {
675
+ selectedRanges
676
+ } = this.state;
677
+ this.setState({
678
+ selectedRanges: [],
679
+ lastSelectedRanges: selectedRanges
680
+ });
681
+ }
682
+
683
+ /** Clears all but the last selected range */
684
+ trimSelectedRanges() {
685
+ var {
686
+ selectedRanges
687
+ } = this.state;
688
+ if (selectedRanges.length > 0) {
689
+ this.setState({
690
+ selectedRanges: selectedRanges.slice(selectedRanges.length - 1)
691
+ });
692
+ }
693
+ }
694
+
695
+ /**
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
699
+ */
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
+ });
709
+ }
710
+
711
+ /**
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.
717
+ */
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;
721
+ this.setState(state => {
722
+ var {
723
+ selectedRanges,
724
+ selectionStartRow,
725
+ selectionStartColumn
726
+ } = state;
727
+ var {
728
+ theme
729
+ } = this.props;
730
+ var {
731
+ autoSelectRow,
732
+ autoSelectColumn
733
+ } = theme;
734
+ if (extendSelection && selectedRanges.length > 0) {
735
+ var lastSelectedRange = selectedRanges[selectedRanges.length - 1];
736
+ var left = null;
737
+ var top = null;
738
+ var right = null;
739
+ var bottom = null;
740
+ if (maximizePreviousRange) {
741
+ var _lastSelectedRange$st, _lastSelectedRange$st2, _lastSelectedRange$en, _lastSelectedRange$en2;
742
+ 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);
743
+ 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);
744
+ 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);
745
+ 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);
746
+ } else {
747
+ left = lastSelectedRange.startColumn;
748
+ top = lastSelectedRange.startRow;
749
+ if (selectionStartColumn != null || selectionStartRow != null) {
750
+ if (autoSelectRow === undefined || !autoSelectRow) {
751
+ left = selectionStartColumn;
752
+ }
753
+ if (autoSelectColumn === undefined || !autoSelectColumn) {
754
+ top = selectionStartRow;
755
+ }
756
+ }
757
+ right = autoSelectRow !== undefined && autoSelectRow ? null : column;
758
+ bottom = autoSelectColumn !== undefined && autoSelectColumn ? null : row;
759
+ }
760
+ var selectedRange = GridRange.makeNormalized(left, top, right, bottom);
761
+ if (lastSelectedRange.equals(selectedRange)) {
762
+ return null;
763
+ }
764
+ var _newRanges = [...selectedRanges];
765
+ _newRanges[_newRanges.length - 1] = selectedRange;
766
+ return {
767
+ selectedRanges: _newRanges,
768
+ selectionEndColumn: column,
769
+ selectionEndRow: row
770
+ };
771
+ }
772
+ var newRanges = [...selectedRanges];
773
+ var selectedColumn = autoSelectRow !== undefined && autoSelectRow ? null : column;
774
+ var selectedRow = autoSelectColumn !== undefined && autoSelectColumn ? null : row;
775
+ newRanges.push(GridRange.makeNormalized(selectedColumn, selectedRow, selectedColumn, selectedRow));
776
+ return {
777
+ selectedRanges: newRanges,
778
+ selectionEndColumn: column,
779
+ selectionEndRow: row
780
+ };
781
+ });
782
+ }
783
+
784
+ /**
785
+ * Commits the last selected range to the selected ranges.
786
+ * First checks if the last range is completely contained within another range, and if it
787
+ * is then it blows those ranges apart.
788
+ * Then it consolidates all the selected ranges, reducing them.
789
+ */
790
+ commitSelection() {
791
+ this.setState(state => {
792
+ var {
793
+ theme
794
+ } = this.props;
795
+ var {
796
+ autoSelectRow
797
+ } = theme;
798
+ var {
799
+ selectedRanges,
800
+ lastSelectedRanges,
801
+ cursorRow,
802
+ cursorColumn
803
+ } = state;
804
+ if (selectedRanges.length === 1 && (autoSelectRow !== undefined && autoSelectRow ? GridRange.rowCount(selectedRanges) === 1 : GridRange.cellCount(selectedRanges) === 1) && GridRange.rangeArraysEqual(selectedRanges, lastSelectedRanges)) {
805
+ // If it's the exact same single selection, then deselect.
806
+ // For if we click on one cell multiple times.
807
+ return {
808
+ selectedRanges: [],
809
+ lastSelectedRanges: [],
810
+ cursorColumn: null,
811
+ cursorRow: null
812
+ };
813
+ }
814
+ var newSelectedRanges = selectedRanges.slice();
815
+ if (newSelectedRanges.length > 1) {
816
+ // Check if the latest selection is entirely within a previously selected range
817
+ // If that's the case, then deselect that section instead
818
+ var lastRange = newSelectedRanges[newSelectedRanges.length - 1];
819
+ for (var i = 0; i < newSelectedRanges.length - 1; i += 1) {
820
+ var selectedRange = newSelectedRanges[i];
821
+ if (selectedRange.contains(lastRange)) {
822
+ // We found a match, now remove the two matching ranges, and add back
823
+ // the remainder of the two
824
+ var remainder = selectedRange.subtract(lastRange);
825
+ newSelectedRanges.pop();
826
+ newSelectedRanges.splice(i, 1);
827
+ newSelectedRanges = newSelectedRanges.concat(remainder);
828
+ break;
829
+ }
830
+ }
831
+ newSelectedRanges = GridRange.consolidate(newSelectedRanges);
832
+ }
833
+ var newCursorColumn = cursorColumn;
834
+ var newCursorRow = cursorRow;
835
+ if (!GridRange.containsCell(newSelectedRanges, cursorColumn, cursorRow)) {
836
+ var {
837
+ model
838
+ } = this.props;
839
+ var {
840
+ columnCount,
841
+ rowCount
842
+ } = model;
843
+ var nextCursor = GridRange.nextCell(GridRange.boundedRanges(selectedRanges, columnCount, rowCount));
844
+ if (nextCursor != null) {
845
+ ({
846
+ column: newCursorColumn,
847
+ row: newCursorRow
848
+ } = nextCursor);
849
+ } else {
850
+ newCursorColumn = null;
851
+ newCursorRow = null;
852
+ }
853
+ }
854
+ if (newSelectedRanges.length === 0) {
855
+ newCursorColumn = null;
856
+ newCursorRow = null;
857
+ }
858
+ return {
859
+ cursorRow: newCursorRow,
860
+ cursorColumn: newCursorColumn,
861
+ selectedRanges: newSelectedRanges,
862
+ lastSelectedRanges: selectedRanges
863
+ };
864
+ });
865
+ }
866
+ setFocusRow(focusedRow) {
867
+ var _userRowHeights$get;
868
+ if (!this.metrics || !this.prevMetrics) {
869
+ return;
870
+ }
871
+ var {
872
+ gridY,
873
+ height,
874
+ lastTop,
875
+ userRowHeights,
876
+ rowHeight
877
+ } = this.metrics;
878
+ var tableHeight = height - gridY;
879
+ var halfViewportHeight = Math.round(tableHeight / 2) + ((_userRowHeights$get = userRowHeights.get(focusedRow)) !== null && _userRowHeights$get !== void 0 ? _userRowHeights$get : rowHeight);
880
+ var metricState = this.getMetricState();
881
+ var newTop = this.metricCalculator.getLastTop(metricState, focusedRow + 1, halfViewportHeight);
882
+ this.setState({
883
+ top: Math.min(lastTop, newTop),
884
+ selectedRanges: [new GridRange(null, focusedRow, null, focusedRow)],
885
+ isStuckToBottom: false
886
+ });
887
+ var {
888
+ cursorColumn
889
+ } = this.state;
890
+ this.moveCursorToPosition(cursorColumn, focusedRow, false, false);
891
+ }
892
+
893
+ /**
894
+ * Set the selection to the entire grid
895
+ */
896
+ selectAll() {
897
+ var {
898
+ model,
899
+ theme
900
+ } = this.props;
901
+ var {
902
+ autoSelectRow,
903
+ autoSelectColumn
904
+ } = theme;
905
+ var top = autoSelectColumn !== undefined && autoSelectColumn ? null : 0;
906
+ var bottom = autoSelectColumn !== undefined && autoSelectColumn ? null : model.rowCount - 1;
907
+ var left = autoSelectRow !== undefined && autoSelectRow ? null : 0;
908
+ var right = autoSelectRow !== undefined && autoSelectRow ? null : model.columnCount - 1;
909
+ this.setSelectedRanges([new GridRange(left, top, right, bottom)]);
910
+ }
911
+
912
+ /**
913
+ * Move the cursor in relation to the current cursor position
914
+ * @param deltaColumn Number of columns to move the cursor
915
+ * @param deltaRow Number of rows to move the cursor
916
+ * @param extendSelection True if the current selection should be extended, false to start a new selection
917
+ */
918
+ moveCursor(deltaColumn, deltaRow, extendSelection) {
919
+ var {
920
+ cursorRow,
921
+ cursorColumn,
922
+ selectionEndColumn,
923
+ selectionEndRow
924
+ } = this.state;
925
+ var column = extendSelection ? selectionEndColumn : cursorColumn;
926
+ var row = extendSelection ? selectionEndRow : cursorRow;
927
+ if (row === null || column === null) {
928
+ var {
929
+ left,
930
+ top
931
+ } = this.state;
932
+ this.moveCursorToPosition(left, top, extendSelection);
933
+ } else {
934
+ var {
935
+ model
936
+ } = this.props;
937
+ var {
938
+ columnCount,
939
+ rowCount
940
+ } = model;
941
+ var _left = clamp(column + deltaColumn, 0, columnCount - 1);
942
+ var _top = clamp(row + deltaRow, 0, rowCount - 1);
943
+ this.moveCursorToPosition(_left, _top, extendSelection);
944
+ }
945
+ }
946
+
947
+ /**
948
+ * Move the cursor in the provided selection direction
949
+ * @param direction The direction to move the cursor in
950
+ */
951
+ moveCursorInDirection() {
952
+ var direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : GridRange.SELECTION_DIRECTION.DOWN;
953
+ var {
954
+ model
955
+ } = this.props;
956
+ var {
957
+ columnCount,
958
+ rowCount
959
+ } = model;
960
+ var {
961
+ cursorRow,
962
+ cursorColumn,
963
+ selectedRanges
964
+ } = this.state;
965
+ var ranges = selectedRanges.length > 0 ? selectedRanges : [GridRange.makeCell(cursorColumn, cursorRow)];
966
+ var nextCursor = null;
967
+ if (ranges.length === 1 && GridRange.cellCount(ranges) === 1) {
968
+ var _gridRange$nextCell;
969
+ // If we only have one cell selected, we want to update the cursor and we want to update the selected cells
970
+ var gridRange = new GridRange(0, 0, columnCount - 1, rowCount - 1);
971
+ nextCursor = (_gridRange$nextCell = gridRange.nextCell(cursorColumn, cursorRow, direction)) !== null && _gridRange$nextCell !== void 0 ? _gridRange$nextCell : gridRange.startCell(direction);
972
+ } else {
973
+ nextCursor = GridRange.nextCell(GridRange.boundedRanges(ranges, columnCount, rowCount), cursorColumn, cursorRow, direction);
974
+ }
975
+ if (nextCursor != null) {
976
+ var {
977
+ column,
978
+ row
979
+ } = nextCursor;
980
+ this.setState({
981
+ cursorColumn: column,
982
+ cursorRow: row
983
+ });
984
+ if (!GridRange.containsCell(selectedRanges, column, row)) {
985
+ this.setState({
986
+ selectedRanges: [GridRange.makeCell(column, row)],
987
+ selectionStartColumn: column,
988
+ selectionStartRow: row,
989
+ selectionEndColumn: column,
990
+ selectionEndRow: row
991
+ });
992
+ }
993
+ this.moveViewToCell(nextCursor.column, nextCursor.row);
994
+ }
995
+ }
996
+
997
+ /**
998
+ * Move a cursor to the specified position in the grid.
999
+ * @param column The column index to move the cursor to
1000
+ * @param row The row index to move the cursor to
1001
+ * @param extendSelection Whether to extend the current selection (eg. holding Shift)
1002
+ * @param keepCursorInView Whether to move the viewport so that the cursor is in view
1003
+ * @param maximizePreviousRange With this and `extendSelection` true, it will maximize/add to the previous range only, ignoring where the selection was started
1004
+ */
1005
+ moveCursorToPosition(column, row) {
1006
+ var extendSelection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1007
+ var keepCursorInView = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
1008
+ var maximizePreviousRange = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
1009
+ if (!extendSelection) {
1010
+ this.beginSelection(column, row);
1011
+ }
1012
+ this.moveSelection(column, row, extendSelection, maximizePreviousRange);
1013
+ if (keepCursorInView) {
1014
+ this.moveViewToCell(column, row);
1015
+ }
1016
+ }
1017
+
1018
+ /**
1019
+ * Moves the view to make the specified cell visible
1020
+ *
1021
+ * @param column The column index to bring into view
1022
+ * @param row The row index to bring into view
1023
+ */
1024
+ moveViewToCell(column, row) {
1025
+ if (!this.metrics) throw new Error('metrics not set');
1026
+ var {
1027
+ metricCalculator
1028
+ } = this;
1029
+ var {
1030
+ bottomVisible,
1031
+ rightVisible,
1032
+ topVisible,
1033
+ leftVisible
1034
+ } = this.metrics;
1035
+ var metricState = this.getMetricState(this.state);
1036
+ var {
1037
+ top,
1038
+ left,
1039
+ topOffset,
1040
+ leftOffset
1041
+ } = this.state;
1042
+ if (row != null && !GridUtils.isFloatingRow(row, this.metrics)) {
1043
+ if (row < topVisible) {
1044
+ top = metricCalculator.getTopForTopVisible(metricState, row);
1045
+ topOffset = 0;
1046
+ } else if (row > bottomVisible) {
1047
+ top = metricCalculator.getTopForBottomVisible(metricState, row);
1048
+ topOffset = 0;
1049
+ }
1050
+ }
1051
+ if (column != null && !GridUtils.isFloatingColumn(column, this.metrics)) {
1052
+ if (column < leftVisible) {
1053
+ left = metricCalculator.getLeftForLeftVisible(metricState, column);
1054
+ leftOffset = 0;
1055
+ } else if (column > rightVisible) {
1056
+ left = metricCalculator.getLeftForRightVisible(metricState, column);
1057
+ leftOffset = 0;
1058
+ }
1059
+ }
1060
+ this.setViewState({
1061
+ top,
1062
+ left,
1063
+ topOffset,
1064
+ leftOffset
1065
+ });
1066
+ }
1067
+
1068
+ /**
1069
+ * Checks the `top` and `left` properties that are set and updates the isStuckToBottom/Right properties
1070
+ * Should be called when user interaction occurs
1071
+ * @param viewState New state properties to set.
1072
+ * @param forceUpdate Whether to force an update.
1073
+ */
1074
+ setViewState(viewState) {
1075
+ var forceUpdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1076
+ if (!this.metrics) throw new Error('metrics not set');
1077
+ var {
1078
+ isStickyBottom,
1079
+ isStickyRight
1080
+ } = this.props;
1081
+ var {
1082
+ top,
1083
+ left
1084
+ } = viewState;
1085
+ var {
1086
+ lastTop,
1087
+ lastLeft
1088
+ } = this.metrics;
1089
+ if (top != null) {
1090
+ this.setState({
1091
+ isStuckToBottom: isStickyBottom && top >= lastTop
1092
+ });
1093
+ }
1094
+ if (left != null) {
1095
+ this.setState({
1096
+ isStuckToRight: isStickyRight && left >= lastLeft
1097
+ });
1098
+ }
1099
+ this.setState(viewState);
1100
+ if (forceUpdate) {
1101
+ this.forceUpdate();
1102
+ }
1103
+ }
1104
+
1105
+ /**
1106
+ * Start editing the data at the given index
1107
+ *
1108
+ * @param column The visible column index to start editing
1109
+ * @param row The visible row index to start editing
1110
+ * @param isQuickEdit If this is a quick edit (the arrow keys can commit)
1111
+ * @param selectionRange The tuple [start,end] text selection range of the value to select when editing
1112
+ * @param value The value to start with in the edit field. Leave undefined to use the current value.
1113
+ */
1114
+ startEditing(column, row) {
1115
+ var isQuickEdit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1116
+ var selectionRange = arguments.length > 3 ? arguments[3] : undefined;
1117
+ var value = arguments.length > 4 ? arguments[4] : undefined;
1118
+ var {
1119
+ model
1120
+ } = this.props;
1121
+ if (!isEditableGridModel(model)) throw new Error('model is not editable');
1122
+ var modelColumn = this.getModelColumn(column);
1123
+ var modelRow = this.getModelRow(row);
1124
+ var cell = {
1125
+ column,
1126
+ row,
1127
+ selectionRange,
1128
+ value: value !== undefined ? value : model.editValueForCell(modelColumn, modelRow),
1129
+ isQuickEdit
1130
+ };
1131
+ this.setState({
1132
+ editingCell: cell,
1133
+ cursorColumn: column,
1134
+ cursorRow: row
1135
+ });
1136
+ this.moveViewToCell(column, row);
1137
+ }
1138
+
1139
+ /**
1140
+ * Check if a value is valid for a specific cell
1141
+ * @param column Column index of the cell to check
1142
+ * @param row Row index of the cell to check
1143
+ * @param value Value to check
1144
+ * @returns True if the value is valid for the provided cell, false otherwise
1145
+ */
1146
+ isValidForCell(column, row, value) {
1147
+ var {
1148
+ model
1149
+ } = this.props;
1150
+ var modelColumn = this.getModelColumn(column);
1151
+ var modelRow = this.getModelRow(row);
1152
+ return isEditableGridModel(model) && model.isValidForCell(modelColumn, modelRow, value);
1153
+ }
1154
+
1155
+ /**
1156
+ * Paste a value with the current selection
1157
+ * It first needs to validate that the pasted table is valid for the given selection.
1158
+ * Also may update selection if single cells are selected and a table is pasted.
1159
+ * @param value Table or a string that is being pasted
1160
+ */
1161
+ pasteValue(value) {
1162
+ var _this = this;
1163
+ return _asyncToGenerator(function* () {
1164
+ var {
1165
+ model
1166
+ } = _this.props;
1167
+ var {
1168
+ movedColumns,
1169
+ movedRows,
1170
+ selectedRanges
1171
+ } = _this.state;
1172
+ try {
1173
+ assertIsEditableGridModel(model);
1174
+ if (!model.isEditable || !selectedRanges.every(range => model.isEditableRange(range))) {
1175
+ throw new PasteError("Can't paste in to read-only area.");
1176
+ }
1177
+ if (selectedRanges.length <= 0) {
1178
+ throw new PasteError('Select an area to paste to.');
1179
+ }
1180
+ if (typeof value === 'string') {
1181
+ // Just paste the value into all the selected cells
1182
+ var _edits = [];
1183
+ var modelRanges = GridUtils.getModelRanges(selectedRanges, movedColumns, movedRows);
1184
+ GridRange.forEachCell(modelRanges, (column, row) => {
1185
+ _edits.push({
1186
+ column,
1187
+ row,
1188
+ text: value
1189
+ });
1190
+ });
1191
+ yield model.setValues(_edits);
1192
+ return;
1193
+ }
1194
+
1195
+ // Otherwise it's a table of data
1196
+ var tableHeight = value.length;
1197
+ var tableWidth = value[0].length;
1198
+ var {
1199
+ columnCount,
1200
+ rowCount
1201
+ } = model;
1202
+ var _ranges = selectedRanges;
1203
+ // If each cell is a single selection, we need to update the selection to map to the newly pasted data
1204
+ if (_ranges.every(range => {
1205
+ var _range$startColumn, _range$startRow;
1206
+ return GridRange.cellCount([range]) === 1 && ((_range$startColumn = range.startColumn) !== null && _range$startColumn !== void 0 ? _range$startColumn : 0) + tableWidth <= columnCount && ((_range$startRow = range.startRow) !== null && _range$startRow !== void 0 ? _range$startRow : 0) + tableHeight <= rowCount;
1207
+ })) {
1208
+ // Remap the selected ranges
1209
+ _ranges = _ranges.map(range => {
1210
+ var _range$startColumn2, _range$startRow2;
1211
+ return new GridRange(range.startColumn, range.startRow, ((_range$startColumn2 = range.startColumn) !== null && _range$startColumn2 !== void 0 ? _range$startColumn2 : 0) + tableWidth - 1, ((_range$startRow2 = range.startRow) !== null && _range$startRow2 !== void 0 ? _range$startRow2 : 0) + tableHeight - 1);
1212
+ });
1213
+ _this.setSelectedRanges(_ranges);
1214
+ }
1215
+ if (!_ranges.every(range => GridRange.rowCount([range]) === tableHeight && GridRange.columnCount([range]) === tableWidth)) {
1216
+ throw new PasteError('Copy and paste area are not same size.');
1217
+ }
1218
+ var edits = [];
1219
+ _ranges.forEach(range => {
1220
+ for (var x = 0; x < tableWidth; x += 1) {
1221
+ for (var y = 0; y < tableHeight; y += 1) {
1222
+ var _range$startColumn3, _range$startRow3;
1223
+ edits.push({
1224
+ column: ((_range$startColumn3 = range.startColumn) !== null && _range$startColumn3 !== void 0 ? _range$startColumn3 : 0) + x,
1225
+ row: ((_range$startRow3 = range.startRow) !== null && _range$startRow3 !== void 0 ? _range$startRow3 : 0) + y,
1226
+ text: value[y][x]
1227
+ });
1228
+ }
1229
+ }
1230
+ });
1231
+ yield model.setValues(edits);
1232
+ } catch (e) {
1233
+ var {
1234
+ onError
1235
+ } = _this.props;
1236
+ onError(e instanceof Error ? e : new Error("".concat(e)));
1237
+ }
1238
+ })();
1239
+ }
1240
+
1241
+ /**
1242
+ * Set a value to a specific cell. If the value is not valid for that cell, do not set it
1243
+ * @param column Column index to set the value for
1244
+ * @param row Row index to set the value for
1245
+ * @param value Value to set at that cell
1246
+ * @returns true If the value was valid and attempted to be set, false is it was not valid
1247
+ */
1248
+ setValueForCell(column, row, value) {
1249
+ var {
1250
+ model
1251
+ } = this.props;
1252
+ assertIsEditableGridModel(model);
1253
+ var modelColumn = this.getModelColumn(column);
1254
+ var modelRow = this.getModelRow(row);
1255
+ if (model.isValidForCell(modelColumn, modelRow, value)) {
1256
+ model.setValueForCell(modelColumn, modelRow, value);
1257
+ return true;
1258
+ }
1259
+ return false;
1260
+ }
1261
+
1262
+ /**
1263
+ * Set a value on all the ranges provided
1264
+ * @param ranges Ranges to set
1265
+ * @param value The value to set on all the ranges
1266
+ */
1267
+ setValueForRanges(ranges, value) {
1268
+ var {
1269
+ model
1270
+ } = this.props;
1271
+ var {
1272
+ movedColumns,
1273
+ movedRows
1274
+ } = this.state;
1275
+ var modelRanges = GridUtils.getModelRanges(ranges, movedColumns, movedRows);
1276
+ if (isEditableGridModel(model)) {
1277
+ model.setValueForRanges(modelRanges, value);
1278
+ }
1279
+ }
1280
+
1281
+ /**
1282
+ * Check if a given cell is within the current selection
1283
+ * @param row Row to check
1284
+ * @param column Column to check
1285
+ * @returns True if the cell is in the current selection, false otherwise
1286
+ */
1287
+ isSelected(row, column) {
1288
+ var {
1289
+ selectedRanges
1290
+ } = this.state;
1291
+ for (var i = 0; i < selectedRanges.length; i += 1) {
1292
+ var _selectedRange$endRow, _selectedRange$endCol;
1293
+ var selectedRange = selectedRanges[i];
1294
+ var rowSelected = selectedRange.startRow === null || selectedRange.startRow <= row && row <= ((_selectedRange$endRow = selectedRange.endRow) !== null && _selectedRange$endRow !== void 0 ? _selectedRange$endRow : 0);
1295
+ var columnSelected = selectedRange.startColumn === null || selectedRange.startColumn <= column && column <= ((_selectedRange$endCol = selectedRange.endColumn) !== null && _selectedRange$endCol !== void 0 ? _selectedRange$endCol : 0);
1296
+ if (rowSelected && columnSelected) {
1297
+ return true;
1298
+ }
1299
+ }
1300
+ return false;
1301
+ }
1302
+ addDocumentCursor() {
1303
+ var cursor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1304
+ if (this.documentCursor === Grid.getCursorClassName(cursor)) return;
1305
+ if (this.documentCursor != null) {
1306
+ document.documentElement.classList.remove(this.documentCursor);
1307
+ }
1308
+ this.documentCursor = Grid.getCursorClassName(cursor);
1309
+ if (this.documentCursor != null) {
1310
+ document.documentElement.classList.add(this.documentCursor);
1311
+ }
1312
+ document.documentElement.classList.add('grid-block-events');
1313
+ }
1314
+ removeDocumentCursor() {
1315
+ if (this.documentCursor != null) {
1316
+ document.documentElement.classList.remove(this.documentCursor);
1317
+ document.documentElement.classList.remove('grid-block-events');
1318
+ this.documentCursor = null;
1319
+ }
1320
+ }
1321
+ startDragTimer(event) {
1322
+ this.stopDragTimer();
1323
+ var mouseEvent = new MouseEvent('custom', event.nativeEvent);
1324
+ this.dragTimer = setTimeout(() => {
1325
+ this.handleMouseDrag(mouseEvent);
1326
+ }, Grid.dragTimeout);
1327
+ }
1328
+ stopDragTimer() {
1329
+ if (this.dragTimer) {
1330
+ clearTimeout(this.dragTimer);
1331
+ this.dragTimer = null;
1332
+ }
1333
+ }
1334
+
1335
+ /**
1336
+ * Draw the grid with the metrics provided
1337
+ * When scrolling you've have to re-draw the whole canvas. As a consequence, all these drawing methods
1338
+ * must be very quick.
1339
+ * @param metrics Metrics to use for rendering the grid
1340
+ */
1341
+ drawCanvas() {
1342
+ var metrics = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.updateMetrics();
1343
+ if (!this.canvas) throw new Error('canvas is not set');
1344
+ if (!this.canvasContext) throw new Error('context not set');
1345
+ var {
1346
+ renderer,
1347
+ canvasContext: context,
1348
+ renderState
1349
+ } = this;
1350
+ context.save();
1351
+ renderer.drawCanvas(renderState);
1352
+ context.restore();
1353
+ }
1354
+
1355
+ /**
1356
+ * Set focus to this grid element
1357
+ */
1358
+ focus() {
1359
+ var _this$canvas3;
1360
+ (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 ? void 0 : _this$canvas3.focus();
1361
+ }
1362
+
1363
+ /**
1364
+ * Check if this grid is currently focused
1365
+ * @returns True if the active element is this grid
1366
+ */
1367
+ isFocused() {
1368
+ return document.activeElement === this.canvas;
1369
+ }
1370
+
1371
+ /**
1372
+ * Handle a mouse click event. Pass the event to the registered mouse handlers until one handles it.
1373
+ * Focuses the grid after the click.
1374
+ * @param event The mouse event
1375
+ */
1376
+ handleClick(event) {
1377
+ var _this$canvas4;
1378
+ var gridPoint = this.getGridPointFromEvent(event);
1379
+ var mouseHandlers = this.getMouseHandlers();
1380
+ for (var i = 0; i < mouseHandlers.length; i += 1) {
1381
+ var mouseHandler = mouseHandlers[i];
1382
+ if (mouseHandler.onClick(gridPoint, this, event) !== false) {
1383
+ event.stopPropagation();
1384
+ event.preventDefault();
1385
+ break;
1386
+ }
1387
+ }
1388
+ (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 ? void 0 : _this$canvas4.focus();
1389
+ }
1390
+
1391
+ /**
1392
+ * Handle a mouse context menu event. Pass the event to the registered mouse handlers until one handles it.
1393
+ * @param event The mouse event triggering the context menu
1394
+ */
1395
+ handleContextMenu(event) {
1396
+ var gridPoint = this.getGridPointFromEvent(event);
1397
+ var mouseHandlers = this.getMouseHandlers();
1398
+ for (var i = 0; i < mouseHandlers.length; i += 1) {
1399
+ var mouseHandler = mouseHandlers[i];
1400
+ if (mouseHandler.onContextMenu(gridPoint, this, event) !== false) {
1401
+ event.stopPropagation();
1402
+ event.preventDefault();
1403
+ break;
1404
+ }
1405
+ }
1406
+ }
1407
+
1408
+ /**
1409
+ * Handle a key down event from the keyboard. Pass the event to the registered keyboard handlers until one handles it.
1410
+ * @param event Keyboard event
1411
+ */
1412
+ handleKeyDown(event) {
1413
+ var keyHandlers = this.getKeyHandlers();
1414
+ for (var i = 0; i < keyHandlers.length; i += 1) {
1415
+ var keyHandler = keyHandlers[i];
1416
+ var result = keyHandler.onDown(event, this);
1417
+ if (result !== false) {
1418
+ var _options$stopPropagat, _options$preventDefau;
1419
+ var options = result;
1420
+ if ((_options$stopPropagat = options === null || options === void 0 ? void 0 : options.stopPropagation) !== null && _options$stopPropagat !== void 0 ? _options$stopPropagat : true) event.stopPropagation();
1421
+ if ((_options$preventDefau = options === null || options === void 0 ? void 0 : options.preventDefault) !== null && _options$preventDefau !== void 0 ? _options$preventDefau : true) event.preventDefault();
1422
+ break;
1423
+ }
1424
+ }
1425
+ }
1426
+
1427
+ /**
1428
+ * Notify all of the mouse handlers for this grid of a mouse event.
1429
+ * @param functionName The name of the function in the mouse handler to call
1430
+ * @param event The mouse event to notify
1431
+ * @param updateCoordinates Whether to update the mouse coordinates
1432
+ * @param addCursorToDocument Whether to add a cursor overlay or not (for dragging)
1433
+ */
1434
+ notifyMouseHandlers(functionName, event) {
1435
+ var updateCoordinates = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
1436
+ var addCursorToDocument = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
1437
+ var gridPoint = this.getGridPointFromEvent(event);
1438
+ var mouseHandlers = this.getMouseHandlers();
1439
+ var cursor = null;
1440
+ for (var i = 0; i < mouseHandlers.length; i += 1) {
1441
+ var mouseHandler = mouseHandlers[i];
1442
+ var result = mouseHandler[functionName] != null && mouseHandler[functionName](gridPoint, this, event);
1443
+ if (result !== false) {
1444
+ var _options$stopPropagat2, _options$preventDefau2;
1445
+ if (mouseHandler.cursor != null) {
1446
+ ({
1447
+ cursor
1448
+ } = mouseHandler);
1449
+ if (addCursorToDocument) {
1450
+ this.addDocumentCursor(cursor);
1451
+ }
1452
+ }
1453
+
1454
+ // result is bool or object, events are stopped by default
1455
+ var options = result;
1456
+ if ((_options$stopPropagat2 = options === null || options === void 0 ? void 0 : options.stopPropagation) !== null && _options$stopPropagat2 !== void 0 ? _options$stopPropagat2 : true) event.stopPropagation();
1457
+ if ((_options$preventDefau2 = options === null || options === void 0 ? void 0 : options.preventDefault) !== null && _options$preventDefau2 !== void 0 ? _options$preventDefau2 : true) event.preventDefault();
1458
+ break;
1459
+ }
1460
+ }
1461
+ this.setState({
1462
+ cursor
1463
+ });
1464
+ if (updateCoordinates) {
1465
+ var {
1466
+ x,
1467
+ y
1468
+ } = gridPoint;
1469
+ this.setState({
1470
+ mouseX: x,
1471
+ mouseY: y
1472
+ });
1473
+ }
1474
+ }
1475
+ handleMouseDown(event) {
1476
+ window.addEventListener('mousemove', this.handleMouseDrag, true);
1477
+ window.addEventListener('mouseup', this.handleMouseUp, true);
1478
+ if (event.button != null && event.button !== 0) {
1479
+ return;
1480
+ }
1481
+ this.notifyMouseHandlers('onDown', event);
1482
+ this.startDragTimer(event);
1483
+ }
1484
+ handleDoubleClick(event) {
1485
+ this.notifyMouseHandlers('onDoubleClick', event);
1486
+ }
1487
+ handleMouseMove(event) {
1488
+ this.notifyMouseHandlers('onMove', event);
1489
+ }
1490
+ handleMouseLeave(event) {
1491
+ this.notifyMouseHandlers('onLeave', event, false);
1492
+ this.setState({
1493
+ mouseX: null,
1494
+ mouseY: null
1495
+ });
1496
+ }
1497
+ handleMouseDrag(event) {
1498
+ this.setState({
1499
+ isDragging: true
1500
+ });
1501
+ this.notifyMouseHandlers('onDrag', event, true, true);
1502
+ this.stopDragTimer();
1503
+ }
1504
+ handleMouseUp(event) {
1505
+ window.removeEventListener('mousemove', this.handleMouseDrag, true);
1506
+ window.removeEventListener('mouseup', this.handleMouseUp, true);
1507
+ if (event.button != null && event.button !== 0) {
1508
+ return;
1509
+ }
1510
+ this.notifyMouseHandlers('onUp', event, false);
1511
+ this.stopDragTimer();
1512
+ this.removeDocumentCursor();
1513
+ }
1514
+ handleResize() {
1515
+ /**
1516
+ * We need to always redraw the canvas in the same frame as the updateCanvasScale
1517
+ * because it clears the canvas by nature of direct dom manipulation. However,
1518
+ * We also need to verify the state/metrics, which we currently have no way
1519
+ * of doing outside of a full componentDidUpdate() call, so we force the update.
1520
+ * Ideally, we could verify state/metrics without the forced update.
1521
+ */
1522
+ this.updateCanvas();
1523
+ if (!this.metrics) throw new Error('metrics not set');
1524
+ this.forceUpdate();
1525
+ }
1526
+ handleWheel(event) {
1527
+ this.notifyMouseHandlers('onWheel', event);
1528
+ if (event.defaultPrevented) {
1529
+ return;
1530
+ }
1531
+ var {
1532
+ metricCalculator,
1533
+ metrics
1534
+ } = this;
1535
+ var metricState = this.getMetricState();
1536
+ if (!metrics) throw new Error('metrics not set');
1537
+ var {
1538
+ lastTop,
1539
+ lastLeft,
1540
+ columnCount,
1541
+ rowCount,
1542
+ scrollableContentWidth,
1543
+ scrollableViewportWidth,
1544
+ scrollableContentHeight,
1545
+ scrollableViewportHeight,
1546
+ hasHorizontalBar,
1547
+ hasVerticalBar
1548
+ } = metrics;
1549
+ var {
1550
+ top,
1551
+ left,
1552
+ topOffset,
1553
+ leftOffset
1554
+ } = metrics;
1555
+ var theme = this.getTheme();
1556
+ var {
1557
+ deltaX,
1558
+ deltaY
1559
+ } = GridUtils.getScrollDelta(event, metrics.barWidth, metrics.barHeight, metrics.rowHeight, metrics.rowHeight);
1560
+
1561
+ // iterate through each column to determine column width and figure out how far to scroll
1562
+ // get column width of next column to scroll to, and subract it from the remaining distance to travel
1563
+ while (hasHorizontalBar && deltaX !== 0) {
1564
+ leftOffset += deltaX;
1565
+ deltaX = 0;
1566
+ if (columnCount > 1) {
1567
+ // no scrolling needed, at directional edge
1568
+ if (leftOffset > 0 && left >= lastLeft || leftOffset < 0 && left <= 0) {
1569
+ leftOffset = 0;
1570
+ break;
1571
+ }
1572
+ } else {
1573
+ // single column at edge
1574
+ if (leftOffset <= 0) {
1575
+ leftOffset = 0;
1576
+ break;
1577
+ }
1578
+ var maxLeftOffset = scrollableContentWidth - scrollableViewportWidth;
1579
+ if (leftOffset >= maxLeftOffset) {
1580
+ leftOffset = maxLeftOffset;
1581
+ break;
1582
+ }
1583
+ }
1584
+ if (leftOffset > 0) {
1585
+ var _metrics$allColumnWid;
1586
+ // scroll right
1587
+
1588
+ // get width of next column
1589
+ var columnWidth = (_metrics$allColumnWid = metrics.allColumnWidths.get(left)) !== null && _metrics$allColumnWid !== void 0 ? _metrics$allColumnWid : metricCalculator.getVisibleColumnWidth(left, metricState);
1590
+ if (leftOffset >= columnWidth) {
1591
+ // remove width from balance and advance by 1 column
1592
+ deltaX = leftOffset - columnWidth;
1593
+ leftOffset = 0;
1594
+ left += 1;
1595
+ } else if (theme.scrollSnapToColumn && columnCount > 1) {
1596
+ // if there's still a balance to travel but its less then a column and snapping is on
1597
+ leftOffset = 0;
1598
+ left += 1;
1599
+ }
1600
+ } else if (leftOffset < 0) {
1601
+ var _metrics$allColumnWid2;
1602
+ // scroll left
1603
+
1604
+ // get width of next column
1605
+ var _columnWidth = (_metrics$allColumnWid2 = metrics.allColumnWidths.get(left - 1)) !== null && _metrics$allColumnWid2 !== void 0 ? _metrics$allColumnWid2 : metricCalculator.getVisibleColumnWidth(left - 1, metricState);
1606
+ if (Math.abs(leftOffset) <= _columnWidth && theme.scrollSnapToColumn && columnCount > 1) {
1607
+ // if there's still a balance to travel but its less then a column and snapping is on
1608
+ leftOffset = 0;
1609
+ left -= 1;
1610
+ } else {
1611
+ // remove width from balance and advance by 1 column
1612
+ deltaX = leftOffset + _columnWidth;
1613
+ leftOffset = 0;
1614
+ left -= 1;
1615
+ }
1616
+ }
1617
+ }
1618
+
1619
+ // iterate through each row to determine row height and figure out how far to scroll
1620
+ // get row height of next row to scroll to, and subract it from the remaining distance to travel
1621
+ while (hasVerticalBar && deltaY !== 0) {
1622
+ topOffset += deltaY;
1623
+ deltaY = 0;
1624
+ if (rowCount > 1) {
1625
+ // no scrolling needed, at directional edge
1626
+ if (topOffset > 0 && top >= lastTop || topOffset < 0 && top <= 0) {
1627
+ topOffset = 0;
1628
+ break;
1629
+ }
1630
+ } else {
1631
+ // single row at edge
1632
+ if (topOffset <= 0) {
1633
+ topOffset = 0;
1634
+ break;
1635
+ }
1636
+ var maxTopOffset = scrollableContentHeight - scrollableViewportHeight;
1637
+ if (topOffset >= maxTopOffset) {
1638
+ topOffset = maxTopOffset;
1639
+ break;
1640
+ }
1641
+ }
1642
+ if (topOffset > 0) {
1643
+ var _metrics$allRowHeight;
1644
+ // scroll direction down
1645
+
1646
+ // get height of next row
1647
+ var rowHeight = (_metrics$allRowHeight = metrics.allRowHeights.get(top)) !== null && _metrics$allRowHeight !== void 0 ? _metrics$allRowHeight : metricCalculator.getVisibleRowHeight(top, metricState);
1648
+ if (topOffset >= rowHeight) {
1649
+ // remove height from balance and advance by 1 row
1650
+ deltaY = topOffset - rowHeight;
1651
+ topOffset = 0;
1652
+ top += 1;
1653
+ } else if (theme.scrollSnapToRow && rowCount > 1) {
1654
+ // if there's still a balance to travel but its less then a row and snapping is on
1655
+ topOffset = 0;
1656
+ top += 1;
1657
+ }
1658
+ } else if (topOffset < 0) {
1659
+ var _metrics$allRowHeight2;
1660
+ // scroll direction up
1661
+
1662
+ // get height of next row
1663
+ var _rowHeight = (_metrics$allRowHeight2 = metrics.allRowHeights.get(top - 1)) !== null && _metrics$allRowHeight2 !== void 0 ? _metrics$allRowHeight2 : metricCalculator.getVisibleRowHeight(top - 1, metricState);
1664
+ if (Math.abs(topOffset) <= _rowHeight && theme.scrollSnapToRow && rowCount > 1) {
1665
+ // if there's still a balance to travel but its less then a row and snapping is on
1666
+ topOffset = 0;
1667
+ top -= 1;
1668
+ } else {
1669
+ // remove height from balance and advance by 1 row
1670
+ deltaY = topOffset + _rowHeight;
1671
+ topOffset = 0;
1672
+ top -= 1;
1673
+ }
1674
+ }
1675
+ }
1676
+ this.setViewState({
1677
+ top,
1678
+ left,
1679
+ leftOffset,
1680
+ topOffset
1681
+ });
1682
+ event.stopPropagation();
1683
+ event.preventDefault();
1684
+ }
1685
+
1686
+ /**
1687
+ * Handle cancelling the cell edit action
1688
+ */
1689
+ handleEditCellCancel() {
1690
+ this.setState({
1691
+ editingCell: null
1692
+ });
1693
+ this.focus();
1694
+ }
1695
+
1696
+ /**
1697
+ * Handle a change in the value in an editing cell
1698
+ * @param value New value set
1699
+ */
1700
+ handleEditCellChange(value) {
1701
+ this.setState(_ref5 => {
1702
+ var {
1703
+ editingCell
1704
+ } = _ref5;
1705
+ try {
1706
+ assertIsDefined(editingCell);
1707
+ return {
1708
+ editingCell: _objectSpread(_objectSpread({}, editingCell), {}, {
1709
+ value
1710
+ })
1711
+ };
1712
+ } catch (e) {
1713
+ // This case should _never_ happen, since the editingCell shouldn't be null if this method is called
1714
+ var {
1715
+ onError
1716
+ } = this.props;
1717
+ onError(e instanceof Error ? e : new Error("".concat(e)));
1718
+ return null;
1719
+ }
1720
+ });
1721
+ }
1722
+
1723
+ /**
1724
+ * Commit an edit for the currently editing cell
1725
+ * @param value Value that was committed
1726
+ * @param options Options for committing
1727
+ */
1728
+ handleEditCellCommit(value) {
1729
+ var {
1730
+ direction = SELECTION_DIRECTION.DOWN,
1731
+ fillRange = false
1732
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1733
+ var {
1734
+ editingCell,
1735
+ selectedRanges
1736
+ } = this.state;
1737
+ if (!editingCell) throw new Error('editingCell not set');
1738
+ var {
1739
+ column,
1740
+ row
1741
+ } = editingCell;
1742
+ if (!this.isValidForCell(column, row, value)) {
1743
+ // Don't allow an invalid value to be commited, the editing cell should show an error
1744
+ if (direction === null) {
1745
+ // If they clicked off of the editing cell, just remove focus
1746
+ this.setState({
1747
+ editingCell: null
1748
+ });
1749
+ }
1750
+ return;
1751
+ }
1752
+ if (fillRange) {
1753
+ this.setValueForRanges(selectedRanges, value);
1754
+ } else {
1755
+ this.setValueForCell(column, row, value);
1756
+ }
1757
+ if (direction !== null) {
1758
+ this.moveCursorInDirection(direction);
1759
+ }
1760
+ this.setState({
1761
+ editingCell: null
1762
+ });
1763
+ this.focus();
1764
+ }
1765
+ renderInputField() {
1766
+ var {
1767
+ model
1768
+ } = this.props;
1769
+ var {
1770
+ editingCell
1771
+ } = this.state;
1772
+ var {
1773
+ metrics
1774
+ } = this;
1775
+ if (editingCell == null || metrics == null || !isEditableGridModel(model)) {
1776
+ return null;
1777
+ }
1778
+ var {
1779
+ selectionRange,
1780
+ value,
1781
+ isQuickEdit
1782
+ } = editingCell;
1783
+ var {
1784
+ column,
1785
+ row
1786
+ } = editingCell;
1787
+ var {
1788
+ gridX,
1789
+ gridY,
1790
+ allColumnXs,
1791
+ allRowYs,
1792
+ allColumnWidths,
1793
+ allRowHeights
1794
+ } = metrics;
1795
+ var x = allColumnXs.get(column);
1796
+ var y = allRowYs.get(row);
1797
+ var w = allColumnWidths.get(column);
1798
+ var h = allRowHeights.get(row);
1799
+
1800
+ // If the cell isn't visible, we still need to display an invisible cell for focus purposes
1801
+ var wrapperStyle = x != null && y != null && w != null && h != null ? {
1802
+ position: 'absolute',
1803
+ left: gridX + x,
1804
+ top: gridY + y,
1805
+ width: w,
1806
+ height: h
1807
+ } : {
1808
+ opacity: 0
1809
+ };
1810
+ var modelColumn = this.getModelColumn(column);
1811
+ var modelRow = this.getModelRow(row);
1812
+ var inputStyle = modelColumn != null && modelRow != null ? {
1813
+ textAlign: model.textAlignForCell(modelColumn, modelRow)
1814
+ } : undefined;
1815
+ var isValid = model.isValidForCell(modelColumn, modelRow, value);
1816
+ return /*#__PURE__*/React.createElement("div", {
1817
+ style: wrapperStyle
1818
+ }, /*#__PURE__*/React.createElement(CellInputField, {
1819
+ key: "".concat(column, ",").concat(row),
1820
+ selectionRange: selectionRange,
1821
+ className: classNames({
1822
+ error: !isValid
1823
+ }),
1824
+ onCancel: this.handleEditCellCancel,
1825
+ onChange: this.handleEditCellChange,
1826
+ onDone: this.handleEditCellCommit,
1827
+ isQuickEdit: isQuickEdit,
1828
+ style: inputStyle,
1829
+ value: value
1830
+ }));
1831
+ }
1832
+
1833
+ /**
1834
+ * Gets the render state
1835
+ * @returns The render state
1836
+ */
1837
+ updateRenderState() {
1838
+ if (!this.canvas) throw new Error('canvas is not set');
1839
+ if (!this.canvasContext) throw new Error('context not set');
1840
+ var {
1841
+ cursorColumn,
1842
+ cursorRow,
1843
+ draggingColumn,
1844
+ draggingColumnSeparator,
1845
+ draggingRow,
1846
+ draggingRowOffset,
1847
+ draggingRowSeparator,
1848
+ editingCell,
1849
+ isDraggingHorizontalScrollBar,
1850
+ isDraggingVerticalScrollBar,
1851
+ isDragging,
1852
+ mouseX,
1853
+ mouseY,
1854
+ selectedRanges
1855
+ } = this.state;
1856
+ var {
1857
+ model,
1858
+ stateOverride
1859
+ } = this.props;
1860
+ var {
1861
+ metrics
1862
+ } = this;
1863
+ var context = this.canvasContext;
1864
+ var theme = this.getTheme();
1865
+ var width = this.canvas.clientWidth;
1866
+ var height = this.canvas.clientHeight;
1867
+ assertNotNull(metrics);
1868
+ this.renderState = _objectSpread({
1869
+ width,
1870
+ height,
1871
+ context,
1872
+ theme,
1873
+ model,
1874
+ metrics,
1875
+ mouseX,
1876
+ mouseY,
1877
+ selectedRanges,
1878
+ draggingColumn,
1879
+ draggingColumnSeparator,
1880
+ draggingRow,
1881
+ draggingRowOffset,
1882
+ draggingRowSeparator,
1883
+ editingCell,
1884
+ isDraggingHorizontalScrollBar,
1885
+ isDraggingVerticalScrollBar,
1886
+ isDragging,
1887
+ cursorColumn,
1888
+ cursorRow
1889
+ }, stateOverride);
1890
+ return this.renderState;
1891
+ }
1892
+ render() {
1893
+ var {
1894
+ cursor
1895
+ } = this.state;
1896
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("canvas", {
1897
+ className: classNames('grid-canvas', Grid.getCursorClassName(cursor)),
1898
+ ref: canvas => {
1899
+ this.canvas = canvas;
1900
+ },
1901
+ onClick: this.handleClick,
1902
+ onContextMenu: this.handleContextMenu,
1903
+ onDoubleClick: this.handleDoubleClick,
1904
+ onKeyDown: this.handleKeyDown,
1905
+ onMouseDown: this.handleMouseDown,
1906
+ onMouseMove: this.handleMouseMove,
1907
+ onMouseLeave: this.handleMouseLeave,
1908
+ tabIndex: 0
1909
+ }, "Your browser does not support HTML canvas. Update your browser?"), this.renderInputField());
1910
+ }
1911
+ }
1912
+ _defineProperty(Grid, "contextType", ThemeContext);
1913
+ _defineProperty(Grid, "defaultProps", {
1914
+ canvasOptions: {
1915
+ alpha: false
1916
+ },
1917
+ isStickyBottom: false,
1918
+ isStickyRight: false,
1919
+ isStuckToBottom: false,
1920
+ isStuckToRight: false,
1921
+ keyHandlers: EMPTY_ARRAY,
1922
+ mouseHandlers: EMPTY_ARRAY,
1923
+ movedColumns: EMPTY_ARRAY,
1924
+ movedRows: EMPTY_ARRAY,
1925
+ onError: () => undefined,
1926
+ onSelectionChanged: () => undefined,
1927
+ onMovedColumnsChanged: moveOperations => undefined,
1928
+ onMoveColumnComplete: () => undefined,
1929
+ onMovedRowsChanged: () => undefined,
1930
+ onMoveRowComplete: () => undefined,
1931
+ onViewChanged: metrics => undefined,
1932
+ onTokenClicked: token => {
1933
+ if (isLinkToken(token)) {
1934
+ window.open(token.href, '_blank', 'noopener,noreferrer');
1935
+ }
1936
+ },
1937
+ stateOverride: {},
1938
+ theme: {
1939
+ autoSelectColumn: false,
1940
+ autoSelectRow: false
1941
+ }
1942
+ });
1943
+ _defineProperty(Grid, "pixelsPerLine", 100 / 3);
1944
+ _defineProperty(Grid, "dragTimeout", 1000);
1945
+ _defineProperty(Grid, "getTheme", memoize((contextTheme, userTheme) => _objectSpread(_objectSpread(_objectSpread({}, GridTheme), contextTheme), userTheme)));
1946
+ export default Grid;
1947
+ //# sourceMappingURL=Grid.js.map