@perspective-dev/viewer-datagrid 4.4.0 → 4.5.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 (87) hide show
  1. package/dist/cdn/perspective-viewer-datagrid.js +4 -22
  2. package/dist/cdn/perspective-viewer-datagrid.js.map +4 -4
  3. package/dist/css/perspective-viewer-datagrid-toolbar.css +1 -1
  4. package/dist/css/perspective-viewer-datagrid.css +1 -1
  5. package/dist/esm/color_utils.d.ts +22 -0
  6. package/dist/esm/custom_elements/datagrid.d.ts +16 -21
  7. package/dist/esm/data_listener/format_cell.d.ts +1 -1
  8. package/dist/esm/data_listener/formatter_cache.d.ts +1 -1
  9. package/dist/esm/data_listener/index.d.ts +3 -2
  10. package/dist/esm/event_handlers/click/edit_click.d.ts +3 -2
  11. package/dist/esm/event_handlers/click.d.ts +4 -6
  12. package/dist/esm/event_handlers/dispatch_click.d.ts +3 -2
  13. package/dist/esm/event_handlers/expand_collapse.d.ts +1 -1
  14. package/dist/esm/event_handlers/focus.d.ts +4 -5
  15. package/dist/esm/event_handlers/header_click.d.ts +5 -3
  16. package/dist/esm/event_handlers/keydown/edit_keydown.d.ts +3 -4
  17. package/dist/esm/event_handlers/select_region.d.ts +3 -1
  18. package/dist/esm/event_handlers/sort.d.ts +8 -7
  19. package/dist/esm/model/create.d.ts +1 -1
  20. package/dist/esm/model/meta_columns.d.ts +1 -0
  21. package/dist/esm/perspective-viewer-datagrid.js +3 -3
  22. package/dist/esm/perspective-viewer-datagrid.js.map +4 -4
  23. package/dist/esm/plugin/activate.d.ts +1 -1
  24. package/dist/esm/plugin/column_config_schema.d.ts +31 -0
  25. package/dist/esm/style_handlers/body.d.ts +3 -3
  26. package/dist/esm/style_handlers/column_header.d.ts +4 -3
  27. package/dist/esm/style_handlers/consolidated.d.ts +3 -47
  28. package/dist/esm/style_handlers/editable.d.ts +3 -2
  29. package/dist/esm/style_handlers/focus.d.ts +4 -4
  30. package/dist/esm/style_handlers/group_header.d.ts +1 -1
  31. package/dist/esm/style_handlers/table_cell/boolean.d.ts +1 -1
  32. package/dist/esm/style_handlers/table_cell/cell_flash.d.ts +1 -1
  33. package/dist/esm/style_handlers/table_cell/datetime.d.ts +1 -1
  34. package/dist/esm/style_handlers/table_cell/numeric.d.ts +1 -1
  35. package/dist/esm/style_handlers/table_cell/row_header.d.ts +1 -1
  36. package/dist/esm/style_handlers/table_cell/string.d.ts +1 -1
  37. package/dist/esm/style_handlers/types.d.ts +0 -4
  38. package/dist/esm/types.d.ts +10 -17
  39. package/package.json +2 -4
  40. package/src/css/regular_table.css +87 -31
  41. package/src/css/row-hover.css +20 -7
  42. package/src/css/toolbar.css +11 -0
  43. package/src/ts/color_utils.ts +181 -16
  44. package/src/ts/custom_elements/datagrid.ts +70 -56
  45. package/src/ts/custom_elements/toolbar.ts +4 -5
  46. package/src/ts/data_listener/format_cell.ts +28 -9
  47. package/src/ts/data_listener/format_tree_header.ts +2 -2
  48. package/src/ts/data_listener/formatter_cache.ts +9 -96
  49. package/src/ts/data_listener/index.ts +13 -11
  50. package/src/ts/event_handlers/click/edit_click.ts +10 -6
  51. package/src/ts/event_handlers/click.ts +39 -68
  52. package/src/ts/event_handlers/dispatch_click.ts +27 -25
  53. package/src/ts/event_handlers/expand_collapse.ts +11 -8
  54. package/src/ts/event_handlers/focus.ts +38 -35
  55. package/src/ts/event_handlers/header_click.ts +107 -62
  56. package/src/ts/event_handlers/keydown/edit_keydown.ts +60 -54
  57. package/src/ts/event_handlers/select_region.ts +153 -131
  58. package/src/ts/event_handlers/sort.ts +20 -25
  59. package/src/ts/get_cell_config.ts +10 -3
  60. package/src/ts/model/column_overrides.ts +16 -9
  61. package/src/ts/model/create.ts +68 -55
  62. package/src/ts/{event_handlers/deselect_all.ts → model/meta_columns.ts} +33 -14
  63. package/src/ts/model/toolbar.ts +33 -8
  64. package/src/ts/plugin/activate.ts +122 -92
  65. package/src/ts/plugin/column_config_schema.ts +187 -0
  66. package/src/ts/plugin/draw.ts +1 -0
  67. package/src/ts/plugin/restore.ts +6 -2
  68. package/src/ts/plugin/save.ts +2 -5
  69. package/src/ts/style_handlers/body.ts +48 -51
  70. package/src/ts/style_handlers/column_header.ts +22 -21
  71. package/src/ts/style_handlers/consolidated.ts +23 -123
  72. package/src/ts/style_handlers/editable.ts +16 -10
  73. package/src/ts/style_handlers/focus.ts +7 -5
  74. package/src/ts/style_handlers/group_header.ts +13 -6
  75. package/src/ts/style_handlers/table_cell/boolean.ts +3 -3
  76. package/src/ts/style_handlers/table_cell/cell_flash.ts +11 -11
  77. package/src/ts/style_handlers/table_cell/datetime.ts +3 -3
  78. package/src/ts/style_handlers/table_cell/numeric.ts +24 -25
  79. package/src/ts/style_handlers/table_cell/row_header.ts +2 -2
  80. package/src/ts/style_handlers/table_cell/string.ts +20 -18
  81. package/src/ts/style_handlers/types.ts +0 -10
  82. package/src/ts/types.ts +28 -20
  83. package/dist/esm/event_handlers/deselect_all.d.ts +0 -5
  84. package/dist/esm/event_handlers/row_select_click.d.ts +0 -4
  85. package/dist/esm/plugin/column_style_controls.d.ts +0 -28
  86. package/src/ts/event_handlers/row_select_click.ts +0 -92
  87. package/src/ts/plugin/column_style_controls.ts +0 -76
