@perspective-dev/viewer-datagrid 4.3.0 → 4.4.1

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 +5 -5
  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/perspective-viewer-datagrid.js +3 -3
  21. package/dist/esm/perspective-viewer-datagrid.js.map +4 -4
  22. package/dist/esm/plugin/activate.d.ts +1 -1
  23. package/dist/esm/plugin/column_style_controls.d.ts +1 -1
  24. package/dist/esm/style_handlers/body.d.ts +3 -3
  25. package/dist/esm/style_handlers/column_header.d.ts +4 -3
  26. package/dist/esm/style_handlers/consolidated.d.ts +3 -47
  27. package/dist/esm/style_handlers/editable.d.ts +3 -2
  28. package/dist/esm/style_handlers/focus.d.ts +4 -4
  29. package/dist/esm/style_handlers/group_header.d.ts +1 -1
  30. package/dist/esm/style_handlers/table_cell/boolean.d.ts +1 -1
  31. package/dist/esm/style_handlers/table_cell/cell_flash.d.ts +1 -1
  32. package/dist/esm/style_handlers/table_cell/datetime.d.ts +6 -2
  33. package/dist/esm/style_handlers/table_cell/numeric.d.ts +1 -1
  34. package/dist/esm/style_handlers/table_cell/row_header.d.ts +1 -1
  35. package/dist/esm/style_handlers/table_cell/string.d.ts +1 -1
  36. package/dist/esm/style_handlers/types.d.ts +0 -4
  37. package/dist/esm/types.d.ts +10 -17
  38. package/package.json +3 -5
  39. package/src/css/mitered-headers.css +64 -0
  40. package/src/css/perspective-viewer-datagrid.css +6 -0
  41. package/src/{less/pro.less → css/pro.css} +32 -31
  42. package/src/css/regular_table.css +589 -0
  43. package/src/{less/row-hover.less → css/row-hover.css} +48 -29
  44. package/src/{less/scrollbar.less → css/scrollbar.css} +16 -15
  45. package/src/{less/sub-cell-scroll.less → css/sub-cell-scroll.css} +14 -13
  46. package/src/{less/toolbar.less → css/toolbar.css} +57 -39
  47. package/src/ts/color_utils.ts +144 -16
  48. package/src/ts/custom_elements/datagrid.ts +11 -12
  49. package/src/ts/custom_elements/toolbar.ts +4 -5
  50. package/src/ts/data_listener/format_cell.ts +28 -9
  51. package/src/ts/data_listener/formatter_cache.ts +1 -1
  52. package/src/ts/data_listener/index.ts +4 -8
  53. package/src/ts/event_handlers/click/edit_click.ts +7 -6
  54. package/src/ts/event_handlers/click.ts +39 -68
  55. package/src/ts/event_handlers/dispatch_click.ts +24 -25
  56. package/src/ts/event_handlers/expand_collapse.ts +7 -7
  57. package/src/ts/event_handlers/focus.ts +38 -35
  58. package/src/ts/event_handlers/header_click.ts +101 -62
  59. package/src/ts/event_handlers/keydown/edit_keydown.ts +49 -52
  60. package/src/ts/event_handlers/select_region.ts +144 -133
  61. package/src/ts/event_handlers/sort.ts +16 -24
  62. package/src/ts/model/column_overrides.ts +13 -4
  63. package/src/ts/model/create.ts +55 -59
  64. package/src/ts/model/toolbar.ts +23 -7
  65. package/src/ts/plugin/activate.ts +120 -92
  66. package/src/ts/plugin/column_style_controls.ts +1 -1
  67. package/src/ts/plugin/save.ts +1 -0
  68. package/src/ts/style_handlers/body.ts +56 -61
  69. package/src/ts/style_handlers/column_header.ts +16 -19
  70. package/src/ts/style_handlers/consolidated.ts +22 -123
  71. package/src/ts/style_handlers/editable.ts +10 -8
  72. package/src/ts/style_handlers/focus.ts +5 -5
  73. package/src/ts/style_handlers/group_header.ts +3 -2
  74. package/src/ts/style_handlers/table_cell/boolean.ts +3 -3
  75. package/src/ts/style_handlers/table_cell/cell_flash.ts +11 -11
  76. package/src/ts/style_handlers/table_cell/datetime.ts +14 -11
  77. package/src/ts/style_handlers/table_cell/numeric.ts +24 -25
  78. package/src/ts/style_handlers/table_cell/row_header.ts +2 -2
  79. package/src/ts/style_handlers/table_cell/string.ts +20 -18
  80. package/src/ts/style_handlers/types.ts +0 -10
  81. package/src/ts/types.ts +28 -20
  82. package/dist/esm/event_handlers/deselect_all.d.ts +0 -5
  83. package/dist/esm/event_handlers/row_select_click.d.ts +0 -4
  84. package/src/less/mitered-headers.less +0 -65
  85. package/src/less/regular_table.less +0 -526
  86. package/src/ts/event_handlers/deselect_all.ts +0 -28
  87. package/src/ts/event_handlers/row_select_click.ts +0 -92
