@deephaven/iris-grid 1.28.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 (56) hide show
  1. package/dist/ColumnStatistics.js +1 -1
  2. package/dist/ColumnStatistics.js.map +1 -1
  3. package/dist/IrisGrid.d.ts +28 -4
  4. package/dist/IrisGrid.d.ts.map +1 -1
  5. package/dist/IrisGrid.js +185 -113
  6. package/dist/IrisGrid.js.map +1 -1
  7. package/dist/IrisGridCopyHandler.d.ts +5 -4
  8. package/dist/IrisGridCopyHandler.d.ts.map +1 -1
  9. package/dist/IrisGridCopyHandler.js +36 -29
  10. package/dist/IrisGridCopyHandler.js.map +1 -1
  11. package/dist/IrisGridModel.d.ts +2 -2
  12. package/dist/IrisGridModel.d.ts.map +1 -1
  13. package/dist/IrisGridModel.js +2 -2
  14. package/dist/IrisGridModel.js.map +1 -1
  15. package/dist/IrisGridSelectionUtils.d.ts +26 -0
  16. package/dist/IrisGridSelectionUtils.d.ts.map +1 -0
  17. package/dist/IrisGridSelectionUtils.js +84 -0
  18. package/dist/IrisGridSelectionUtils.js.map +1 -0
  19. package/dist/IrisGridTableModel.d.ts +4 -4
  20. package/dist/IrisGridTableModel.d.ts.map +1 -1
  21. package/dist/IrisGridTableModel.js +8 -8
  22. package/dist/IrisGridTableModel.js.map +1 -1
  23. package/dist/IrisGridTableModelTemplate.d.ts +25 -3
  24. package/dist/IrisGridTableModelTemplate.d.ts.map +1 -1
  25. package/dist/IrisGridTableModelTemplate.js +283 -79
  26. package/dist/IrisGridTableModelTemplate.js.map +1 -1
  27. package/dist/IrisGridTreeTableModel.d.ts +2 -0
  28. package/dist/IrisGridTreeTableModel.d.ts.map +1 -1
  29. package/dist/IrisGridTreeTableModel.js +11 -0
  30. package/dist/IrisGridTreeTableModel.js.map +1 -1
  31. package/dist/KeyedGridModel.d.ts +63 -0
  32. package/dist/KeyedGridModel.d.ts.map +1 -0
  33. package/dist/KeyedGridModel.js +12 -0
  34. package/dist/KeyedGridModel.js.map +1 -0
  35. package/dist/KeyedSelection.d.ts +194 -0
  36. package/dist/KeyedSelection.d.ts.map +1 -0
  37. package/dist/KeyedSelection.js +699 -0
  38. package/dist/KeyedSelection.js.map +1 -0
  39. package/dist/LazyIrisGrid.d.ts +1 -0
  40. package/dist/LazyIrisGrid.d.ts.map +1 -1
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +2 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/key-handlers/CopyKeyHandler.d.ts.map +1 -1
  46. package/dist/key-handlers/CopyKeyHandler.js +8 -6
  47. package/dist/key-handlers/CopyKeyHandler.js.map +1 -1
  48. package/dist/mousehandlers/IrisGridContextMenuHandler.d.ts +1 -1
  49. package/dist/mousehandlers/IrisGridContextMenuHandler.d.ts.map +1 -1
  50. package/dist/mousehandlers/IrisGridContextMenuHandler.js +94 -69
  51. package/dist/mousehandlers/IrisGridContextMenuHandler.js.map +1 -1
  52. package/dist/sidebar/TableCsvExporter.d.ts +7 -4
  53. package/dist/sidebar/TableCsvExporter.d.ts.map +1 -1
  54. package/dist/sidebar/TableCsvExporter.js +86 -38
  55. package/dist/sidebar/TableCsvExporter.js.map +1 -1
  56. package/package.json +9 -9