@@ -14,25 +14,29 @@ import { focusSelectedCell } from "../../style_handlers/focus.js";
14
14
  import type {
15
15
  RegularTable,
16
16
  DatagridModel,
17
- PerspectiveViewerElement,
18
- SelectedPosition,
17
+ SelectedPositionMap,
19
18
  } from "../../types.js";
19
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
20
20
 
21
- type SelectedPositionMap = Map<RegularTable, SelectedPosition>;
22
-
23
- type AsyncFunction<T extends unknown[], R> = (
24
- this: DatagridModel,
25
- ...args: T
26
- ) => Promise<R>;
21
+ type AsyncMoveFunction = (
22
+ model: DatagridModel,
23
+ table: RegularTable,
24
+ selected_position_map: SelectedPositionMap,
25
+ active_cell: HTMLElement,
26
+ dx: number,
27
+ dy: number,
28
+ ) => Promise<void | undefined>;
27
29
 
28
- function lock<T extends unknown[], R>(
29
- body: AsyncFunction<T, R>,
30
- ): AsyncFunction<T, R | undefined> {
30
+ function lock(body: AsyncMoveFunction): AsyncMoveFunction {
31
31
  let lockPromise: Promise<void> | undefined;
32
32
  return async function (
33
- this: DatagridModel,
34
- ...args: T
35
- ): Promise<R | undefined> {
33
+ model: DatagridModel,
34
+ table: RegularTable,
35
+ selected_position_map: SelectedPositionMap,
36
+ active_cell: HTMLElement,
37
+ dx: number,
38
+ dy: number,
39
+ ): Promise<void | undefined> {
36
40
  if (lockPromise) {
37
41
  await lockPromise;
38
42
  return;
@@ -40,7 +44,14 @@ function lock<T extends unknown[], R>(
40
44
 
41
45
  let resolve: () => void;
42
46
  lockPromise = new Promise((x) => (resolve = x));
43
- const result = await body.apply(this, args);
47
+ const result = await body(
48
+ model,
49
+ table,
50
+ selected_position_map,
51
+ active_cell,
52
+ dx,
53
+ dy,
54
+ );
44
55
  lockPromise = undefined;
45
56
  resolve!();
46
57
  return result;
@@ -52,23 +63,26 @@ interface ContentEditableElement extends HTMLElement {
52
63
  selectionStart?: number;
53
64
  }
54
65
 
55
- function getPos(this: ContentEditableElement): number {
56
- if (this.isContentEditable) {
57
- const _range = (this.getRootNode() as Document)
66
+ function getPos(elem: ContentEditableElement): number {
67
+ if (elem.isContentEditable) {
68
+ const _range = (elem.getRootNode() as Document)
58
69
  .getSelection()
59
70
  ?.getRangeAt(0);
60
- if (!_range) return 0;
71
+ if (!_range) {
72
+ return 0;
73
+ }
74
+
61
75
  const range = _range.cloneRange();
62
- range.selectNodeContents(this);
76
+ range.selectNodeContents(elem);
63
77
  range.setEnd(_range.endContainer, _range.endOffset);
64
78
  return range.toString().length;
65
79
  } else {
66
- return this.selectionStart || 0;
80
+ return elem.selectionStart || 0;
67
81
  }
68
82
  }
69
83
 
70
84
  const moveSelection = lock(async function (
71
- this: DatagridModel,
85
+ model: DatagridModel,
72
86
  table: RegularTable,
73
87
  selected_position_map: SelectedPositionMap,
74
88
  active_cell: HTMLElement,
@@ -76,9 +90,12 @@ const moveSelection = lock(async function (
76
90
  dy: number,
77
91
  ): Promise<void> {
78
92
  const meta = table.getMeta(active_cell);
79
- if (!meta || meta.type !== "body") return;
80
- const num_columns = this._column_paths.length;
81
- const num_rows = this._num_rows;
93
+ if (!meta || meta.type !== "body") {
94
+ return;
95
+ }
96
+
97
+ const num_columns = model._column_paths.length;
98
+ const num_rows = model._num_rows;
82
99
  const selected_position = selected_position_map.get(table);
83
100
  if (!selected_position) {
84
101
  return;
@@ -122,9 +139,9 @@ function isLastCell(
122
139
  }
123
140
 
124
141
  export function keydownListener(
125
- this: DatagridModel,
142
+ model: DatagridModel,
126
143
  table: RegularTable,
127
- _viewer: PerspectiveViewerElement,
144
+ _viewer: HTMLPerspectiveViewerElement,
128
145
  selected_position_map: SelectedPositionMap,
129
146
  event: KeyboardEvent,
130
147
  ): void {
@@ -134,12 +151,12 @@ export function keydownListener(
134
151
  switch (event.key) {
135
152
  case "Enter":
136
153
  event.preventDefault();
137
- if (isLastCell(this, table, target)) {
154
+ if (isLastCell(model, table, target)) {
138
155
  target.blur();
139
156
  selected_position_map.delete(table);
140
157
  } else if (event.shiftKey) {
141
- moveSelection.call(
142
- this,
158
+ moveSelection(
159
+ model,
143
160
  table,
144
161
  selected_position_map,
145
162
  target,
@@ -147,8 +164,8 @@ export function keydownListener(
147
164
  -1,
148
165
  );
149
166
  } else {
150
- moveSelection.call(
151
- this,
167
+ moveSelection(
168
+ model,
152
169
  table,
153
170
  selected_position_map,
154
171
  target,
@@ -156,12 +173,13 @@ export function keydownListener(
156
173
  1,
157
174
  );
158
175
  }
176
+
159
177
  break;
160
178
  case "ArrowLeft":
161
- if (getPos.call(target as ContentEditableElement) === 0) {
179
+ if (getPos(target as ContentEditableElement) === 0) {
162
180
  event.preventDefault();
163
- moveSelection.call(
164
- this,
181
+ moveSelection(
182
+ model,
165
183
  table,
166
184
  selected_position_map,
167
185
  target,
@@ -169,26 +187,20 @@ export function keydownListener(
169
187
  0,
170
188
  );
171
189
  }
190
+
172
191
  break;
173
192
  case "ArrowUp":
174
193
  event.preventDefault();
175
- moveSelection.call(
176
- this,
177
- table,
178
- selected_position_map,
179
- target,
180
- 0,
181
- -1,
182
- );
194
+ moveSelection(model, table, selected_position_map, target, 0, -1);
183
195
  break;
184
196
  case "ArrowRight":
185
197
  if (
186
- getPos.call(target as ContentEditableElement) ===
198
+ getPos(target as ContentEditableElement) ===
187
199
  (target.textContent?.length || 0)
188
200
  ) {
189
201
  event.preventDefault();
190
- moveSelection.call(
191
- this,
202
+ moveSelection(
203
+ model,
192
204
  table,
193
205
  selected_position_map,
194
206
  target,
@@ -196,17 +208,11 @@ export function keydownListener(
196
208
  0,
197
209
  );
198
210
  }
211
+
199
212
  break;
200
213
  case "ArrowDown":
201
214
  event.preventDefault();
202
- moveSelection.call(
203
- this,
204
- table,
205
- selected_position_map,
206
- target,
207
- 0,
208
- 1,
209
- );
215
+ moveSelection(model, table, selected_position_map, target, 0, 1);
210
216
  break;
211
217
  default:
212
218
  }
@@ -13,16 +13,24 @@
13
13
  import { RegularTableElement } from "regular-table";
14
14
  import type {
15
15
  DatagridPluginElement,
16
- PerspectiveViewerElement,
16
+ EditMode,
17
17
  SelectionArea,
18
18
  } from "../types.js";
19
- import { ViewWindow } from "@perspective-dev/client";
19
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
20
+ import type { ViewWindow } from "@perspective-dev/client";
21
+ import type { CellMetadataBody } from "regular-table/dist/esm/types.js";
20
22
 
21
23
  const MOUSE_SELECTED_AREA_CLASS = "mouse-selected-area";
22
24
 
25
+ export type OnSelectCallback = (
26
+ area: SelectionArea,
27
+ isDeselect: boolean,
28
+ ) => void;
29
+
23
30
  interface AddAreaMouseSelectionOptions {
24
31
  className?: string;
25
32
  selected?: SelectionArea[];
33
+ onSelect?: OnSelectCallback;
26
34
  }
27
35
 
28
36
  export const addAreaMouseSelection = (
@@ -31,6 +39,7 @@ export const addAreaMouseSelection = (
31
39
  {
32
40
  className = MOUSE_SELECTED_AREA_CLASS,
33
41
  selected = [],
42
+ onSelect,
34
43
  }: AddAreaMouseSelectionOptions = {},
35
44
  ): RegularTableElement => {
36
45
  datagrid.model!._selection_state = {
@@ -50,7 +59,7 @@ export const addAreaMouseSelection = (
50
59
 
51
60
  table.addEventListener(
52
61
  "mouseup",
53
- getMouseupListener(datagrid, table, className),
62
+ getMouseupListener(datagrid, table, className, onSelect),
54
63
  );
55
64
 
56
65
  table.addStyleListener(() =>
@@ -60,6 +69,10 @@ export const addAreaMouseSelection = (
60
69
  return table;
61
70
  };
62
71
 
72
+ function isSingleClickMode(mode: EditMode): boolean {
73
+ return mode === "SELECT_ROW_TREE";
74
+ }
75
+
63
76
  const getMousedownListener =
64
77
  (
65
78
  datagrid: DatagridPluginElement,
@@ -70,10 +83,12 @@ const getMousedownListener =
70
83
  const mouseEvent = event as MouseEvent;
71
84
  if (
72
85
  mouseEvent.button === 0 &&
73
- (datagrid.model!._edit_mode === "SELECT_REGION" ||
74
- datagrid.model!._edit_mode === "SELECT_ROW" ||
75
- datagrid.model!._edit_mode === "SELECT_COLUMN")
86
+ isSelectionMode(datagrid.model!._edit_mode)
76
87
  ) {
88
+ if (isSingleClickMode(datagrid.model!._edit_mode)) {
89
+ return;
90
+ }
91
+
77
92
  datagrid.model!._selection_state.CURRENT_MOUSEDOWN_COORDINATES = {};
78
93
  const meta = table.getMeta(mouseEvent.target as HTMLElement);
79
94
  if (
@@ -122,11 +137,8 @@ const getMouseoverListener =
122
137
  ) =>
123
138
  (event: Event): void => {
124
139
  const mouseEvent = event as MouseEvent;
125
- if (
126
- datagrid.model!._edit_mode === "SELECT_REGION" ||
127
- datagrid.model!._edit_mode === "SELECT_ROW" ||
128
- datagrid.model!._edit_mode === "SELECT_COLUMN"
129
- ) {
140
+ const mode = datagrid.model!._edit_mode;
141
+ if (isSelectionMode(mode) && !isSingleClickMode(mode)) {
130
142
  if (
131
143
  datagrid.model!._selection_state
132
144
  .CURRENT_MOUSEDOWN_COORDINATES &&
@@ -183,17 +195,65 @@ const getMouseupListener =
183
195
  datagrid: DatagridPluginElement,
184
196
  table: RegularTableElement,
185
197
  className: string,
198
+ onSelect?: OnSelectCallback,
186
199
  ) =>
187
200
  (event: Event): void => {
188
201
  const mouseEvent = event as MouseEvent;
189
- if (
190
- datagrid.model!._edit_mode === "SELECT_REGION" ||
191
- datagrid.model!._edit_mode === "SELECT_ROW" ||
192
- datagrid.model!._edit_mode === "SELECT_COLUMN"
193
- ) {
202
+ const mode = datagrid.model!._edit_mode;
203
+ if (isSelectionMode(mode)) {
194
204
  const meta = table.getMeta(mouseEvent.target as HTMLElement);
195
- if (!meta) return;
205
+ if (!meta) {
206
+ return;
207
+ }
196
208
 
209
+ // For single-click modes (SELECT_ROW_TREE), handle toggle
210
+ if (isSingleClickMode(mode)) {
211
+ if (
212
+ (meta.type === "body" || meta.type === "row_header") &&
213
+ meta.y !== undefined &&
214
+ meta.y >= 0
215
+ ) {
216
+ const existing =
217
+ datagrid.model!._selection_state.selected_areas;
218
+ const isSameRow =
219
+ existing.length > 0 && existing[0].y0 === meta.y;
220
+
221
+ if (isSameRow) {
222
+ // Deselect
223
+ datagrid.model!._selection_state.selected_areas = [];
224
+ datagrid.model!._selection_state.dirty = true;
225
+ applyMouseAreaSelections(
226
+ datagrid,
227
+ table,
228
+ className,
229
+ [],
230
+ );
231
+ onSelect?.(existing[0], true);
232
+ } else {
233
+ // Select new row
234
+ const area: SelectionArea = {
235
+ x0: 0,
236
+ x1: 0,
237
+ y0: meta.y,
238
+ y1: meta.y,
239
+ };
240
+ datagrid.model!._selection_state.selected_areas = [
241
+ area,
242
+ ];
243
+ datagrid.model!._selection_state.dirty = true;
244
+ applyMouseAreaSelections(datagrid, table, className);
245
+ onSelect?.(area, false);
246
+ }
247
+ }
248
+
249
+ datagrid.model!._selection_state.CURRENT_MOUSEDOWN_COORDINATES =
250
+ {};
251
+ datagrid.model!._selection_state.potential_selection =
252
+ undefined;
253
+ return;
254
+ }
255
+
256
+ // Drag-based modes (SELECT_ROW, SELECT_COLUMN, SELECT_REGION)
197
257
  if (
198
258
  (datagrid.model!._selection_state.old_selected_areas?.length ??
199
259
  0) > 0
@@ -260,80 +320,103 @@ const getMouseupListener =
260
320
  }
261
321
  };
262
322
 
323
+ function modeIncludesColumns(mode: EditMode): boolean {
324
+ return mode === "SELECT_COLUMN" || mode === "SELECT_REGION";
325
+ }
326
+
327
+ function modeIncludesRows(mode: EditMode): boolean {
328
+ return (
329
+ mode === "SELECT_ROW" ||
330
+ mode === "SELECT_REGION" ||
331
+ mode === "SELECT_ROW_TREE"
332
+ );
333
+ }
334
+
263
335
  function set_psp_selection(
264
- viewer: PerspectiveViewerElement,
336
+ viewer: HTMLPerspectiveViewerElement,
265
337
  datagrid: DatagridPluginElement,
266
338
  { x0, x1, y0, y1 }: SelectionArea,
267
339
  ): void {
268
340
  const viewport: ViewWindow = {};
269
341
  const mode = datagrid.model!._edit_mode;
270
- if (
271
- x0 !== undefined &&
272
- ["SELECT_COLUMN", "SELECT_REGION"].indexOf(mode) > -1
273
- ) {
342
+ if (x0 !== undefined && modeIncludesColumns(mode)) {
274
343
  viewport.start_col = x0;
275
344
  }
276
345
 
277
- if (
278
- x1 !== undefined &&
279
- ["SELECT_COLUMN", "SELECT_REGION"].indexOf(mode) > -1
280
- ) {
346
+ if (x1 !== undefined && modeIncludesColumns(mode)) {
281
347
  viewport.end_col = x1 + 1;
282
348
  }
283
349
 
284
- if (
285
- y0 !== undefined &&
286
- ["SELECT_ROW", "SELECT_REGION"].indexOf(mode) > -1
287
- ) {
350
+ if (y0 !== undefined && modeIncludesRows(mode)) {
288
351
  viewport.start_row = y0;
289
352
  }
290
353
 
291
- if (
292
- y1 !== undefined &&
293
- ["SELECT_ROW", "SELECT_REGION"].indexOf(mode) > -1
294
- ) {
354
+ if (y1 !== undefined && modeIncludesRows(mode)) {
295
355
  viewport.end_row = y1 + 1;
296
356
  }
297
357
 
298
358
  viewer.setSelection(viewport);
299
359
  }
300
360
 
361
+ type CellPredicate = (meta: CellMetadataBody, area: SelectionArea) => boolean;
362
+
363
+ const SELECTION_PREDICATES: Record<string, CellPredicate> = {
364
+ SELECT_REGION: (m, a) =>
365
+ a.x0 <= m.x && m.x <= a.x1 && a.y0 <= m.y && m.y <= a.y1,
366
+ SELECT_ROW: (m, a) => a.y0 <= m.y && m.y <= a.y1,
367
+ SELECT_ROW_TREE: (m, a) => a.y0 <= m.y && m.y <= a.y1,
368
+ SELECT_COLUMN: (m, a) => a.x0 <= m.x && m.x <= a.x1,
369
+ };
370
+
371
+ function isSelectionMode(mode: EditMode): boolean {
372
+ return (
373
+ mode === "SELECT_REGION" ||
374
+ mode === "SELECT_ROW" ||
375
+ mode === "SELECT_COLUMN" ||
376
+ mode === "SELECT_ROW_TREE"
377
+ );
378
+ }
379
+
301
380
  export const applyMouseAreaSelections = (
302
381
  datagrid: DatagridPluginElement,
303
382
  table: RegularTableElement,
304
383
  className: string,
305
384
  selected?: SelectionArea[],
306
385
  ): void => {
307
- if (
308
- datagrid.model!._edit_mode === "SELECT_REGION" ||
309
- datagrid.model!._edit_mode === "SELECT_ROW" ||
310
- datagrid.model!._edit_mode === "SELECT_COLUMN"
311
- ) {
386
+ const mode = datagrid.model!._edit_mode;
387
+ if (isSelectionMode(mode)) {
312
388
  selected = datagrid.model!._selection_state.selected_areas.slice(0);
313
389
  if (datagrid.model!._selection_state.potential_selection) {
314
390
  selected.push(datagrid.model!._selection_state.potential_selection);
315
391
  }
316
392
 
317
- const tds = table.querySelectorAll("tbody td");
318
-
319
393
  if (selected.length > 0) {
320
394
  set_psp_selection(
321
- datagrid.parentElement as any,
395
+ datagrid.parentElement as HTMLPerspectiveViewerElement,
322
396
  datagrid,
323
397
  selected[0],
324
398
  );
325
- applyMouseAreaSelection(datagrid, table, selected, className);
399
+
400
+ // SELECT_ROW_TREE styling is handled entirely by the
401
+ // identity-based system in body.ts, which styles both td
402
+ // and th uniformly in a single draw pass.
403
+ if (!isSingleClickMode(mode)) {
404
+ applyMouseAreaSelection(datagrid, table, selected, className);
405
+ }
326
406
  } else {
327
- (datagrid.parentElement as any).setSelection();
407
+ (
408
+ datagrid.parentElement as HTMLPerspectiveViewerElement
409
+ ).setSelection();
410
+ const tds = table.querySelectorAll("tbody td");
328
411
  for (const td of tds) {
329
412
  td.classList.remove(className);
330
413
  }
331
414
  }
332
415
  } else if (datagrid.model!._selection_state.dirty) {
333
416
  datagrid.model!._selection_state.dirty = false;
334
- const tds = table.querySelectorAll("tbody td");
335
- for (const td of tds) {
336
- td.classList.remove(className);
417
+ const cells = table.querySelectorAll("tbody td, tbody th");
418
+ for (const cell of cells) {
419
+ cell.classList.remove(className);
337
420
  }
338
421
  }
339
422
  };
@@ -344,96 +427,35 @@ const applyMouseAreaSelection = (
344
427
  selected: SelectionArea[],
345
428
  className: string,
346
429
  ): void => {
347
- if (datagrid.model!._edit_mode === "SELECT_REGION" && selected.length > 0) {
348
- const tds = table.querySelectorAll("tbody td");
349
-
350
- for (const td of tds) {
351
- const meta = table.getMeta(td as HTMLElement);
352
- if (!meta || meta.type !== "body") continue;
353
- let rendered = false;
354
- for (const { x0, x1, y0, y1 } of selected) {
355
- if (
356
- x0 !== undefined &&
357
- y0 !== undefined &&
358
- x1 !== undefined &&
359
- y1 !== undefined
360
- ) {
361
- if (
362
- x0 <= meta.x &&
363
- meta.x <= x1 &&
364
- y0 <= meta.y &&
365
- meta.y <= y1
366
- ) {
367
- rendered = true;
368
- datagrid.model!._selection_state.dirty = true;
369
- td.classList.add(className);
370
- }
371
- }
372
- }
430
+ const predicate = SELECTION_PREDICATES[datagrid.model!._edit_mode];
431
+ if (!predicate || selected.length === 0) {
432
+ return;
433
+ }
373
434
 
374
- if (!rendered) {
375
- td.classList.remove(className);
376
- }
435
+ const tds = table.querySelectorAll("tbody td");
436
+ for (const td of tds) {
437
+ const meta = table.getMeta(td as HTMLElement);
438
+ if (!meta || meta.type !== "body") {
439
+ continue;
377
440
  }
378
- } else if (
379
- datagrid.model!._edit_mode === "SELECT_ROW" &&
380
- selected.length > 0
381
- ) {
382
- const tds = table.querySelectorAll("tbody td");
383
-
384
- for (const td of tds) {
385
- const meta = table.getMeta(td as HTMLElement);
386
- if (!meta) continue;
387
- let rendered = false;
388
- for (const { x0, x1, y0, y1 } of selected) {
389
- if (
390
- x0 !== undefined &&
391
- y0 !== undefined &&
392
- x1 !== undefined &&
393
- y1 !== undefined &&
394
- meta?.type === "body"
395
- ) {
396
- if (y0 <= meta.y && meta.y <= y1) {
397
- datagrid.model!._selection_state.dirty = true;
398
- rendered = true;
399
- td.classList.add(className);
400
- }
401
- }
402
- }
403
441
 
404
- if (!rendered) {
405
- td.classList.remove(className);
442
+ let rendered = false;
443
+ for (const area of selected) {
444
+ if (
445
+ area.x0 !== undefined &&
446
+ area.y0 !== undefined &&
447
+ area.x1 !== undefined &&
448
+ area.y1 !== undefined &&
449
+ predicate(meta, area)
450
+ ) {
451
+ rendered = true;
452
+ datagrid.model!._selection_state.dirty = true;
453
+ td.classList.add(className);
406
454
  }
407
455
  }
408
- } else if (
409
- datagrid.model!._edit_mode === "SELECT_COLUMN" &&
410
- selected.length > 0
411
- ) {
412
- const tds = table.querySelectorAll("tbody td");
413
-
414
- for (const td of tds) {
415
- const meta = table.getMeta(td as HTMLElement);
416
- if (!meta) continue;
417
- let rendered = false;
418
- for (const { x0, x1, y0, y1 } of selected) {
419
- if (
420
- x0 !== undefined &&
421
- y0 !== undefined &&
422
- x1 !== undefined &&
423
- y1 !== undefined &&
424
- meta?.type === "body"
425
- ) {
426
- if (x0 <= meta.x && meta.x <= x1) {
427
- datagrid.model!._selection_state.dirty = true;
428
- rendered = true;
429
- td.classList.add(className);
430
- }
431
- }
432
- }
433
456
 
434
- if (!rendered) {
435
- td.classList.remove(className);
436
- }
457
+ if (!rendered) {
458
+ td.classList.remove(className);
437
459
  }
438
460
  }
439
461
  };