@@ -12,33 +12,41 @@
12
12
 
13
13
  import { style_selected_column } from "../style_handlers/column_header.js";
14
14
  import {
15
- click_listener,
16
- mousedown_listener,
15
+ createMousedownListener,
16
+ createClickListener,
17
+ createDblclickListener,
17
18
  } from "../event_handlers/header_click.js";
18
19
 
19
- import { focusinListener, focusoutListener } from "../event_handlers/focus.js";
20
- import { keydownListener, clickListener } from "../event_handlers/click.js";
21
-
22
- import { selectionListener } from "../event_handlers/row_select_click.js";
23
- import { deselect_all_listener } from "../event_handlers/deselect_all.js";
20
+ import {
21
+ createFocusinListener,
22
+ createFocusoutListener,
23
+ } from "../event_handlers/focus.js";
24
+ import {
25
+ createKeydownListener,
26
+ createEditClickListener,
27
+ } from "../event_handlers/click.js";
24
28
 
25
29
  import { createModel } from "../model/create.js";
26
- import { dispatch_click_listener } from "../event_handlers/dispatch_click.js";
27
-
28
- import { addAreaMouseSelection } from "../event_handlers/select_region.js";
30
+ import { createDispatchClickListener } from "../event_handlers/dispatch_click.js";
29
31
 
30
32
  import {
31
- createConsolidatedStyleListener,
32
- installConsolidatedStyleMethods,
33
- } from "../style_handlers/consolidated.js";
33
+ addAreaMouseSelection,
34
+ type OnSelectCallback,
35
+ } from "../event_handlers/select_region.js";
36
+
37
+ import { createConsolidatedStyleListener } from "../style_handlers/consolidated.js";
38
+
39
+ import getCellConfig from "../get_cell_config.js";
34
40
 
35
41
  import type { View } from "@perspective-dev/client";
