@perspective-dev/viewer-datagrid 4.4.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 (80) 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 +1 -1
  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 +2 -4
  39. package/src/css/regular_table.css +87 -31
  40. package/src/css/row-hover.css +20 -7
  41. package/src/css/toolbar.css +11 -0
  42. package/src/ts/color_utils.ts +144 -16
  43. package/src/ts/custom_elements/datagrid.ts +11 -12
  44. package/src/ts/custom_elements/toolbar.ts +4 -5
  45. package/src/ts/data_listener/format_cell.ts +28 -9
  46. package/src/ts/data_listener/formatter_cache.ts +1 -1
  47. package/src/ts/data_listener/index.ts +4 -8
  48. package/src/ts/event_handlers/click/edit_click.ts +7 -6
  49. package/src/ts/event_handlers/click.ts +39 -68
  50. package/src/ts/event_handlers/dispatch_click.ts +24 -25
  51. package/src/ts/event_handlers/expand_collapse.ts +7 -7
  52. package/src/ts/event_handlers/focus.ts +38 -35
  53. package/src/ts/event_handlers/header_click.ts +101 -62
  54. package/src/ts/event_handlers/keydown/edit_keydown.ts +49 -52
  55. package/src/ts/event_handlers/select_region.ts +144 -133
  56. package/src/ts/event_handlers/sort.ts +16 -24
  57. package/src/ts/model/column_overrides.ts +13 -4
  58. package/src/ts/model/create.ts +51 -55
  59. package/src/ts/model/toolbar.ts +23 -7
  60. package/src/ts/plugin/activate.ts +120 -92
  61. package/src/ts/plugin/column_style_controls.ts +1 -1
  62. package/src/ts/plugin/save.ts +1 -0
  63. package/src/ts/style_handlers/body.ts +44 -51
  64. package/src/ts/style_handlers/column_header.ts +16 -19
  65. package/src/ts/style_handlers/consolidated.ts +22 -123
  66. package/src/ts/style_handlers/editable.ts +10 -8
  67. package/src/ts/style_handlers/focus.ts +5 -5
  68. package/src/ts/style_handlers/group_header.ts +3 -2
  69. package/src/ts/style_handlers/table_cell/boolean.ts +3 -3
  70. package/src/ts/style_handlers/table_cell/cell_flash.ts +11 -11
  71. package/src/ts/style_handlers/table_cell/datetime.ts +3 -3
  72. package/src/ts/style_handlers/table_cell/numeric.ts +24 -25
  73. package/src/ts/style_handlers/table_cell/row_header.ts +2 -2
  74. package/src/ts/style_handlers/table_cell/string.ts +20 -18
  75. package/src/ts/style_handlers/types.ts +0 -10
  76. package/src/ts/types.ts +28 -20
  77. package/dist/esm/event_handlers/deselect_all.d.ts +0 -5
  78. package/dist/esm/event_handlers/row_select_click.d.ts +0 -4
  79. package/src/ts/event_handlers/deselect_all.ts +0 -28
  80. package/src/ts/event_handlers/row_select_click.ts +0 -92
@@ -10,15 +10,145 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- import chroma from "chroma-js";
14
13
  import type { ColorRecord } from "./types.js";
15
14
 