@@ -0,0 +1,699 @@
1
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
3
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
6
+ import { EMPTY_ARRAY, EMPTY_MAP } from '@deephaven/utils';
7
+ import { computeGestureExtend, cursorLandingCellForRanges, nextCursorInRanges, withCommittedCursor } from '@deephaven/grid';
8
+ /**
9
+ * Serializes key-column values to a stable string for use as a Map/Set key.
10
+ * JSON.stringify encodes NaN, Infinity, and -Infinity as null, so we
11
+ * substitute sentinel strings to preserve their distinct identities.
12
+ */
13
+ export function serializeKeyValues(values) {
14
+ return JSON.stringify(values, (_key, value) => {
15
+ if (typeof value === 'number') {
16
+ if (Number.isNaN(value)) return '__NaN__';
17
+ if (value === Infinity) return '__Infinity__';
18
+ if (value === -Infinity) return '__-Infinity__';
19
+ }
20
+ return value;
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Configuration for a `KeyedSelection`. `getModel` is required; every
26
+ * other field defaults to an "empty" value (`null`, an empty set/map, or
27
+ * `false` where appropriate).
28
+ */
29
+
30
+ /**
31
+ * Immutable `Selection` for keyed tables: identifies rows by their
32
+ * serialized key-column values rather than raw row indices, so the
33
+ * selection survives ticks that shuffle row positions.
34
+ *
35
+ * Rows sharing the same key highlight together (see `isRowSelected`).
36
+ * When `invertedSelection` is true, `selectedKeys` is treated as an
37
+ * exclusion set (all rows selected except those keys).
38
+ */
39
+ export class KeyedSelection {
40
+ static empty(getModel) {
41
+ return new KeyedSelection({
42
+ getModel
43
+ });
44
+ }
45
+ constructor(options) {
46
+ var _options$selectedKeys, _options$overlayRange, _options$invertedSele, _options$selectedKeyV, _options$maxRows, _options$pendingRange, _options$anchorKey, _options$anchorRow, _options$cursorKey, _options$cursorRowHin, _options$cursorColumn, _options$selectionEnd, _options$selectionEnd2, _options$selectionEnd3;
47
+ _defineProperty(this, "getModel", void 0);
48
+ _defineProperty(this, "selectedKeys", void 0);
49
+ _defineProperty(this, "overlayRanges", void 0);
50
+ _defineProperty(this, "invertedSelection", void 0);
51
+ _defineProperty(this, "selectedKeyValues", void 0);
52
+ _defineProperty(this, "maxRows", void 0);
53
+ _defineProperty(this, "pendingRanges", void 0);
54
+ _defineProperty(this, "anchorKey", void 0);
55
+ _defineProperty(this, "anchorRow", void 0);
56
+ _defineProperty(this, "cursorKey", void 0);
57
+ _defineProperty(this, "cursorRowHint", void 0);
58
+ _defineProperty(this, "cursorColumn", void 0);
59
+ _defineProperty(this, "selectionEndKey", void 0);
60
+ _defineProperty(this, "selectionEndRowHint", void 0);
61
+ _defineProperty(this, "selectionEndColumn", void 0);
62
+ /** Keys derived from the current overlay range's viewport-visible rows. */
63
+ _defineProperty(this, "gestureKeys", void 0);
64
+ this.getModel = options.getModel;
65
+ this.selectedKeys = (_options$selectedKeys = options.selectedKeys) !== null && _options$selectedKeys !== void 0 ? _options$selectedKeys : new Set();
66
+ this.overlayRanges = (_options$overlayRange = options.overlayRanges) !== null && _options$overlayRange !== void 0 ? _options$overlayRange : EMPTY_ARRAY;
67
+ this.invertedSelection = (_options$invertedSele = options.invertedSelection) !== null && _options$invertedSele !== void 0 ? _options$invertedSele : false;
68
+ this.selectedKeyValues = (_options$selectedKeyV = options.selectedKeyValues) !== null && _options$selectedKeyV !== void 0 ? _options$selectedKeyV : EMPTY_MAP;
69
+ this.maxRows = (_options$maxRows = options.maxRows) !== null && _options$maxRows !== void 0 ? _options$maxRows : null;
70
+ this.pendingRanges = (_options$pendingRange = options.pendingRanges) !== null && _options$pendingRange !== void 0 ? _options$pendingRange : EMPTY_ARRAY;
71
+ this.anchorKey = (_options$anchorKey = options.anchorKey) !== null && _options$anchorKey !== void 0 ? _options$anchorKey : null;
72
+ this.anchorRow = (_options$anchorRow = options.anchorRow) !== null && _options$anchorRow !== void 0 ? _options$anchorRow : null;
73
+ this.cursorKey = (_options$cursorKey = options.cursorKey) !== null && _options$cursorKey !== void 0 ? _options$cursorKey : null;
74
+ this.cursorRowHint = (_options$cursorRowHin = options.cursorRowHint) !== null && _options$cursorRowHin !== void 0 ? _options$cursorRowHin : null;
75
+ this.cursorColumn = (_options$cursorColumn = options.cursorColumn) !== null && _options$cursorColumn !== void 0 ? _options$cursorColumn : null;
76
+ this.selectionEndKey = (_options$selectionEnd = options.selectionEndKey) !== null && _options$selectionEnd !== void 0 ? _options$selectionEnd : null;
77
+ this.selectionEndRowHint = (_options$selectionEnd2 = options.selectionEndRowHint) !== null && _options$selectionEnd2 !== void 0 ? _options$selectionEnd2 : null;
78
+ this.selectionEndColumn = (_options$selectionEnd3 = options.selectionEndColumn) !== null && _options$selectionEnd3 !== void 0 ? _options$selectionEnd3 : null;
79
+
80
+ // Enumerate only viewport-visible rows so gesture key lookup stays O(1)
81
+ // and construction is O(viewport) regardless of total table size.
82
+ if (this.overlayRanges.length === 0) {
83
+ this.gestureKeys = new Set();
84
+ } else {
85
+ var _model$viewport$top, _model$viewport, _model$viewport$botto, _model$viewport2;
86
+ var model = this.getModel();
87
+ var viewTop = (_model$viewport$top = (_model$viewport = model.viewport) === null || _model$viewport === void 0 ? void 0 : _model$viewport.top) !== null && _model$viewport$top !== void 0 ? _model$viewport$top : 0;
88
+ var viewBottom = (_model$viewport$botto = (_model$viewport2 = model.viewport) === null || _model$viewport2 === void 0 ? void 0 : _model$viewport2.bottom) !== null && _model$viewport$botto !== void 0 ? _model$viewport$botto : 0;
89
+ var keys = new Set();
90
+ for (var i = 0; i < this.overlayRanges.length; i += 1) {
91
+ var _this$overlayRanges$i = this.overlayRanges[i],
92
+ startRow = _this$overlayRanges$i.startRow,
93
+ endRow = _this$overlayRanges$i.endRow;
94
+ if (startRow == null) continue; // eslint-disable-line no-continue
95
+ var last = endRow !== null && endRow !== void 0 ? endRow : startRow;
96
+ var clampedStart = Math.max(startRow, viewTop);
97
+ var clampedEnd = Math.min(last, viewBottom);
98
+ for (var r = clampedStart; r <= clampedEnd; r += 1) {
99
+ var data = this.getRowKeyData(r);
100
+ if (data != null) keys.add(data.key);
101
+ }
102
+ }
103
+ this.gestureKeys = keys;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Returns a copy of this selection with the given fields overridden.
109
+ * Unspecified fields carry through; pass `null` (or an empty
110
+ * set/map/false) to explicitly clear a field.
111
+ */
112
+ copyWith(overrides) {
113
+ return new KeyedSelection(_objectSpread({
114
+ getModel: this.getModel,
115
+ selectedKeys: this.selectedKeys,
116
+ overlayRanges: this.overlayRanges,
117
+ invertedSelection: this.invertedSelection,
118
+ selectedKeyValues: this.selectedKeyValues,
119
+ maxRows: this.maxRows,
120
+ pendingRanges: this.pendingRanges,
121
+ anchorKey: this.anchorKey,
122
+ anchorRow: this.anchorRow,
123
+ cursorKey: this.cursorKey,
124
+ cursorRowHint: this.cursorRowHint,
125
+ cursorColumn: this.cursorColumn,
126
+ selectionEndKey: this.selectionEndKey,
127
+ selectionEndRowHint: this.selectionEndRowHint,
128
+ selectionEndColumn: this.selectionEndColumn
129
+ }, overrides));
130
+ }
131
+
132
+ /**
133
+ * Returns both the serialized key and the raw values for a visible row, or
134
+ * `null` when the row is not keyable (e.g. totals / aggregation floating
135
+ * rows whose column values are aggregates, not real row keys).
136
+ */
137
+ getRowKeyData(row) {
138
+ var model = this.getModel();
139
+ if (model.isKeyableRow(row) !== true) return null;
140
+ var values = model.selectionKeyColumnIndices.map(col => model.valueForCell(col, row));
141
+ return {
142
+ key: serializeKeyValues(values),
143
+ values
144
+ };
145
+ }
146
+ isEmpty() {
147
+ // Inverted selection means all rows are selected — never empty.
148
+ if (this.invertedSelection) return false;
149
+ // Pending resolution means a selection is in progress — not empty.
150
+ if (this.pendingRanges.length > 0) return false;
151
+ return this.selectedKeys.size === 0 && this.gestureKeys.size === 0;
152
+ }
153
+ describe() {
154
+ if (this.pendingRanges.length > 0) return 'Selection loading.';
155
+ // Non-unique key columns can map one key to many rows; report the
156
+ // truthful count (keys) rather than lying about row counts.
157
+ var unit = this.getModel().hasUniqueSelectionKeys ? 'row' : 'key';
158
+ var n = this.selectedKeys.size;
159
+ if (this.invertedSelection) {
160
+ if (n === 0) return 'Everything selected.';
161
+ return unit === 'row' ? "All rows except ".concat(n, " selected.") : "All rows except ".concat(n, " ").concat(n === 1 ? 'key' : 'keys', " selected.");
162
+ }
163
+ if (n === 0) return 'No selection.';
164
+ if (n === 1) return unit === 'row' ? '1 row selected.' : '1 key selected.';
165
+ return unit === 'row' ? "".concat(n, " rows selected.") : "".concat(n, " keys selected.");
166
+ }
167
+
168
+ // Keyed selection is always full-row; column is irrelevant
169
+ isCellSelected(_column, row) {
170
+ return this.isRowSelected(row);
171
+ }
172
+ isRowSelected(row) {
173
+ var data = this.getRowKeyData(row);
174
+ if (data == null) return false;
175
+ var key = data.key;
176
+ if (this.invertedSelection) return !this.selectedKeys.has(key);
177
+ // Include gesture preview keys so key-siblings highlight on mousedown without waiting for commit.
178
+ return this.selectedKeys.has(key) || this.gestureKeys.has(key);
179
+ }
180
+
181
+ // Only fully selected when the inversion covers every row (i.e. select-all).
182
+ isColumnSelected(_column) {
183
+ return this.invertedSelection && this.selectedKeys.size === 0;
184
+ }
185
+ getNextCursorInDirection(current, direction) {
186
+ var _this$getModel = this.getModel(),
187
+ columnCount = _this$getModel.columnCount,
188
+ rowCount = _this$getModel.rowCount;
189
+ return nextCursorInRanges(this.overlayRanges, current, direction, {
190
+ columnCount,
191
+ rowCount
192
+ });
193
+ }
194
+ getCursorLandingCell() {
195
+ var _this$getModel2 = this.getModel(),
196
+ columnCount = _this$getModel2.columnCount,
197
+ rowCount = _this$getModel2.rowCount;
198
+ return cursorLandingCellForRanges(this.overlayRanges, {
199
+ columnCount,
200
+ rowCount
201
+ });
202
+ }
203
+
204
+ // Cursor / endpoint tracking. Rows are stored as `(key, rowHint)` pairs so
205
+ // ticks that shuffle row indices resolve to the live row via
206
+ // `findKeyInViewport`; columns don't drift and are kept as raw indices.
207
+ withCursor(row, column) {
208
+ var _this$getRowKeyData$k, _this$getRowKeyData;
209
+ if (row === this.cursorRow && column === this.cursorColumn) return this;
210
+ var nextKey = row != null ? (_this$getRowKeyData$k = (_this$getRowKeyData = this.getRowKeyData(row)) === null || _this$getRowKeyData === void 0 ? void 0 : _this$getRowKeyData.key) !== null && _this$getRowKeyData$k !== void 0 ? _this$getRowKeyData$k : null : null;
211
+ return this.copyWith({
212
+ cursorKey: nextKey,
213
+ cursorRowHint: row,
214
+ cursorColumn: column
215
+ });
216
+ }
217
+ withSelectionEnd(row, column) {
218
+ var _this$getRowKeyData$k2, _this$getRowKeyData2;
219
+ if (row === this.selectionEndRow && column === this.selectionEndColumn) {
220
+ return this;
221
+ }
222
+ var nextKey = row != null ? (_this$getRowKeyData$k2 = (_this$getRowKeyData2 = this.getRowKeyData(row)) === null || _this$getRowKeyData2 === void 0 ? void 0 : _this$getRowKeyData2.key) !== null && _this$getRowKeyData$k2 !== void 0 ? _this$getRowKeyData$k2 : null : null;
223
+ return this.copyWith({
224
+ selectionEndKey: nextKey,
225
+ selectionEndRowHint: row,
226
+ selectionEndColumn: column
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Replaces the transient overlay ranges (mid-gesture preview) with the
232
+ * given ranges. Preserves the gesture anchor so mid-drag range updates
233
+ * don't clobber the shift-click origin.
234
+ *
235
+ * When `isReplacing` is true the caller intends the overlay to replace
236
+ * the current committed selection (drag / shift+click); previously
237
+ * committed keys are dropped so the commit uses the overlay as a fresh
238
+ * selection. Otherwise the committed keys are kept, letting a subsequent
239
+ * `commitGesture` fold the overlay into the existing set.
240
+ */
241
+ withMouseGestureRanges(ranges) {
242
+ var isReplacing = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
243
+ if (isReplacing) {
244
+ return this.copyWith({
245
+ selectedKeys: new Set(),
246
+ overlayRanges: ranges,
247
+ invertedSelection: false,
248
+ selectedKeyValues: EMPTY_MAP,
249
+ pendingRanges: EMPTY_ARRAY
250
+ });
251
+ }
252
+ return this.copyWith({
253
+ overlayRanges: ranges,
254
+ pendingRanges: EMPTY_ARRAY
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Sets the gesture anchor (extend-from position for shift-click and
260
+ * keyboard extend) to the row's key. Passing `null` clears the anchor.
261
+ */
262
+ withGestureAnchor(row) {
263
+ var _this$getRowKeyData$k3, _this$getRowKeyData3;
264
+ var nextKey = row != null ? (_this$getRowKeyData$k3 = (_this$getRowKeyData3 = this.getRowKeyData(row)) === null || _this$getRowKeyData3 === void 0 ? void 0 : _this$getRowKeyData3.key) !== null && _this$getRowKeyData$k3 !== void 0 ? _this$getRowKeyData$k3 : null : null;
265
+ if (nextKey === this.anchorKey && row === this.anchorRow) return this;
266
+ return this.copyWith({
267
+ anchorKey: nextKey,
268
+ anchorRow: row
269
+ });
270
+ }
271
+
272
+ /**
273
+ * The current `{row, column}` of the gesture anchor, or `null` if none is
274
+ * set. Because `KeyedSelection` tracks the anchor by serialized key, the
275
+ * anchor's visible row may drift as the viewport scrolls. Resolves the
276
+ * key against the current viewport when possible; falls back to the
277
+ * click-time row hint (`anchorRow`) when the key has scrolled out. Returns
278
+ * `null` only when neither is set.
279
+ */
280
+ getGestureAnchor() {
281
+ if (this.anchorKey == null && this.anchorRow == null) return null;
282
+ if (this.anchorKey != null) {
283
+ var viewportRow = this.findKeyInViewport(this.anchorKey, this.anchorRow);
284
+ if (viewportRow != null) return {
285
+ row: viewportRow,
286
+ column: null
287
+ };
288
+ }
289
+ // Anchor key is not in the viewport; fall back to the click-time row hint.
290
+ return {
291
+ row: this.anchorRow,
292
+ column: null
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Returns the visible row of `key`, or `null` if it's not in the viewport.
298
+ * When multiple rows share the key (non-unique key columns), prefers the one
299
+ * closest to `hint` so we track the row the caller last saw the key at.
300
+ */
301
+ findKeyInViewport(key, hint) {
302
+ var _model$viewport$top2, _model$viewport3, _model$viewport$botto2, _model$viewport4;
303
+ var model = this.getModel();
304
+ var viewTop = (_model$viewport$top2 = (_model$viewport3 = model.viewport) === null || _model$viewport3 === void 0 ? void 0 : _model$viewport3.top) !== null && _model$viewport$top2 !== void 0 ? _model$viewport$top2 : 0;
305
+ var viewBottom = (_model$viewport$botto2 = (_model$viewport4 = model.viewport) === null || _model$viewport4 === void 0 ? void 0 : _model$viewport4.bottom) !== null && _model$viewport$botto2 !== void 0 ? _model$viewport$botto2 : 0;
306
+ var best = null;
307
+ var bestDist = Infinity;
308
+ for (var r = viewTop; r <= viewBottom; r += 1) {
309
+ var data = this.getRowKeyData(r);
310
+ if (data == null || data.key !== key) continue; // eslint-disable-line no-continue
311
+ if (hint == null) return r;
312
+ var dist = Math.abs(r - hint);
313
+ if (dist < bestDist) {
314
+ best = r;
315
+ bestDist = dist;
316
+ }
317
+ }
318
+ return best;
319
+ }
320
+ get cursorRow() {
321
+ var _this$findKeyInViewpo;
322
+ if (this.cursorKey == null) return this.cursorRowHint;
323
+ return (_this$findKeyInViewpo = this.findKeyInViewport(this.cursorKey, this.cursorRowHint)) !== null && _this$findKeyInViewpo !== void 0 ? _this$findKeyInViewpo : this.cursorRowHint;
324
+ }
325
+ get selectionEndRow() {
326
+ var _this$findKeyInViewpo2;
327
+ if (this.selectionEndKey == null) return this.selectionEndRowHint;
328
+ return (_this$findKeyInViewpo2 = this.findKeyInViewport(this.selectionEndKey, this.selectionEndRowHint)) !== null && _this$findKeyInViewpo2 !== void 0 ? _this$findKeyInViewpo2 : this.selectionEndRowHint;
329
+ }
330
+ withGestureExtend(cursor, opts) {
331
+ var _computeGestureExtend = computeGestureExtend(this.overlayRanges, this.getGestureAnchor(), cursor, opts),
332
+ newRanges = _computeGestureExtend.newRanges,
333
+ isReplacing = _computeGestureExtend.isReplacing,
334
+ trimBefore = _computeGestureExtend.trimBefore,
335
+ resetAnchor = _computeGestureExtend.resetAnchor;
336
+ var base = trimBefore ? this.trimmed() : this;
337
+ var result = base.withMouseGestureRanges(newRanges, isReplacing);
338
+ if (resetAnchor) {
339
+ // Keyed selection is row based, so column is not used in the anchor.
340
+ result = result.withGestureAnchor(cursor.row);
341
+ }
342
+ return result;
343
+ }
344
+ commitGesture(lastCommitted, opts) {
345
+ if (this.overlayRanges.length === 0) return this;
346
+ if (opts.settle === false) {
347
+ // Mouse-down: keep overlayRanges pending. The overlay-to-committed
348
+ // merge (add / toggle-off / consolidation) runs on mouse-up so a drag
349
+ // can grow the overlay without prematurely folding it into selectedKeys.
350
+ return withCommittedCursor(this, this.getCursorLandingCell(), opts);
351
+ }
352
+
353
+ // Scan ranges for endpoints and total row count
354
+ var first = null;
355
+ var lastRow = 0;
356
+ var rowCount = 0;
357
+ for (var i = 0; i < this.overlayRanges.length; i += 1) {
358
+ var _this$overlayRanges$i2 = this.overlayRanges[i],
359
+ startRow = _this$overlayRanges$i2.startRow,
360
+ endRow = _this$overlayRanges$i2.endRow;
361
+ if (startRow == null) continue; // eslint-disable-line no-continue
362
+ var rEnd = endRow !== null && endRow !== void 0 ? endRow : startRow;
363
+ if (first === null) first = startRow;
364
+ lastRow = rEnd;
365
+ rowCount += rEnd - startRow + 1;
366
+ }
367
+ if (first === null || rowCount === 0) return this;
368
+ var next = new Set(this.selectedKeys);
369
+ if (this.selectedKeys.size > 0 || this.invertedSelection) {
370
+ // Ctrl+click / ctrl+drag path: clearSelectedRanges was not called, so
371
+ // selectedKeys still holds the previous committed keys. Toggle off when
372
+ // every row in the overlay was already committed (single-row ctrl+click
373
+ // on a selected row, or ctrl+drag over an all-selected range); otherwise
374
+ // add all rows to the selection without unioning them off.
375
+ var shouldToggle = true;
376
+ for (var _i = 0; _i < this.overlayRanges.length && shouldToggle; _i += 1) {
377
+ var _this$overlayRanges$_ = this.overlayRanges[_i],
378
+ _startRow = _this$overlayRanges$_.startRow,
379
+ _endRow = _this$overlayRanges$_.endRow;
380
+ if (_startRow == null) continue; // eslint-disable-line no-continue
381
+ var _rEnd = _endRow !== null && _endRow !== void 0 ? _endRow : _startRow;
382
+ for (var r = _startRow; r <= _rEnd; r += 1) {
383
+ if (!lastCommitted.isRowSelected(r)) {
384
+ shouldToggle = false;
385
+ break;
386
+ }
387
+ }
388
+ }
389
+
390
+ // Multi-row ctrl+shift add that spans out-of-viewport should defer
391
+ // to async resolution while preserving the already-committed keys.
392
+ if (!shouldToggle && !this.invertedSelection) {
393
+ var _model$viewport$top3, _model$viewport5, _model$viewport$botto3, _model$viewport6;
394
+ var model = this.getModel();
395
+ var viewTop = (_model$viewport$top3 = (_model$viewport5 = model.viewport) === null || _model$viewport5 === void 0 ? void 0 : _model$viewport5.top) !== null && _model$viewport$top3 !== void 0 ? _model$viewport$top3 : 0;
396
+ var viewBottom = (_model$viewport$botto3 = (_model$viewport6 = model.viewport) === null || _model$viewport6 === void 0 ? void 0 : _model$viewport6.bottom) !== null && _model$viewport$botto3 !== void 0 ? _model$viewport$botto3 : 0;
397
+ if (first < viewTop || lastRow > viewBottom) {
398
+ return withCommittedCursor(this.copyWith({
399
+ overlayRanges: EMPTY_ARRAY,
400
+ pendingRanges: this.overlayRanges
401
+ }), this.getCursorLandingCell(), opts);
402
+ }
403
+ }
404
+ var _nextKeyValues = new Map(this.selectedKeyValues);
405
+ for (var _i2 = 0; _i2 < this.overlayRanges.length; _i2 += 1) {
406
+ var _this$overlayRanges$_2 = this.overlayRanges[_i2],
407
+ _startRow2 = _this$overlayRanges$_2.startRow,
408
+ _endRow2 = _this$overlayRanges$_2.endRow;
409
+ if (_startRow2 == null) continue; // eslint-disable-line no-continue
410
+ var _rEnd2 = _endRow2 !== null && _endRow2 !== void 0 ? _endRow2 : _startRow2;
411
+ for (var _r = _startRow2; _r <= _rEnd2; _r += 1) {
412
+ var _data = this.getRowKeyData(_r);
413
+ if (_data == null) continue; // eslint-disable-line no-continue
414
+ var _k = _data.key,
415
+ _values = _data.values;
416
+ if (shouldToggle) {
417
+ // Toggle off: every overlay row was already in lastCommitted, so
418
+ // ctrl+click / ctrl+drag flips them off.
419
+ if (this.invertedSelection) {
420
+ next.add(_k);
421
+ _nextKeyValues.set(_k, _values);
422
+ } else {
423
+ next.delete(_k);
424
+ _nextKeyValues.delete(_k);
425
+ }
426
+ } else if (this.invertedSelection) {
427
+ next.delete(_k);
428
+ _nextKeyValues.delete(_k);
429
+ } else {
430
+ next.add(_k);
431
+ _nextKeyValues.set(_k, _values);
432
+ }
433
+ }
434
+ }
435
+ return withCommittedCursor(this.copyWith({
436
+ selectedKeys: next,
437
+ overlayRanges: EMPTY_ARRAY,
438
+ selectedKeyValues: _nextKeyValues,
439
+ pendingRanges: EMPTY_ARRAY
440
+ }), this.getCursorLandingCell(), opts);
441
+ }
442
+
443
+ // Regular click path: clearSelectedRanges emptied selectedKeys first.
444
+ // Multi-row shift selections may span out-of-viewport rows where valueForCell
445
+ // returns null. Defer those to async resolution in IrisGrid.
446
+ // Assumes shift+click/drag emits one contiguous overlay range; `first`/`lastRow`
447
+ // are that range's endpoints and drive the anchor/target endpoint capture below.
448
+ if (rowCount > 1) {
449
+ var _model$viewport$top4, _model$viewport7, _model$viewport$botto4, _model$viewport8;
450
+ var _model = this.getModel();
451
+ var _viewTop = (_model$viewport$top4 = (_model$viewport7 = _model.viewport) === null || _model$viewport7 === void 0 ? void 0 : _model$viewport7.top) !== null && _model$viewport$top4 !== void 0 ? _model$viewport$top4 : 0;
452
+ var _viewBottom = (_model$viewport$botto4 = (_model$viewport8 = _model.viewport) === null || _model$viewport8 === void 0 ? void 0 : _model$viewport8.bottom) !== null && _model$viewport$botto4 !== void 0 ? _model$viewport$botto4 : 0;
453
+
454
+ // Fast path: entire range is in the viewport, so we can enumerate keys
455
+ // synchronously and skip the async fetch race entirely.
456
+ if (first >= _viewTop && lastRow <= _viewBottom) {
457
+ var _nextKeyValues2 = new Map();
458
+ for (var _r2 = first; _r2 <= lastRow; _r2 += 1) {
459
+ var _data2 = this.getRowKeyData(_r2);
460
+ if (_data2 == null) continue; // eslint-disable-line no-continue
461
+ _nextKeyValues2.set(_data2.key, _data2.values);
462
+ }
463
+ return withCommittedCursor(this.copyWith({
464
+ selectedKeys: new Set(_nextKeyValues2.keys()),
465
+ overlayRanges: EMPTY_ARRAY,
466
+ invertedSelection: false,
467
+ selectedKeyValues: _nextKeyValues2,
468
+ pendingRanges: EMPTY_ARRAY
469
+ }), this.getCursorLandingCell(), opts);
470
+ }
471
+
472
+ // Slow path: same async-fetch mechanism as `withCommittedRanges`.
473
+ // `IrisGrid.resolveKeyedSelection` fetches keys for the overlay ranges;
474
+ // rows that scroll away during the fetch are dropped from the result.
475
+ return withCommittedCursor(this.copyWith({
476
+ selectedKeys: new Set(),
477
+ invertedSelection: false,
478
+ selectedKeyValues: EMPTY_MAP,
479
+ pendingRanges: this.overlayRanges
480
+ }), this.getCursorLandingCell(), opts);
481
+ }
482
+
483
+ // Single-row path (rowCount === 1): first === lastRow.
484
+ var row = first;
485
+ var data = this.getRowKeyData(row);
486
+ if (data == null) {
487
+ // Row is not keyable (totals / aggregation row); commit clears the
488
+ // overlay without touching the keyed set.
489
+ return withCommittedCursor(this.copyWith({
490
+ overlayRanges: EMPTY_ARRAY
491
+ }), this.getCursorLandingCell(), opts);
492
+ }
493
+ var k = data.key,
494
+ values = data.values;
495
+ var nextKeyValues = new Map(this.selectedKeyValues);
496
+ // Deselect only when the clicked row was the entire previous committed selection.
497
+ var wasEntireSelection = opts.allowDeselect !== false && lastCommitted instanceof KeyedSelection && !lastCommitted.invertedSelection && lastCommitted.selectedKeys.size === 1 && lastCommitted.selectedKeys.has(k);
498
+ if (wasEntireSelection) {
499
+ next.delete(k);
500
+ nextKeyValues.delete(k);
501
+ } else {
502
+ next.add(k);
503
+ nextKeyValues.set(k, values);
504
+ }
505
+ return withCommittedCursor(this.copyWith({
506
+ selectedKeys: next,
507
+ overlayRanges: EMPTY_ARRAY,
508
+ invertedSelection: false,
509
+ selectedKeyValues: nextKeyValues,
510
+ pendingRanges: EMPTY_ARRAY
511
+ }), this.getCursorLandingCell(), opts);
512
+ }
513
+ clear() {
514
+ // Fresh empty carrying cursor forward (Escape semantics). Endpoint is
515
+ // transient gesture state and drops as part of the reset.
516
+ return KeyedSelection.empty(this.getModel).copyWith({
517
+ cursorKey: this.cursorKey,
518
+ cursorRowHint: this.cursorRowHint,
519
+ cursorColumn: this.cursorColumn
520
+ });
521
+ }
522
+
523
+ // Shift+click needs a clean slate so the range replaces rather than extends the old keys.
524
+ // Anchor is preserved so shift+click's extend reads it after the trim.
525
+ trimmed() {
526
+ return this.copyWith({
527
+ selectedKeys: new Set(),
528
+ overlayRanges: EMPTY_ARRAY,
529
+ invertedSelection: false,
530
+ selectedKeyValues: EMPTY_MAP,
531
+ maxRows: null,
532
+ pendingRanges: EMPTY_ARRAY
533
+ });
534
+ }
535
+
536
+ // Always returns non-inverted; switching to a new selection exits inverted mode.
537
+ withCommittedRanges(ranges, anchor) {
538
+ var result = this.installCommittedRanges(ranges);
539
+ if (anchor === undefined) return result;
540
+ // Keyed selection is row based, so column is not used in the anchor.
541
+ return result.withGestureAnchor(anchor.row);
542
+ }
543
+ installCommittedRanges(ranges) {
544
+ var _model$viewport$top5, _model$viewport9, _model$viewport$botto5, _model$viewport0;
545
+ // Fields threaded through every fresh install so cursor / endpoint state
546
+ // survive programmatic range replacement (matches Escape semantics).
547
+ var carry = {
548
+ cursorKey: this.cursorKey,
549
+ cursorRowHint: this.cursorRowHint,
550
+ cursorColumn: this.cursorColumn,
551
+ selectionEndKey: this.selectionEndKey,
552
+ selectionEndRowHint: this.selectionEndRowHint,
553
+ selectionEndColumn: this.selectionEndColumn
554
+ };
555
+ // Replacement semantics: discard previous selection and select exactly these rows.
556
+ if (ranges.length === 0) {
557
+ return new KeyedSelection(_objectSpread({
558
+ getModel: this.getModel
559
+ }, carry));
560
+ }
561
+
562
+ // Compute the true min/max across every range so the viewport check is
563
+ // correct even when input ranges arrive out of order or non-contiguous
564
+ // (e.g. plugin-driven programmatic selection).
565
+ var minRow = null;
566
+ var maxRow = null;
567
+ for (var i = 0; i < ranges.length; i += 1) {
568
+ var _ranges$i = ranges[i],
569
+ startRow = _ranges$i.startRow,
570
+ endRow = _ranges$i.endRow;
571
+ if (startRow == null) continue; // eslint-disable-line no-continue
572
+ var rEnd = endRow !== null && endRow !== void 0 ? endRow : startRow;
573
+ var low = Math.min(startRow, rEnd);
574
+ var high = Math.max(startRow, rEnd);
575
+ if (minRow === null || low < minRow) minRow = low;
576
+ if (maxRow === null || high > maxRow) maxRow = high;
577
+ }
578
+ if (minRow === null || maxRow === null) {
579
+ return new KeyedSelection(_objectSpread({
580
+ getModel: this.getModel
581
+ }, carry));
582
+ }
583
+ var model = this.getModel();
584
+ var viewTop = (_model$viewport$top5 = (_model$viewport9 = model.viewport) === null || _model$viewport9 === void 0 ? void 0 : _model$viewport9.top) !== null && _model$viewport$top5 !== void 0 ? _model$viewport$top5 : 0;
585
+ var viewBottom = (_model$viewport$botto5 = (_model$viewport0 = model.viewport) === null || _model$viewport0 === void 0 ? void 0 : _model$viewport0.bottom) !== null && _model$viewport$botto5 !== void 0 ? _model$viewport$botto5 : 0;
586
+
587
+ // Fast path: every range fits in the viewport, so valueForCell answers
588
+ // synchronously with real values.
589
+ if (minRow >= viewTop && maxRow <= viewBottom) {
590
+ var next = new Set();
591
+ var nextKeyValues = new Map();
592
+ for (var _i3 = 0; _i3 < ranges.length; _i3 += 1) {
593
+ var _ranges$_i = ranges[_i3],
594
+ _startRow3 = _ranges$_i.startRow,
595
+ _endRow3 = _ranges$_i.endRow;
596
+ if (_startRow3 == null) continue; // eslint-disable-line no-continue
597
+ var _rEnd3 = _endRow3 !== null && _endRow3 !== void 0 ? _endRow3 : _startRow3;
598
+ var _low = Math.min(_startRow3, _rEnd3);
599
+ var _high = Math.max(_startRow3, _rEnd3);
600
+ for (var r = _low; r <= _high; r += 1) {
601
+ var data = this.getRowKeyData(r);
602
+ if (data == null) continue; // eslint-disable-line no-continue
603
+ next.add(data.key);
604
+ nextKeyValues.set(data.key, data.values);
605
+ }
606
+ }
607
+ return new KeyedSelection(_objectSpread({
608
+ getModel: this.getModel,
609
+ selectedKeys: next,
610
+ selectedKeyValues: nextKeyValues
611
+ }, carry));
612
+ }
613
+
614
+ // Slow path: any row outside the viewport would resolve to a phantom
615
+ // all-null key via undefined valueForCell reads. Defer to async key fetch;
616
+ // IrisGrid's onSelectionChange handler picks up pendingRanges.
617
+ return new KeyedSelection(_objectSpread({
618
+ getModel: this.getModel,
619
+ pendingRanges: ranges
620
+ }, carry));
621
+ }
622
+
623
+ // Sets invertedSelection=true with an empty exclusion set (all rows selected).
624
+ selectAll() {
625
+ // Fresh inverted-all carrying cursor forward.
626
+ return KeyedSelection.empty(this.getModel).copyWith({
627
+ invertedSelection: true,
628
+ cursorKey: this.cursorKey,
629
+ cursorRowHint: this.cursorRowHint,
630
+ cursorColumn: this.cursorColumn
631
+ });
632
+ }
633
+ truncate(maxRows) {
634
+ if (maxRows === this.maxRows) return this;
635
+ return this.copyWith({
636
+ maxRows
637
+ });
638
+ }
639
+
640
+ /**
641
+ * Builds a fully-resolved selection from async-fetched key values merged with the current selection, clearing pendingRanges.
642
+ */
643
+ resolve(keyValues) {
644
+ var mergedKeys = new Set(this.selectedKeys);
645
+ var mergedKeyValues = new Map(this.selectedKeyValues);
646
+ keyValues.forEach((values, key) => {
647
+ mergedKeys.add(key);
648
+ mergedKeyValues.set(key, values);
649
+ });
650
+ return this.copyWith({
651
+ selectedKeys: mergedKeys,
652
+ overlayRanges: EMPTY_ARRAY,
653
+ invertedSelection: false,
654
+ selectedKeyValues: mergedKeyValues,
655
+ maxRows: null,
656
+ pendingRanges: EMPTY_ARRAY
657
+ });
658
+ }
659
+
660
+ /**
661
+ * Returns the exact committed row count when each key maps to one row,
662
+ * or null when the count is unknown (non-unique keys or pending resolution).
663
+ * For inverted selections the count is approximate on ticking tables.
664
+ */
665
+ getUniqueRowCount() {
666
+ if (this.pendingRanges.length > 0 || !this.getModel().hasUniqueSelectionKeys) {
667
+ return null;
668
+ }
669
+ if (this.invertedSelection) {
670
+ return this.getModel().rowCount - this.selectedKeys.size;
671
+ }
672
+ return this.selectedKeys.size;
673
+ }
674
+
675
+ /** Returns a new selection with the given row's key toggled. */
676
+ withToggledRow(row) {
677
+ var data = this.getRowKeyData(row);
678
+ if (data == null) return this;
679
+ var key = data.key,
680
+ values = data.values;
681
+ var next = new Set(this.selectedKeys);
682
+ var nextKeyValues = new Map(this.selectedKeyValues);
683
+ if (next.has(key)) {
684
+ next.delete(key);
685
+ nextKeyValues.delete(key);
686
+ } else {
687
+ next.add(key);
688
+ nextKeyValues.set(key, values);
689
+ }
690
+ return this.copyWith({
691
+ selectedKeys: next,
692
+ overlayRanges: EMPTY_ARRAY,
693
+ selectedKeyValues: nextKeyValues,
694
+ pendingRanges: EMPTY_ARRAY
695
+ });
696
+ }
697
+ }
698
+ export default KeyedSelection;
699
+ //# sourceMappingURL=KeyedSelection.js.map