36
- import type {
37
- DatagridPluginElement,
38
- PerspectiveViewerElement,
39
- SelectedPosition,
42
+ import {
43
+ type DatagridPluginElement,
44
+ type SelectedPositionMap,
45
+ type SelectionArea,
46
+ PerspectiveSelectDetail,
40
47
  } from "../types.js";
41
- import type { RegularTableElement } from "regular-table";
48
+
49
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
42
50
 
43
51
  interface ToggleColumnSettingsEvent extends CustomEvent {
44
52
  detail: {
@@ -54,7 +62,7 @@ export async function activate(
54
62
  this: DatagridPluginElement,
55
63
  view: View,
56
64
  ): Promise<void> {
57
- const viewer = this.parentElement as PerspectiveViewerElement;
65
+ const viewer = this.parentElement as HTMLPerspectiveViewerElement;
58
66
  const table = await viewer.getTable();
59
67
 
60
68
  if (!this._initialized) {
@@ -70,132 +78,151 @@ export async function activate(
70
78
  this.regular_table,
71
79
  table,
72
80
  view,
81
+ viewer.getAttribute("theme")!,
73
82
  );
74
83
 
75
84
  if (!this.model) {
76
85
  return;
77
86
  }
78
87
 
88
+ const model = this.model;
89
+ const regularTable = this.regular_table;
90
+ const onSelect: OnSelectCallback = async (
91
+ area: SelectionArea,
92
+ isDeselect: boolean,
93
+ ) => {
94
+ if (model._edit_mode !== "SELECT_ROW_TREE") return;
95
+
96
+ // Store the selected row identity on the model so it persists
97
+ // even when the selected row scrolls out of the viewport.
98
+ if (isDeselect) {
99
+ model._tree_selection_id = undefined;
100
+ } else {
101
+ const idx = area.y0 - (model._last_window?.start_row ?? 0);
102
+ if (idx >= 0 && idx < model._ids.length) {
103
+ model._tree_selection_id = model._ids[idx];
104
+ }
105
+ }
106
+
107
+ const { row, column_names, config } = await getCellConfig(
108
+ model,
109
+ area.y0,
110
+ 0,
111
+ );
112
+
113
+ let detail: PerspectiveSelectDetail;
114
+ if (isDeselect) {
115
+ if ((model._last_insert_configs?.length || 0) > 0) {
116
+ detail = new PerspectiveSelectDetail(
117
+ false,
118
+ row,
119
+ [],
120
+ model._last_insert_configs ?? [],
121
+ [],
122
+ );
123
+ } else {
124
+ throw new Error("Suprious deselect");
125
+ }
126
+
127
+ model._last_insert_configs = undefined;
128
+ } else {
129
+ detail = new PerspectiveSelectDetail(
130
+ true,
131
+ row,
132
+ column_names,
133
+ model._last_insert_configs ?? [],
134
+ [config],
135
+ );
136
+ model._last_insert_configs = [config];
137
+ }
138
+
139
+ await regularTable.draw({ preserve_width: true });
140
+ viewer.dispatchEvent(
141
+ new CustomEvent<PerspectiveSelectDetail>(
142
+ "perspective-global-filter",
143
+ {
144
+ bubbles: true,
145
+ composed: true,
146
+ detail,
147
+ },
148
+ ),
149
+ );
150
+ };
151
+
79
152
  addAreaMouseSelection(this, this.regular_table, {
80
153
  className: "psp-select-region",
154
+ onSelect,
81
155
  });
82
156
 
83
- // Create shared state maps for selection and focus tracking
84
- const selected_rows_map = new WeakMap<
85
- RegularTableElement,
86
- Set<number>
87
- >();
88
- const selected_position_map = new WeakMap<
89
- RegularTableElement,
90
- SelectedPosition
91
- >();
92
-
93
- // Install consolidated style methods on model prototype
94
- installConsolidatedStyleMethods(this.model);
95
-
96
- // Single consolidated style listener replaces:
97
- // - table_cell_style_listener
98
- // - group_header_style_listener
99
- // - column_header_style_listener
100
- // - selectionStyleListener
101
- // - editable_style_listener
102
- // - focus_style_listener
157
+ // Create shared state map for focus tracking
158
+ const selected_position_map: SelectedPositionMap = new WeakMap();
159
+
103
160
  this.regular_table.addStyleListener(
104
161
  createConsolidatedStyleListener(
105
162
  this,
106
- selected_rows_map as any,
107
- selected_position_map as any,
108
- ).bind(this.model, this.regular_table, viewer),
109
- );
110
-
111
- // uh ..
112
- this.regular_table.addEventListener(
113
- "click",
114
- click_listener.bind(
115
- this.model,
116
- this.regular_table,
117
- ) as EventListener,
118
- );
119
-
120
- this.regular_table.addEventListener(
121
- "mousedown",
122
- selectionListener.bind(
123
163
  this.model,
124
164
  this.regular_table,
125
165
  viewer,
126
- selected_rows_map as any,
127
- ) as unknown as EventListener,
166
+ selected_position_map,
167
+ ),
128
168
  );
129
169
 
130
170
  this.regular_table.addEventListener(
131
- "psp-deselect-all",
132
- deselect_all_listener.bind(
133
- this.model,
134
- this.regular_table,
135
- viewer,
136
- selected_rows_map as any,
137
- ) as unknown as EventListener,
171
+ "click",
172
+ createClickListener(this.regular_table),
138
173
  );
139
174
 
140
175
  // User event click
141
176
  this.regular_table.addEventListener(
142
177
  "click",
143
- dispatch_click_listener.bind(
144
- this.model,
145
- this.regular_table,
146
- viewer,
147
- ) as unknown as EventListener,
178
+ createDispatchClickListener(this.model, this.regular_table, viewer),
148
179
  );
149
180
 
150
181
  // tree collapse, expand, edit button headers
151
182
  this.regular_table.addEventListener(
152
183
  "mousedown",
153
- mousedown_listener.bind(
154
- this.model,
155
- this.regular_table,
156
- viewer,
157
- ) as unknown as EventListener,
184
+ createMousedownListener(this.model, this.regular_table, viewer),
158
185
  );
159
186
 
160
- // Editing event handlers (style handling is now in consolidated listener)
161
- // TODO relies on this.model._is_editable
187
+ this.regular_table.addEventListener(
188
+ "dblclick",
189
+ createDblclickListener(this.model, this.regular_table, viewer),
190
+ );
191
+
192
+ // Editing event handlers
162
193
  this.regular_table.addEventListener(
163
194
  "click",
164
- clickListener.bind(
165
- this.model,
166
- this.regular_table,
167
- viewer,
168
- ) as EventListener,
195
+ createEditClickListener(this.model, this.regular_table, viewer),
169
196
  );
170
197
 
171
198
  this.regular_table.addEventListener(
172
199
  "focusin",
173
- focusinListener.bind(
200
+ createFocusinListener(
174
201
  this.model,
175
202
  this.regular_table,
176
203
  viewer,
177
- selected_position_map as any,
178
- ) as EventListener,
204
+ selected_position_map,
205
+ ),
179
206
  );
180
207
 
181
208
  this.regular_table.addEventListener(
182
209
  "focusout",
183
- focusoutListener.bind(
210
+ createFocusoutListener(
184
211
  this.model,
185
212
  this.regular_table,
186
213
  viewer,
187
- selected_position_map as any,
188
- ) as EventListener,
214
+ selected_position_map,
215
+ ),
189
216
  );
190
217
 
191
218
  this.regular_table.addEventListener(
192
219
  "keydown",
193
- keydownListener.bind(
220
+ createKeydownListener(
194
221
  this.model,
195
222
  this.regular_table,
196
223
  viewer,
197
- selected_position_map as any,
198
- ) as EventListener,
224
+ selected_position_map,
225
+ ),
199
226
  );
200
227
 
201
228
  // viewer event listeners
@@ -204,7 +231,7 @@ export async function activate(
204
231
  (event: Event) => {
205
232
  const toggleEvent = event as ToggleColumnSettingsEvent;
206
233
  if (this.isConnected) {
207
- style_selected_column.call(
234
+ style_selected_column(
208
235
  this.model!,
209
236
  this.regular_table,
210
237
  viewer,
@@ -229,6 +256,7 @@ export async function activate(
229
256
  this.regular_table,
230
257
  table,
231
258
  view,
259
+ viewer.getAttribute("theme")!,
232
260
  this.model,
233
261
  );
234
262
  }
@@ -10,7 +10,7 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- import { ColumnType } from "@perspective-dev/client";
13
+ import type { ColumnType } from "@perspective-dev/client";
14
14
  import type { DatagridPluginElement } from "../types.js";
15
15
 
16
16
  interface NumberStyleOpts {
@@ -41,5 +41,6 @@ export function save(
41
41
 
42
42
  return JSON.parse(JSON.stringify(token));
43
43
  }
44
+
44
45
  return {};
45
46
  }
@@ -14,9 +14,7 @@ import { RegularTableElement } from "regular-table";
14
14
 
15
15
  import {
16
16
  type DatagridModel,
17
- type PerspectiveViewerElement,
18
17
  type ColumnsConfig,
19
- type DatagridPluginElement,
20
18
  get_psp_type,
21
19
  } from "../types.js";
22
20
 
@@ -25,39 +23,31 @@ import { cell_style_string } from "./table_cell/string.js";
25
23
  import { cell_style_datetime } from "./table_cell/datetime.js";
26
24
  import { cell_style_boolean } from "./table_cell/boolean.js";
27
25
  import { cell_style_row_header } from "./table_cell/row_header.js";
28
- import {
29
- CollectedCell,
30
- LocalSelectedPositionMap,
31
- LocalSelectedRowsMap,
32
- } from "./types.js";
26
+ import { CollectedCell } from "./types.js";
33
27
 
34
28
  /**
35
29
  * Apply styles to all body cells in a single pass.
36
30
  */
37
31
  export function applyBodyCellStyles(
38
- this: DatagridModel,
32
+ model: DatagridModel,
39
33
  cells: CollectedCell[],
40
34
  plugins: ColumnsConfig,
41
35
  isSettingsOpen: boolean,
42
36
  isSelectable: boolean,
43
37
  isEditable: boolean,
44
38
  regularTable: RegularTableElement,
45
- selectedRowsMap: LocalSelectedRowsMap,
46
- selectedPositionMap: LocalSelectedPositionMap,
47
- viewer: PerspectiveViewerElement,
48
39
  ): void {
49
- const hasSelected = selectedRowsMap.has(regularTable);
50
- const selected = selectedRowsMap.get(regularTable);
40
+ const selectedId = isSelectable ? model._tree_selection_id : undefined;
51
41
 
52
42
  regularTable.classList.toggle(
53
43
  "flat-group-rollup-mode",
54
- this._config.group_rollup_mode === "flat",
44
+ model._config.group_rollup_mode === "flat",
55
45
  );
56
46
 
57
47
  for (const { element: td, metadata, isHeader } of cells) {
58
48
  const column_name =
59
- metadata.column_header?.[this._config.split_by.length];
60
- const type = get_psp_type(this, metadata);
49
+ metadata.column_header?.[model._config.split_by.length];
50
+ const type = get_psp_type(model, metadata);
61
51
  const plugin = column_name
62
52
  ? plugins[column_name.toString()]
63
53
  : undefined;
@@ -65,34 +55,36 @@ export function applyBodyCellStyles(
65
55
 
66
56
  // Calculate aggregate depth visibility
67
57
  // @ts-ignore
68
- metadata._is_hidden_by_aggregate_depth = ((x?: number) =>
69
- x === 0 || x === undefined
70
- ? false
71
- : x - 1 <
72
- Math.min(
73
- this._config.group_by.length,
74
- plugin?.aggregate_depth || 0,
75
- ))(
76
- (metadata.row_header as unknown[] | undefined)?.filter(
77
- (x) => x !== undefined,
78
- )?.length,
79
- );
58
+ metadata._is_hidden_by_aggregate_depth =
59
+ model._config.group_rollup_mode === "rollup" &&
60
+ ((x?: number) =>
61
+ x === 0 || x === undefined
62
+ ? false
63
+ : x - 1 <
64
+ Math.min(
65
+ model._config.group_by.length,
66
+ plugin?.aggregate_depth || 0,
67
+ ))(
68
+ (metadata.row_header as unknown[] | undefined)?.filter(
69
+ (x) => x !== undefined,
70
+ )?.length,
71
+ );
80
72
 
81
73
  // Apply type-specific cell styling
82
74
  if (is_numeric) {
83
- cell_style_numeric.call(
84
- this,
75
+ cell_style_numeric(
76
+ model,
85
77
  plugin as any,
86
78
  td,
87
79
  metadata as any,
88
80
  isSettingsOpen,
89
81
  );
90
82
  } else if (type === "boolean") {
91
- cell_style_boolean.call(this, plugin, td, metadata as any);
83
+ cell_style_boolean(model, plugin, td, metadata as any);
92
84
  } else if (type === "string") {
93
- cell_style_string.call(this, plugin as any, td, metadata as any);
85
+ cell_style_string(model, plugin as any, td, metadata as any);
94
86
  } else if (type === "date" || type === "datetime") {
95
- cell_style_datetime.call(this, plugin as any, td, metadata as any);
87
+ cell_style_datetime(model, plugin as any, td, metadata);
96
88
  } else {
97
89
  td.style.backgroundColor = "";
98
90
  td.style.color = "";
@@ -107,10 +99,10 @@ export function applyBodyCellStyles(
107
99
  td.classList.toggle("psp-null", metadata.value === null);
108
100
  td.classList.toggle("psp-align-right", !isHeader && is_numeric);
109
101
  td.classList.toggle("psp-align-left", isHeader || !is_numeric);
110
- if (this._column_settings_selected_column) {
102
+ if (model._column_settings_selected_column) {
111
103
  td.classList.toggle(
112
104
  "psp-menu-open",
113
- column_name === this._column_settings_selected_column,
105
+ column_name === model._column_settings_selected_column,
114
106
  );
115
107
  } else {
116
108
  td.classList.toggle("psp-menu-open", false);
@@ -121,9 +113,14 @@ export function applyBodyCellStyles(
121
113
  plugin?.number_fg_mode === "bar" && is_numeric,
122
114
  );
123
115
 
116
+ td.classList.toggle(
117
+ "psp-color-mode-label-bar",
118
+ plugin?.number_fg_mode === "label-bar" && is_numeric,
119
+ );
120
+
124
121
  // Apply row header styling
125
122
  if (isHeader) {
126
- cell_style_row_header.call(this, regularTable, td, metadata as any);
123
+ cell_style_row_header(model, regularTable, td, metadata as any);
127
124
  }
128
125
 
129
126
  // Set data attributes
@@ -150,55 +147,53 @@ export function applyBodyCellStyles(
150
147
  delete td.dataset.x;
151
148
  }
152
149
 
153
- // Apply selection styling (if selectable)
150
+ // Apply tree selection styling (SELECT_ROW_TREE).
151
+ // psp-select-region-inactive is exclusively a tree-selection class,
152
+ // so always clean it up. psp-select-region is shared with the
153
+ // coordinate-based selection modes, so only touch it when in
154
+ // SELECT_ROW_TREE mode (isSelectable).
155
+ td.classList.toggle("psp-select-region-inactive", false);
154
156
  if (isSelectable) {
155
- if (!hasSelected) {
156
- td.classList.toggle("psp-row-selected", false);
157
- td.classList.toggle("psp-row-subselected", false);
157
+ if (!selectedId) {
158
+ td.classList.toggle("psp-select-region", false);
158
159
  } else {
159
- const id = this._ids[(metadata.y ?? 0) - (metadata.y0 ?? 0)];
160
- const key_match = (selected as unknown[]).reduce<boolean>(
160
+ const id = model._ids[(metadata.y ?? 0) - (metadata.y0 ?? 0)];
161
+ const key_match = selectedId.reduce<boolean>(
161
162
  (agg, x, i) => agg && x === id[i],
162
163
  true,
163
164
  );
164
165
 
165
- const selectedArr = selected as unknown[];
166
+ const isExact = id.length === selectedId.length && key_match;
167
+ const isSub = id.length !== selectedId.length && key_match;
168
+
166
169
  if (isHeader) {
167
170
  if (
168
171
  metadata.type === "row_header" &&
169
172
  metadata.row_header_x !== undefined &&
170
173
  !!id[metadata.row_header_x]
171
174
  ) {
172
- td.classList.toggle("psp-row-selected", false);
173
- td.classList.toggle("psp-row-subselected", false);
175
+ td.classList.toggle("psp-select-region", false);
174
176
  } else {
177
+ td.classList.toggle("psp-select-region", isExact);
175
178
  td.classList.toggle(
176
- "psp-row-selected",
177
- id.length === selectedArr.length && key_match,
178
- );
179
- td.classList.toggle(
180
- "psp-row-subselected",
181
- id.length !== selectedArr.length && key_match,
179
+ "psp-select-region-inactive",
180
+ isSub,
182
181
  );
183
182
  }
184
183
  } else {
185
- td.classList.toggle(
186
- "psp-row-selected",
187
- id.length === selectedArr.length && key_match,
188
- );
189
- td.classList.toggle(
190
- "psp-row-subselected",
191
- id.length !== selectedArr.length && key_match,
192
- );
184
+ td.classList.toggle("psp-select-region", isExact);
185
+ td.classList.toggle("psp-select-region-inactive", isSub);
193
186
  }
194
187
  }
188
+ } else {
189
+ td.classList.toggle("psp-select-region", false);
195
190
  }
196
191
 
197
192
  // Apply editable styling (if editable)
198
193
  if (!isHeader && metadata.type === "body") {
199
- if (isEditable && this._is_editable[metadata.x]) {
194
+ if (isEditable && model._is_editable[metadata.x]) {
200
195
  const col_name =
201
- metadata.column_header?.[this._config.split_by.length];
196
+ metadata.column_header?.[model._config.split_by.length];
202
197
  const col_name_str = col_name?.toString();
203
198
  if (
204
199
  col_name_str &&
@@ -11,12 +11,9 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  import { RegularTableElement } from "regular-table";
14
- import {
15
- get_psp_type,
16
- type DatagridModel,
17
- type PerspectiveViewerElement,
18
- } from "../types.js";
14
+ import { get_psp_type, type DatagridModel } from "../types.js";
19
15
  import { CollectedHeaderRow } from "./types.js";
16
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
20
17
 
21
18
  /**
22
19
  * Apply selected column styling in response to column settings toggle events.
@@ -24,9 +21,9 @@ import { CollectedHeaderRow } from "./types.js";
24
21
  * the column settings panel.
25
22
  */
26
23
  export function style_selected_column(
27
- this: DatagridModel,
24
+ model: DatagridModel,
28
25
  regularTable: RegularTableElement,
29
- viewer: PerspectiveViewerElement,
26
+ viewer: HTMLPerspectiveViewerElement,
30
27
  selectedColumn: string | undefined,
31
28
  ): void {
32
29
  const group_header_trs = Array.from(
@@ -72,7 +69,7 @@ export function style_selected_column(
72
69
  const open = title.textContent === selectedColumn;
73
70
  title.classList.toggle("psp-menu-open", open);
74
71
  editBtn.classList.toggle("psp-menu-open", open);
75
- if (this._config.columns.length > 1) {
72
+ if (model._config.columns.length > 1) {
76
73
  for (const r of regularTable.querySelectorAll("td")) {
77
74
  const meta = regularTable.getMeta(r);
78
75
  if (!meta?.column_header) continue;
@@ -92,16 +89,16 @@ export function style_selected_column(
92
89
  * Style a single column header row.
93
90
  */
94
91
  export function styleColumnHeaderRow(
95
- this: DatagridModel,
92
+ model: DatagridModel,
96
93
  headerRow: CollectedHeaderRow,
97
94
  regularTable: RegularTableElement,
98
95
  is_menu_row: boolean,
99
96
  ): void {
100
97
  const header_depth =
101
- this._config.group_by.length -
102
- (this._config.group_rollup_mode === "flat" ? 1 : 0);
98
+ model._config.group_by.length -
99
+ (model._config.group_rollup_mode === "flat" ? 1 : 0);
103
100
 
104
- const selectedColumn = this._column_settings_selected_column;
101
+ const selectedColumn = model._column_settings_selected_column;
105
102
  for (const { element: td, metadata } of headerRow.cells) {
106
103
  if (
107
104
  !metadata ||
@@ -111,15 +108,15 @@ export function styleColumnHeaderRow(
111
108
  continue;
112
109
 
113
110
  const column_name =
114
- metadata.column_header?.[this._config.split_by.length];
111
+ metadata.column_header?.[model._config.split_by.length];
115
112
 
116
- const sort = this._config.sort.find((x) => x[0] === column_name);
113
+ const sort = model._config.sort.find((x) => x[0] === column_name);
117
114
  const is_corner = typeof metadata.x === "undefined";
118
115
  const needs_border =
119
116
  (metadata.type === "corner" &&
120
117
  metadata.row_header_x === header_depth) ||
121
118
  (!is_corner &&
122
- (metadata.x + 1) % this._config.columns.length === 0);
119
+ (metadata.x + 1) % model._config.columns.length === 0);
123
120
 
124
121
  td.classList.toggle("psp-header-border", needs_border);
125
122
  td.classList.toggle("psp-header-group", false);
@@ -159,7 +156,7 @@ export function styleColumnHeaderRow(
159
156
  !is_menu_row && !!sort && sort[1] === "col desc abs",
160
157
  );
161
158
 
162
- const type = get_psp_type(this, metadata);
159
+ const type = get_psp_type(model, metadata);
163
160
  const is_numeric = type === "integer" || type === "float";
164
161
  const is_string = type === "string";
165
162
  const is_date = type === "date";
@@ -171,13 +168,13 @@ export function styleColumnHeaderRow(
171
168
  "psp-menu-enabled",
172
169
  (is_string || is_numeric || is_date || is_datetime) &&
173
170
  !is_corner &&
174
- metadata.column_header_y === this._config.split_by.length + 1,
171
+ metadata.column_header_y === model._config.split_by.length + 1,
175
172
  );
176
173
  td.classList.toggle(
177
174
  "psp-sort-enabled",
178
175
  (is_string || is_numeric || is_date || is_datetime) &&
179
176
  !is_corner &&
180
- metadata.column_header_y === this._config.split_by.length,
177
+ metadata.column_header_y === model._config.split_by.length,
181
178
  );
182
179
  td.classList.toggle(
183
180
  "psp-is-width-override",
@@ -185,7 +182,7 @@ export function styleColumnHeaderRow(
185
182
  );
186
183
 
187
184
  // Apply menu-open for selected column
188
- if (this._config.columns.length > 1 && selectedColumn) {
185
+ if (model._config.columns.length > 1 && selectedColumn) {
189
186
  const isOpen =
190
187
  metadata.column_header?.[metadata.column_header.length - 2] ===
191
188
  selectedColumn;