15
+ /** 8-bit sRGB color as `[r, g, b]` with each channel in `[0, 255]`. */
16
+ export type RGB = [number, number, number];
17
+
18
+ /** HSL color as `[h, s, l]` with `h` in degrees `[0, 360)` and `s`, `l` in `[0, 1]`. */
19
+ export type HSL = [number, number, number];
20
+
21
+ const parse_cache = new Map<string, RGB>();
22
+ let parse_ctx: CanvasRenderingContext2D | null = null;
23
+
24
+ /** Parse a CSS hex color (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`). Returns `null` if `input` is not a hex literal. Alpha is ignored. */
25
+ function parse_hex(input: string): RGB | null {
26
+ const s = input.startsWith("#") ? input.slice(1) : input;
27
+ if (s.length === 3 || s.length === 4) {
28
+ const r = parseInt(s[0] + s[0], 16);
29
+ const g = parseInt(s[1] + s[1], 16);
30
+ const b = parseInt(s[2] + s[2], 16);
31
+ if (!isNaN(r) && !isNaN(g) && !isNaN(b)) return [r, g, b];
32
+ } else if (s.length === 6 || s.length === 8) {
33
+ const r = parseInt(s.slice(0, 2), 16);
34
+ const g = parseInt(s.slice(2, 4), 16);
35
+ const b = parseInt(s.slice(4, 6), 16);
36
+ if (!isNaN(r) && !isNaN(g) && !isNaN(b)) return [r, g, b];
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /** Parse a CSS `rgb()` or `rgba()` functional color. Returns `null` if `input` does not match. Alpha is ignored. */
42
+ function parse_rgb_fn(input: string): RGB | null {
43
+ const m = input.match(
44
+ /^rgba?\(\s*([\d.]+)\s*[, ]\s*([\d.]+)\s*[, ]\s*([\d.]+)/i,
45
+ );
46
+ if (!m) return null;
47
+ return [
48
+ Math.round(parseFloat(m[1])),
49
+ Math.round(parseFloat(m[2])),
50
+ Math.round(parseFloat(m[3])),
51
+ ];
52
+ }
53
+
54
+ /** Fallback parser that defers to the browser by assigning `input` to a 2D canvas `fillStyle` and re-reading the normalized value. Handles named colors, `hsl()`, etc. Returns `[0, 0, 0]` if the value is invalid or no canvas context is available. */
55
+ function parse_via_canvas(input: string): RGB {
56
+ if (!parse_ctx) {
57
+ const canvas = document.createElement("canvas");
58
+ canvas.width = canvas.height = 1;
59
+ parse_ctx = canvas.getContext("2d");
60
+ }
61
+ if (!parse_ctx) return [0, 0, 0];
62
+ parse_ctx.fillStyle = "#000";
63
+ parse_ctx.fillStyle = input;
64
+ const normalized = parse_ctx.fillStyle as string;
65
+ return parse_hex(normalized) ?? parse_rgb_fn(normalized) ?? [0, 0, 0];
66
+ }
67
+
68
+ /** Parse any CSS color string into an `RGB` triple. Tries hex and `rgb()` fast paths, then falls back to a canvas-based parser for named colors, `hsl()`, etc. Results are memoized per input. */
69
+ export function parseColor(input: string): RGB {
70
+ const key = input.trim();
71
+ const cached = parse_cache.get(key);
72
+ if (cached) return cached;
73
+ const rgb = parse_hex(key) ?? parse_rgb_fn(key) ?? parse_via_canvas(key);
74
+ parse_cache.set(key, rgb);
75
+ return rgb;
76
+ }
77
+
78
+ /** Format a single channel as a clamped, zero-padded two-digit hex byte. */
79
+ function toHex(c: number): string {
80
+ const v = Math.max(0, Math.min(255, Math.round(c)));
81
+ return v.toString(16).padStart(2, "0");
82
+ }
83
+
84
+ /** Format an `RGB` triple as a `#rrggbb` hex string. Channels are clamped to `[0, 255]`. */
85
+ export function rgbToHex([r, g, b]: RGB): string {
86
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
87
+ }
88
+
89
+ /** Convert sRGB to HSL. Output `h` is in degrees `[0, 360)`; `s` and `l` are in `[0, 1]`. */
90
+ export function rgbToHsl([r, g, b]: RGB): HSL {
91
+ const rn = r / 255,
92
+ gn = g / 255,
93
+ bn = b / 255;
94
+ const max = Math.max(rn, gn, bn);
95
+ const min = Math.min(rn, gn, bn);
96
+ const l = (max + min) / 2;
97
+ const d = max - min;
98
+ let h = 0;
99
+ let s = 0;
100
+ if (d !== 0) {
101
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
102
+ if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) * 60;
103
+ else if (max === gn) h = ((bn - rn) / d + 2) * 60;
104
+ else h = ((rn - gn) / d + 4) * 60;
105
+ }
106
+ return [h, s, l];
107
+ }
108
+
109
+ /** Convert HSL to sRGB. `h` is wrapped into `[0, 360)`; `s` and `l` are expected in `[0, 1]`. Output channels are rounded to integers in `[0, 255]`. */
110
+ export function hslToRgb([h, s, l]: HSL): RGB {
111
+ const hn = (((h % 360) + 360) % 360) / 360;
112
+ if (s === 0) {
113
+ const v = Math.round(l * 255);
114
+ return [v, v, v];
115
+ }
116
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
117
+ const p = 2 * l - q;
118
+ const f = (t: number): number => {
119
+ if (t < 0) t += 1;
120
+ if (t > 1) t -= 1;
121
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
122
+ if (t < 1 / 2) return q;
123
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
124
+ return p;
125
+ };
126
+ return [
127
+ Math.round(f(hn + 1 / 3) * 255),
128
+ Math.round(f(hn) * 255),
129
+ Math.round(f(hn - 1 / 3) * 255),
130
+ ];
131
+ }
132
+
133
+ /**
134
+ * Blend two `RGB` colors using LRGB (gamma-naive linear) interpolation,
135
+ * matching chroma-js's default `mix` mode. `f` is the weight of `b` in `[0, 1]`
136
+ * (0 returns `a`, 1 returns `b`).
137
+ */
138
+ export function mixRgb(a: RGB, b: RGB, f = 0.5): RGB {
139
+ return [
140
+ Math.round(Math.sqrt(a[0] * a[0] * (1 - f) + b[0] * b[0] * f)),
141
+ Math.round(Math.sqrt(a[1] * a[1] * (1 - f) + b[1] * b[1] * f)),
142
+ Math.round(Math.sqrt(a[2] * a[2] * (1 - f) + b[2] * b[2] * f)),
143
+ ];
144
+ }
145
+
146
+ /** 50/50 LRGB blend of CSS color `a` with `RGB`-ish triple `b`, returned as `#rrggbb`. */
16
147
  export function blend(a: string, b: number[]): string {
17
- return chroma.mix(a, `rgb(${b[0]},${b[1]},${b[2]})`, 0.5).hex();
148
+ return rgbToHex(mixRgb(parseColor(a), [b[0], b[1], b[2]], 0.5));
18
149
  }
19
150
 
20
- // AFAICT `chroma-js` has no alpha-aware blending? So we need a function to get
21
- // the color of a heatmap cell over the background.
151
+ /** Composite a premultiplied-style `RGBA` cell color over `source` (default white) and return the resulting opaque `RGB`. Used to flatten heatmap cells against the background. */
22
152
  export function rgbaToRgb(
23
153
  [r, g, b, a]: [number, number, number, number],
24
154
  source: [number, number, number] = [255, 255, 255],
@@ -30,7 +160,7 @@ export function rgbaToRgb(
30
160
  return [f(0, r), f(1, g), f(2, b)];
31
161
  }
32
162
 
33
- // Chroma does this but why bother?
163
+ /** Pick a readable foreground (`#161616` or `#ffffff`) for the given background using a perceptual luminance threshold. */
34
164
  export function infer_foreground_from_background([r, g, b]: [
35
165
  number,
36
166
  number,
@@ -42,21 +172,19 @@ export function infer_foreground_from_background([r, g, b]: [
42
172
  : "#ffffff";
43
173
  }
44
174
 
45
- function make_gradient(chromahex: chroma.Color): string {
46
- const [r, g, b] = chromahex.rgb();
47
- const [r1, g1, b1] = chromahex
48
- .set("hsl.h", (chromahex.get("hsl.h") - 15) % 360)
49
- .rgb();
50
- const [r2, g2, b2] = chromahex
51
- .set("hsl.h", (chromahex.get("hsl.h") + 15) % 360)
52
- .rgb();
175
+ /** Build a CSS `linear-gradient` that fans `rgb` ±15° in hue, used as the negative-value swatch in column color pickers. */
176
+ function make_gradient(rgb: RGB): string {
177
+ const [h, s, l] = rgbToHsl(rgb);
178
+ const [r, g, b] = rgb;
179
+ const [r1, g1, b1] = hslToRgb([h - 15, s, l]);
180
+ const [r2, g2, b2] = hslToRgb([h + 15, s, l]);
53
181
  return `linear-gradient(to right top,rgb(${r1},${g1},${b1}),rgb(${r},${g},${b}) 50%,rgb(${r2},${g2},${b2}))`;
54
182
  }
55
183
 
184
+ /** Precompute the tuple of derived color strings (RGB channels, gradient, opaque/transparent rgba) cached on the model for a configured plugin color. */
56
185
  export function make_color_record(color: string): ColorRecord {
57
- const chroma_neg = chroma(color);
58
- const _neg_grad = make_gradient(chroma_neg);
59
- const rgb = chroma_neg.rgb();
186
+ const rgb = parseColor(color);
187
+ const _neg_grad = make_gradient(rgb);
60
188
  return [
61
189
  color,
62
190
  rgb[0],
@@ -22,12 +22,14 @@ import datagridStyles from "../../../dist/css/perspective-viewer-datagrid.css";
22
22
  import { format_raw } from "../data_listener/format_cell.js";
23
23
 
24
24
  import type { View, ViewWindow } from "@perspective-dev/client";
25
- import type { IPerspectiveViewerPlugin } from "@perspective-dev/viewer";
25
+ import type {
26
+ HTMLPerspectiveViewerElement,
27
+ IPerspectiveViewerPlugin,
28
+ } from "@perspective-dev/viewer";
26
29
  import type {
27
30
  DatagridModel,
28
31
  DatagridToolbarElement,
29
32
  EditMode,
30
- PerspectiveViewerElement,
31
33
  DatagridPluginConfig,
32
34
  ColumnsConfig,
33
35
  } from "../types.js";
@@ -171,7 +173,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
171
173
  }
172
174
 
173
175
  async render(viewport?: ViewWindow): Promise<string> {
174
- const viewer = this.parentElement as PerspectiveViewerElement;
176
+ const viewer = this.parentElement as HTMLPerspectiveViewerElement;
175
177
  const view = await viewer.getView();
176
178
  const json = await view.to_columns(viewport as any);
177
179
  const cols = await view.column_paths(viewport as any);
@@ -210,7 +212,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
210
212
  return out.trim();
211
213
  }
212
214
 
213
- async resize(): Promise<void> {
215
+ async resize(_view: View): Promise<void> {
214
216
  if (!this.isConnected || this.offsetParent == null) {
215
217
  return;
216
218
  }
@@ -225,25 +227,22 @@ export class HTMLPerspectiveViewerDatagridPluginElement
225
227
  this.regular_table.clear();
226
228
  }
227
229
 
228
- async save(): Promise<any> {
230
+ save(): any {
229
231
  return save.call(this);
230
232
  }
231
233
 
232
- async restore(
233
- token: DatagridPluginConfig,
234
- columns_config?: ColumnsConfig,
235
- ): Promise<any> {
234
+ restore(token: DatagridPluginConfig, columns_config?: ColumnsConfig): void {
236
235
  return restore.call(this, token, columns_config ?? {});
237
236
  }
238
237
 
239
- async restyle(): Promise<void> {
238
+ async restyle(view: View): Promise<void> {
240
239
  // Get view from model if available, otherwise no-op
241
240
  if (this.model?._view) {
242
- await this.draw(this.model._view);
241
+ await this.draw(view);
243
242
  }
244
243
  }
245
244
 
246
- async delete(): Promise<void> {
245
+ delete(): void {
247
246
  this.disconnectedCallback();
248
247
  this._toolbar = undefined;
249
248
  if ((this.regular_table as any).table_model) {
@@ -10,12 +10,10 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
13
14
  import TOOLBAR_STYLE from "../../../dist/css/perspective-viewer-datagrid-toolbar.css";
14
15
  import { toggle_edit_mode, toggle_scroll_lock } from "../model/toolbar.js";
15
- import type {
16
- DatagridPluginElement,
17
- PerspectiveViewerElement,
18
- } from "../types.js";
16
+ import type { DatagridPluginElement } from "../types.js";
19
17
 
20
18
  const stylesheet = new CSSStyleSheet();
21
19
  stylesheet.replaceSync(TOOLBAR_STYLE);
@@ -53,7 +51,7 @@ export class HTMLPerspectiveViewerDatagridToolbarElement extends HTMLElement {
53
51
  </div>
54
52
  `;
55
53
 
56
- const viewer = this.parentElement as PerspectiveViewerElement;
54
+ const viewer = this.parentElement as HTMLPerspectiveViewerElement;
57
55
  const plugin = this.previousElementSibling as DatagridPluginElement;
58
56
 
59
57
  plugin._scroll_lock = this.shadowRoot!.querySelector(
@@ -66,6 +64,7 @@ export class HTMLPerspectiveViewerDatagridToolbarElement extends HTMLElement {
66
64
  plugin._edit_button = this.shadowRoot!.querySelector(
67
65
  "#edit_mode",
68
66
  ) as HTMLElement;
67
+
69
68
  plugin._edit_button.addEventListener("click", () => {
70
69
  toggle_edit_mode.call(plugin);
71
70
  plugin.regular_table.draw();
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { FormatterCache, Formatter } from "./formatter_cache.js";
14
14
  import type { DatagridModel, ColumnsConfig, ColumnConfig } from "../types.js";
15
- import { ColumnType } from "@perspective-dev/client";
15
+ import type { ColumnType } from "@perspective-dev/client";
16
16
 
17
17
  const FORMAT_CACHE = new FormatterCache();
18
18
  const MAX_BAR_WIDTH_PCT = 1;
@@ -44,7 +44,11 @@ export function format_cell(
44
44
  const plugin: ColumnConfig = plugins[title] || {};
45
45
  const is_numeric = type === "integer" || type === "float";
46
46
 
47
- if (is_numeric && plugin?.number_fg_mode === "bar") {
47
+ if (
48
+ is_numeric &&
49
+ (plugin?.number_fg_mode === "bar" ||
50
+ plugin?.number_fg_mode === "label-bar")
51
+ ) {
48
52
  const a = Math.max(
49
53
  0,
50
54
  Math.min(
@@ -54,15 +58,30 @@ export function format_cell(
54
58
  ),
55
59
  );
56
60
 
57
- const div = this._div_factory.get();
58
- const anchor = (val as number) >= 0 ? "left" : "right";
61
+ const anchor = (val as number) >= 0 ? "" : "justify-self:flex-end;";
59
62
  const pct = (a * 100).toFixed(2);
60
- div.setAttribute(
61
- "style",
62
- `width:calc(${pct}% - 4px);position:absolute;${anchor}:2px;height:80%;top:10%;pointer-events:none;`,
63
- );
64
63
 
65
- return div;
64
+ if (plugin.number_fg_mode === "bar") {
65
+ const div = this._div_factory.get();
66
+ div.className = "psp-bar";
67
+ div.setAttribute(
68
+ "style",
69
+ `${anchor}width:${pct}%;height:80%;top:10%;pointer-events:none;background:var(--psp-label-bar-color)`,
70
+ );
71
+
72
+ return div;
73
+ } else {
74
+ const formatter = FORMAT_CACHE.get(type, plugin);
75
+ const label = formatter ? formatter.format(val) : (val as string);
76
+
77
+ const div = this._div_factory.get();
78
+ div.className = "psp-bar";
79
+ div.setAttribute(
80
+ "style",
81
+ `--label:"${label}";${anchor}width:${pct}%;height:80%;top:10%;pointer-events:none;background:var(--psp-label-bar-color)`,
82
+ );
83
+ return div;
84
+ }
66
85
  } else if (plugin?.format === "link" && type === "string") {
67
86
  const anchor = document.createElement("a");
68
87
  anchor.setAttribute("href", val as string);
@@ -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 { ColumnConfig } from "../types.js";
15
15
 
16
16
  export interface Formatter {
@@ -17,14 +17,10 @@ import {
17
17
  format_tree_header,
18
18
  format_tree_header_row_path,
19
19
  } from "./format_tree_header.js";
20
- import type {
21
- DatagridModel,
22
- PerspectiveViewerElement,
23
- RegularTable,
24
- Schema,
25
- } from "../types.js";
20
+ import type { DatagridModel, RegularTable, Schema } from "../types.js";
26
21
  import type { CellScalar, DataResponse } from "regular-table/dist/esm/types.js";
27
- import { ViewConfig, ViewWindow } from "@perspective-dev/client";
22
+ import type { ViewConfig, ViewWindow } from "@perspective-dev/client";
23
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
28
24
 
29
25
  interface ColumnData {
30
26
  __ROW_PATH__?: unknown[][];
@@ -40,7 +36,7 @@ interface ColumnData {
40
36
  * @returns A data listener for the plugin.
41
37
  */
42
38
  export function createDataListener(
43
- viewer: PerspectiveViewerElement,
39
+ viewer: HTMLPerspectiveViewerElement,
44
40
  ): (
45
41
  regularTable: RegularTable,
46
42
  x0: number,
@@ -14,10 +14,11 @@ import { CellMetadataBody } from "regular-table/dist/esm/types.js";
14
14
  import {
15
15
  type RegularTable,
16
16
  type DatagridModel,
17
- type PerspectiveViewerElement,
18
17
  get_psp_type,
19
18
  } from "../../types.js";
20
19
 
20
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
21
+
21
22
  export function write_cell(
22
23
  table: RegularTable,
23
24
  model: DatagridModel,
@@ -56,21 +57,21 @@ export function write_cell(
56
57
  }
57
58
 
58
59
  export function clickListener(
59
- this: DatagridModel,
60
+ model: DatagridModel,
60
61
  table: RegularTable,
61
- _viewer: PerspectiveViewerElement,
62
+ _viewer: HTMLPerspectiveViewerElement,
62
63
  event: MouseEvent,
63
64
  ): void {
64
65
  const meta = table.getMeta(event.target as HTMLElement);
65
66
  if (meta?.type === "body" || meta?.type === "column_header") {
66
- const is_editable2 = this._is_editable[meta.x];
67
- const is_bool = get_psp_type(this, meta) === "boolean";
67
+ const is_editable2 = model._is_editable[meta.x];
68
+ const is_bool = get_psp_type(model, meta) === "boolean";
68
69
  const is_null = (event.target as Element).classList.contains(
69
70
  "psp-null",
70
71
  );
71
72
 
72
73
  if (is_editable2 && is_bool && !is_null) {
73
- write_cell(table, this, event.target as HTMLElement);
74
+ write_cell(table, model, event.target as HTMLElement);
74
75
  }
75
76
  }
76
77
  }
@@ -12,81 +12,52 @@
12
12
 
13
13
  import * as edit_click from "./click/edit_click.js";
14
14
  import * as edit_keydown from "./keydown/edit_keydown.js";
15
- import type {
16
- DatagridModel,
17
- PerspectiveViewerElement,
18
- SelectedPosition,
19
- } from "../types.js";
15
+ import type { DatagridModel, SelectedPositionMap } from "../types.js";
16
+ import { isEditableMode } from "../types.js";
20
17
  import { RegularTableElement } from "regular-table";
21
- import { HTMLPerspectiveViewerDatagridPluginElement } from "../custom_elements/datagrid.js";
18
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
22
19
 
23
- type SelectedPositionMap = Map<RegularTableElement, SelectedPosition>;
24
-
25
- export function is_editable(
26
- this: DatagridModel,
27
- viewer: PerspectiveViewerElement,
28
- allowed: boolean = false,
29
- ): boolean {
30
- const has_pivots =
31
- this._config.group_by.length === 0 &&
32
- this._config.split_by.length === 0;
33
- const selectable = viewer.hasAttribute("selectable");
34
- const plugin = viewer.children[0] as
35
- | HTMLPerspectiveViewerDatagridPluginElement
36
- | undefined;
37
- const editable = allowed || !!(plugin?._edit_mode === "EDIT");
38
- return has_pivots && !selectable && editable;
39
- }
40
-
41
- export function keydownListener(
42
- this: DatagridModel,
20
+ export function createKeydownListener(
21
+ model: DatagridModel,
43
22
  table: RegularTableElement,
44
- viewer: PerspectiveViewerElement,
23
+ viewer: HTMLPerspectiveViewerElement,
45
24
  selected_position_map: SelectedPositionMap,
46
- event: KeyboardEvent,
47
- ): void {
48
- if (this._edit_mode === "EDIT") {
49
- if (!is_editable.call(this, viewer)) {
50
- return;
51
- }
25
+ ): EventListener {
26
+ return (event: Event): void => {
27
+ const keyEvent = event as KeyboardEvent;
28
+ if (model._edit_mode === "EDIT") {
29
+ if (!isEditableMode(model, viewer)) {
30
+ return;
31
+ }
52
32
 
53
- edit_keydown.keydownListener.call(
54
- this,
55
- table,
56
- viewer,
57
- selected_position_map,
58
- event,
59
- );
60
- } else {
61
- console.debug(
62
- `Mode ${this._edit_mode} for "keydown" event not yet implemented`,
63
- );
64
- }
33
+ edit_keydown.keydownListener(
34
+ model,
35
+ table,
36
+ viewer,
37
+ selected_position_map,
38
+ keyEvent,
39
+ );
40
+ } else {
41
+ console.debug(
42
+ `Mode ${model._edit_mode} for "keydown" event not yet implemented`,
43
+ );
44
+ }
45
+ };
65
46
  }
66
47
 
67
- export function clickListener(
68
- this: DatagridModel,
48
+ export function createEditClickListener(
49
+ model: DatagridModel,
69
50
  table: RegularTableElement,
70
- viewer: PerspectiveViewerElement,
71
- event: MouseEvent,
72
- ): void {
73
- if (this._edit_mode === "EDIT") {
74
- if (!is_editable.call(this, viewer)) {
75
- return;
76
- }
51
+ viewer: HTMLPerspectiveViewerElement,
52
+ ): EventListener {
53
+ return (event: Event): void => {
54
+ const mouseEvent = event as MouseEvent;
55
+ if (model._edit_mode === "EDIT") {
56
+ if (!isEditableMode(model, viewer)) {
57
+ return;
58
+ }
77
59
 
78
- edit_click.clickListener.call(this, table, viewer, event);
79
- } else if (this._edit_mode === "READ_ONLY") {
80
- // No-op for read-only mode
81
- } else if (this._edit_mode === "SELECT_COLUMN") {
82
- // Not yet implemented
83
- } else if (this._edit_mode === "SELECT_ROW") {
84
- // Not yet implemented
85
- } else if (this._edit_mode === "SELECT_REGION") {
86
- // Not yet implemented
87
- } else {
88
- console.debug(
89
- `Mode ${this._edit_mode} for "click" event not yet implemented`,
90
- );
91
- }
60
+ edit_click.clickListener(model, table, viewer, mouseEvent);
61
+ }
62
+ };
92
63
  }
@@ -12,31 +12,30 @@
12
12
 
13
13
  import { RegularTableElement } from "regular-table";
14
14
  import getCellConfig from "../get_cell_config.js";
15
- import type {
16
- DatagridModel,
17
- PerspectiveViewerElement,
18
- PerspectiveClickDetail,
19
- } from "../types.js";
15
+ import type { DatagridModel, PerspectiveClickDetail } from "../types.js";
16
+ import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
20
17
 
21
- export async function dispatch_click_listener(
22
- this: DatagridModel,
18
+ export function createDispatchClickListener(
19
+ model: DatagridModel,
23
20
  table: RegularTableElement,
24
- viewer: PerspectiveViewerElement,
25
- event: MouseEvent,
26
- ): Promise<void> {
27
- const meta = table.getMeta(event.target as HTMLElement);
28
- if (!meta || meta.type !== "body") return;
29
- const { x, y } = meta;
30
- const { row, column_names, config } = await getCellConfig(this, y, x);
31
- viewer.dispatchEvent(
32
- new CustomEvent<PerspectiveClickDetail>("perspective-click", {
33
- bubbles: true,
34
- composed: true,
35
- detail: {
36
- row,
37
- column_names,
38
- config,
39
- },
40
- }),
41
- );
21
+ viewer: HTMLPerspectiveViewerElement,
22
+ ): EventListener {
23
+ return async (event: Event): Promise<void> => {
24
+ const mouseEvent = event as MouseEvent;
25
+ const meta = table.getMeta(mouseEvent.target as HTMLElement);
26
+ if (!meta || meta.type !== "body") return;
27
+ const { x, y } = meta;
28
+ const { row, column_names, config } = await getCellConfig(model, y, x);
29
+ viewer.dispatchEvent(
30
+ new CustomEvent<PerspectiveClickDetail>("perspective-click", {
31
+ bubbles: true,
32
+ composed: true,
33
+ detail: {
34
+ row,
35
+ column_names,
36
+ config,
37
+ },
38
+ }),
39
+ );
40
+ };
42
41
  }
@@ -13,7 +13,7 @@
13
13
  import type { RegularTable, DatagridModel } from "../types.js";
14
14
 
15
15
  export async function expandCollapseHandler(
16
- this: DatagridModel,
16
+ model: DatagridModel,
17
17
  regularTable: RegularTable,
18
18
  event: MouseEvent,
19
19
  ): Promise<void> {
@@ -24,22 +24,22 @@ export async function expandCollapseHandler(
24
24
  );
25
25
 
26
26
  if (event.shiftKey && is_collapse) {
27
- this._view.set_depth(
27
+ model._view.set_depth(
28
28
  (meta.row_header as unknown[]).filter((x) => x !== undefined)
29
29
  .length - 2,
30
30
  );
31
31
  } else if (event.shiftKey) {
32
- this._view.set_depth(
32
+ model._view.set_depth(
33
33
  (meta.row_header as unknown[]).filter((x) => x !== undefined)
34
34
  .length - 1,
35
35
  );
36
36
  } else if (is_collapse) {
37
- this._view.collapse(meta.y);
37
+ model._view.collapse(meta.y);
38
38
  } else {
39
- this._view.expand(meta.y);
39
+ model._view.expand(meta.y);
40
40
  }
41
41
 
42
- this._num_rows = await this._view.num_rows();
43
- this._num_columns = await this._view.num_columns();
42
+ model._num_rows = await model._view.num_rows();
43
+ model._num_columns = await model._view.num_columns();
44
44
  regularTable.draw();
45
45
  }