@perspective-dev/viewer-datagrid 4.2.0 → 4.3.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.
@@ -1,4 +1,4 @@
1
- import type { View, Table, ViewConfig, ColumnType, SortDir, ViewWindow } from "@perspective-dev/client";
1
+ import type { View, Table, ViewConfig, ColumnType, SortDir, ViewWindow, ViewConfigUpdate } from "@perspective-dev/client";
2
2
  import { RegularTableElement } from "regular-table";
3
3
  import { CellMetadata, DataResponse } from "regular-table/dist/esm/types";
4
4
  export type { RegularTableElement as RegularTable };
@@ -158,19 +158,14 @@ export type FormatterCache = Map<string, FormatterCacheEntry>;
158
158
  export interface CellConfigResult {
159
159
  row: Record<string, unknown>;
160
160
  column_names: string[];
161
- config: Partial<ViewConfig>;
161
+ config: ViewConfigUpdate;
162
162
  }
163
163
  export interface PerspectiveClickDetail {
164
164
  row: Record<string, unknown>;
165
165
  column_names: string[];
166
166
  config: Partial<ViewConfig>;
167
167
  }
168
- export interface PerspectiveSelectDetail {
169
- selected: boolean;
170
- row: Record<string, unknown>;
171
- column_names?: string[];
172
- config: Partial<ViewConfig>;
173
- }
168
+ export { PerspectiveSelectDetail } from "@perspective-dev/viewer";
174
169
  export interface HandledMouseEvent extends MouseEvent {
175
170
  handled?: boolean;
176
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/viewer-datagrid",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "Perspective datagrid plugin based on `regular-table`",
5
5
  "unpkg": "dist/cdn/perspective-viewer-datagrid.js",
6
6
  "jsdelivr": "dist/cdn/perspective-viewer-datagrid.js",
@@ -32,7 +32,7 @@
32
32
  "@perspective-dev/client": "",
33
33
  "@perspective-dev/viewer": "",
34
34
  "chroma-js": ">=3 <4",
35
- "regular-table": "=0.8.1"
35
+ "regular-table": "=0.8.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@prospective.co/procss": "0.1.18",
@@ -128,10 +128,20 @@ perspective-viewer {
128
128
  border-color: var(--selected-row--background-color, #ea7319) !important;
129
129
  }
130
130
 
131
+ regular-table.flat-group-rollup-mode.vertical-row-headers
132
+ th.psp-tree-label:not(:last-of-type) {
133
+ writing-mode: vertical-lr;
134
+ }
135
+
131
136
  .psp-row-selected.psp-tree-label:not(:hover):before {
132
137
  color: white;
133
138
  }
134
139
 
140
+ regular-table:not(.flat-group-rollup-mode)
141
+ .psp-row-selected.psp-tree-label:not(:hover):before {
142
+ color: white;
143
+ }
144
+
135
145
  .psp-row-subselected,
136
146
  :hover .psp-row-subselected,
137
147
  :hover th.psp-tree-leaf.psp-row-subselected,
@@ -254,29 +264,36 @@ tbody th:empty {
254
264
  max-width: 20px;
255
265
  pointer-events: none;
256
266
  }
257
- .psp-tree-label {
258
- max-width: 0px;
259
- min-width: 0px;
260
- }
261
- .psp-tree-label:before {
262
- color: var(--icon--color);
263
- font-family: var(--button--font-family, inherit);
264
- padding-right: 11px;
265
- }
266
- .psp-tree-label-expand:before {
267
- content: var(--tree-label-expand--content, "+");
268
- }
269
- .psp-tree-label-collapse:before {
270
- content: var(--tree-label-collapse--content, "-");
271
- }
272
- .psp-tree-label-expand,
273
- .psp-tree-label-collapse {
274
- cursor: pointer;
275
- }
276
267
 
277
- .psp-tree-label:hover:before {
278
- color: var(--active--color);
279
- text-shadow: 0px 0px 5px var(--active--color);
268
+ regular-table:not(.flat-group-rollup-mode) {
269
+ .psp-tree-label {
270
+ max-width: 0px;
271
+ min-width: 0px;
272
+ }
273
+
274
+ .psp-tree-label:before {
275
+ color: var(--icon--color);
276
+ font-family: var(--button--font-family, inherit);
277
+ padding-right: 11px;
278
+ }
279
+
280
+ .psp-tree-label-expand:before {
281
+ content: var(--tree-label-expand--content, "+");
282
+ }
283
+
284
+ .psp-tree-label-collapse:before {
285
+ content: var(--tree-label-collapse--content, "-");
286
+ }
287
+
288
+ .psp-tree-label-expand,
289
+ .psp-tree-label-collapse {
290
+ cursor: pointer;
291
+ }
292
+
293
+ .psp-tree-label:hover:before {
294
+ color: var(--active--color);
295
+ text-shadow: 0px 0px 5px var(--active--color);
296
+ }
280
297
  }
281
298
 
282
299
  .psp-tree-leaf {
@@ -133,6 +133,10 @@ export class HTMLPerspectiveViewerDatagridPluginElement
133
133
  return ["Columns"];
134
134
  }
135
135
 
136
+ get group_rollups(): string[] {
137
+ return ["rollup", "flat", "total"];
138
+ }
139
+
136
140
  /**
137
141
  * Give the Datagrid a higher priority so it is loaded
138
142
  * over the default charts by default.
@@ -179,6 +183,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
179
183
  viewport?.start_row !== null
180
184
  ? viewport.end_row - viewport.start_row
181
185
  : await view.num_rows();
186
+
182
187
  let out = "";
183
188
  for (let ridx = 0; ridx < nrows; ridx++) {
184
189
  for (const col_name of cols) {
@@ -52,6 +52,22 @@ export function* format_tree_header_row_path(
52
52
  }
53
53
  }
54
54
 
55
+ export function* format_flat_header_row_path(
56
+ this: DatagridModel,
57
+ paths: unknown[][] = [],
58
+ row_headers: string[],
59
+ regularTable: RegularTable,
60
+ ): Generator<RowHeaderCell[]> {
61
+ const plugins: ColumnsConfig =
62
+ (regularTable as any)[PRIVATE_PLUGIN_SYMBOL] || {};
63
+
64
+ for (let path of paths) {
65
+ yield path.map((part, i) =>
66
+ format_cell.call(this, row_headers[i], part, plugins, true),
67
+ ) as RowHeaderCell[];
68
+ }
69
+ }
70
+
55
71
  /**
56
72
  * Format a single cell of the `group_by` tree header.
57
73
  */
@@ -13,6 +13,7 @@
13
13
  import { PRIVATE_PLUGIN_SYMBOL } from "../types.js";
14
14
  import { format_cell } from "./format_cell.js";
15
15
  import {
16
+ format_flat_header_row_path,
16
17
  format_tree_header,
17
18
  format_tree_header_row_path,
18
19
  } from "./format_tree_header.js";
@@ -23,7 +24,7 @@ import type {
23
24
  Schema,
24
25
  } from "../types.js";
25
26
  import type { CellScalar, DataResponse } from "regular-table/dist/esm/types.js";
26
- import { ViewWindow } from "@perspective-dev/client";
27
+ import { ViewConfig, ViewWindow } from "@perspective-dev/client";
27
28
 
28
29
  interface ColumnData {
29
30
  __ROW_PATH__?: unknown[][];
@@ -191,8 +192,10 @@ export function createDataListener(
191
192
  column_paths.push(path);
192
193
  }
193
194
 
195
+ const is_dim_call = x1 - x0 > 0 && y1 - y0 > 0;
196
+
194
197
  // Only update the last state if this is not a "phantom" call.
195
- if (x1 - x0 > 0 && y1 - y0 > 0) {
198
+ if (is_dim_call) {
196
199
  this.last_column_paths = last_column_paths;
197
200
  this.last_meta = last_meta;
198
201
  this.last_ids = last_ids;
@@ -207,9 +210,12 @@ export function createDataListener(
207
210
  }
208
211
 
209
212
  const is_row_path = columns.__ROW_PATH__ !== undefined;
213
+ const is_flat = this._config.group_rollup_mode === "flat";
210
214
  const row_headers = Array.from(
211
215
  (is_row_path
212
- ? format_tree_header_row_path
216
+ ? is_flat
217
+ ? format_flat_header_row_path
218
+ : format_tree_header_row_path
213
219
  : format_tree_header
214
220
  ).call(
215
221
  this,
@@ -219,7 +225,9 @@ export function createDataListener(
219
225
  ),
220
226
  ) as (string | HTMLElement)[][];
221
227
 
222
- const num_row_headers = row_headers[0]?.length;
228
+ const num_row_headers = !is_dim_call
229
+ ? row_header_depth(this._config)
230
+ : row_headers[0]?.length;
223
231
 
224
232
  const result: DataResponse = {
225
233
  num_column_headers:
@@ -240,3 +248,15 @@ export function createDataListener(
240
248
  return result;
241
249
  };
242
250
  }
251
+
252
+ function row_header_depth(config: ViewConfig) {
253
+ if (config.group_rollup_mode === "flat") {
254
+ return config.group_by.length;
255
+ } else if (config.group_rollup_mode === "total") {
256
+ return 0;
257
+ } else if (config.group_by.length === 0) {
258
+ return 0;
259
+ } else {
260
+ return config.group_by.length + 1;
261
+ }
262
+ }
@@ -11,11 +11,11 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  import getCellConfig from "../get_cell_config.js";
14
- import type {
15
- RegularTable,
16
- DatagridModel,
17
- PerspectiveViewerElement,
18
- HandledMouseEvent,
14
+ import {
15
+ type RegularTable,
16
+ type DatagridModel,
17
+ type PerspectiveViewerElement,
18
+ type HandledMouseEvent,
19
19
  PerspectiveSelectDetail,
20
20
  } from "../types.js";
21
21
 
@@ -52,28 +52,31 @@ export async function selectionListener(
52
52
  const is_deselect =
53
53
  !!selected && id.length === selected.length && key_match;
54
54
 
55
- let detail: PerspectiveSelectDetail = {
56
- selected: !is_deselect,
57
- row: {},
58
- config: { filter: [] },
59
- };
60
-
61
55
  const { row, column_names, config } = await getCellConfig(
62
56
  this,
63
57
  meta.y,
64
58
  meta.type === "body" ? meta.x : 0,
65
59
  );
66
60
 
61
+ let detail: PerspectiveSelectDetail;
67
62
  if (is_deselect) {
68
63
  selected_rows_map.delete(regularTable);
69
- detail = {
70
- ...detail,
64
+ detail = new PerspectiveSelectDetail(
65
+ false,
71
66
  row,
72
- config: { filter: structuredClone(this._config.filter) },
73
- };
67
+ [],
68
+ [],
69
+ [{ filter: structuredClone(this._config.filter) }],
70
+ );
74
71
  } else {
75
72
  selected_rows_map.set(regularTable, id);
76
- detail = { ...detail, row, column_names, config };
73
+ detail = new PerspectiveSelectDetail(
74
+ true,
75
+ row,
76
+ column_names,
77
+ [],
78
+ [config],
79
+ );
77
80
  }
78
81
 
79
82
  await regularTable.draw({ preserve_width: true });
@@ -28,13 +28,13 @@ const ROW_SORT_ORDER: SortRotationOrder = {
28
28
 
29
29
  const ROW_COL_SORT_ORDER: SortRotationOrder = {
30
30
  desc: "asc",
31
- asc: "col desc",
31
+ asc: undefined,
32
32
  "desc abs": "asc abs",
33
- "asc abs": "col desc abs",
34
- "col desc": "col asc",
35
- "col asc": undefined,
36
- "col desc abs": "col asc abs",
37
- "col asc abs": undefined,
33
+ "asc abs": undefined,
34
+ // "col desc": "col asc",
35
+ // "col asc": undefined,
36
+ // "col desc abs": "col asc abs",
37
+ // "col asc abs": undefined,
38
38
  };
39
39
 
40
40
  export async function sortHandler(
@@ -49,7 +49,7 @@ export async function sortHandler(
49
49
  const column_name = meta.column_header[this._config.split_by.length];
50
50
  const sort_method =
51
51
  event.ctrlKey ||
52
- (event as MouseEvent & { metaKet?: boolean }).metaKet ||
52
+ (event as MouseEvent & { metaKey?: boolean }).metaKey ||
53
53
  event.altKey
54
54
  ? append_sort
55
55
  : override_sort;
@@ -97,6 +97,7 @@ export function override_sort(
97
97
  return sort ? [sort] : [];
98
98
  }
99
99
  }
100
+
100
101
  return [[column_name, abs ? "desc abs" : "desc"]];
101
102
  }
102
103
 
@@ -111,8 +112,10 @@ export function create_sort(
111
112
  const inc_sort_dir: SortDir | undefined = sort_dir
112
113
  ? order[sort_dir]
113
114
  : "desc";
115
+
114
116
  if (inc_sort_dir) {
115
117
  return [column_name, inc_sort_dir];
116
118
  }
119
+
117
120
  return undefined;
118
121
  }
@@ -87,21 +87,21 @@ export async function createModel(
87
87
  }
88
88
 
89
89
  let split_by_changed = old.split_by.length !== config.split_by.length;
90
- if (split_by_changed) {
90
+ if (!split_by_changed) {
91
91
  for (const lvl in old.split_by) {
92
92
  split_by_changed ||= config.split_by[lvl] !== old.split_by[lvl];
93
93
  }
94
94
  }
95
95
 
96
96
  let columns_changed = old.columns.length !== config.columns.length;
97
- if (columns_changed) {
97
+ if (!columns_changed) {
98
98
  for (const lvl in old.columns) {
99
99
  columns_changed ||= config.columns[lvl] !== old.columns[lvl];
100
100
  }
101
101
  }
102
102
 
103
103
  let filter_changed = old.filter.length !== config.filter.length;
104
- if (filter_changed) {
104
+ if (!filter_changed) {
105
105
  for (const lvl in old.filter) {
106
106
  for (const i in config.filter[lvl]) {
107
107
  filter_changed ||=
@@ -112,7 +112,7 @@ export async function createModel(
112
112
  }
113
113
 
114
114
  let sort_changed = old.sort.length !== config.sort.length;
115
- if (sort_changed) {
115
+ if (!sort_changed) {
116
116
  for (const lvl in old.sort) {
117
117
  for (const i in config.sort[lvl]) {
118
118
  sort_changed ||=
@@ -122,6 +122,9 @@ export async function createModel(
122
122
  }
123
123
  }
124
124
 
125
+ const group_rollup_mode_changed =
126
+ old.group_rollup_mode !== config.group_rollup_mode;
127
+
125
128
  this._reset_scroll_top = group_by_changed;
126
129
  this._reset_scroll_left = split_by_changed;
127
130
  this._reset_select =
@@ -132,6 +135,7 @@ export async function createModel(
132
135
  columns_changed;
133
136
 
134
137
  this._reset_column_size =
138
+ group_rollup_mode_changed ||
135
139
  split_by_changed ||
136
140
  group_by_changed ||
137
141
  columns_changed ||
@@ -37,6 +37,7 @@ export async function draw(
37
37
  const drawPromise = this.regular_table.draw({
38
38
  invalid_columns: true,
39
39
  } as any);
40
+
40
41
  if (this._reset_scroll_top) {
41
42
  this.regular_table.scrollTop = 0;
42
43
  this._reset_scroll_top = false;
@@ -49,6 +49,11 @@ export function applyBodyCellStyles(
49
49
  const hasSelected = selectedRowsMap.has(regularTable);
50
50
  const selected = selectedRowsMap.get(regularTable);
51
51
 
52
+ regularTable.classList.toggle(
53
+ "flat-group-rollup-mode",
54
+ this._config.group_rollup_mode === "flat",
55
+ );
56
+
52
57
  for (const { element: td, metadata, isHeader } of cells) {
53
58
  const column_name =
54
59
  metadata.column_header?.[this._config.split_by.length];
@@ -97,9 +97,11 @@ export function styleColumnHeaderRow(
97
97
  regularTable: RegularTableElement,
98
98
  is_menu_row: boolean,
99
99
  ): void {
100
- const header_depth = this._config.group_by.length;
101
- const selectedColumn = this._column_settings_selected_column;
100
+ const header_depth =
101
+ this._config.group_by.length -
102
+ (this._config.group_rollup_mode === "flat" ? 1 : 0);
102
103
 
104
+ const selectedColumn = this._column_settings_selected_column;
103
105
  for (const { element: td, metadata } of headerRow.cells) {
104
106
  if (
105
107
  !metadata ||
@@ -110,14 +112,13 @@ export function styleColumnHeaderRow(
110
112
 
111
113
  const column_name =
112
114
  metadata.column_header?.[this._config.split_by.length];
115
+
113
116
  const sort = this._config.sort.find((x) => x[0] === column_name);
114
- let needs_border =
115
- metadata.type === "corner" &&
116
- metadata.row_header_x === header_depth;
117
117
  const is_corner = typeof metadata.x === "undefined";
118
- needs_border =
119
- needs_border ||
120
- (metadata.x !== undefined &&
118
+ const needs_border =
119
+ (metadata.type === "corner" &&
120
+ metadata.row_header_x === header_depth) ||
121
+ (!is_corner &&
121
122
  (metadata.x + 1) % this._config.columns.length === 0);
122
123
 
123
124
  td.classList.toggle("psp-header-border", needs_border);
package/src/ts/types.ts CHANGED
@@ -17,6 +17,7 @@ import type {
17
17
  ColumnType,
18
18
  SortDir,
19
19
  ViewWindow,
20
+ ViewConfigUpdate,
20
21
  } from "@perspective-dev/client";
21
22
  import { RegularTableElement } from "regular-table";
22
23
  import { CellMetadata, DataResponse } from "regular-table/dist/esm/types";
@@ -244,7 +245,7 @@ export type FormatterCache = Map<string, FormatterCacheEntry>;
244
245
  export interface CellConfigResult {
245
246
  row: Record<string, unknown>;
246
247
  column_names: string[];
247
- config: Partial<ViewConfig>;
248
+ config: ViewConfigUpdate;
248
249
  }
249
250
 
250
251
  // Custom event detail types
@@ -254,12 +255,7 @@ export interface PerspectiveClickDetail {
254
255
  config: Partial<ViewConfig>;
255
256
  }
256
257
 
257
- export interface PerspectiveSelectDetail {
258
- selected: boolean;
259
- row: Record<string, unknown>;
260
- column_names?: string[];
261
- config: Partial<ViewConfig>;
262
- }
258
+ export { PerspectiveSelectDetail } from "@perspective-dev/viewer";
263
259
 
264
260
  // Mouse event with handled flag
265
261
  export interface HandledMouseEvent extends MouseEvent {