@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.
- package/dist/cdn/perspective-viewer-datagrid.js +4 -22
- package/dist/cdn/perspective-viewer-datagrid.js.map +4 -4
- package/dist/css/perspective-viewer-datagrid-toolbar.css +1 -1
- package/dist/css/perspective-viewer-datagrid.css +1 -1
- package/dist/esm/color_utils.d.ts +22 -0
- package/dist/esm/custom_elements/datagrid.d.ts +5 -5
- package/dist/esm/data_listener/format_cell.d.ts +1 -1
- package/dist/esm/data_listener/formatter_cache.d.ts +1 -1
- package/dist/esm/data_listener/index.d.ts +3 -2
- package/dist/esm/event_handlers/click/edit_click.d.ts +3 -2
- package/dist/esm/event_handlers/click.d.ts +4 -6
- package/dist/esm/event_handlers/dispatch_click.d.ts +3 -2
- package/dist/esm/event_handlers/expand_collapse.d.ts +1 -1
- package/dist/esm/event_handlers/focus.d.ts +4 -5
- package/dist/esm/event_handlers/header_click.d.ts +5 -3
- package/dist/esm/event_handlers/keydown/edit_keydown.d.ts +3 -4
- package/dist/esm/event_handlers/select_region.d.ts +3 -1
- package/dist/esm/event_handlers/sort.d.ts +8 -7
- package/dist/esm/model/create.d.ts +1 -1
- package/dist/esm/perspective-viewer-datagrid.js +3 -3
- package/dist/esm/perspective-viewer-datagrid.js.map +4 -4
- package/dist/esm/plugin/activate.d.ts +1 -1
- package/dist/esm/plugin/column_style_controls.d.ts +1 -1
- package/dist/esm/style_handlers/body.d.ts +3 -3
- package/dist/esm/style_handlers/column_header.d.ts +4 -3
- package/dist/esm/style_handlers/consolidated.d.ts +3 -47
- package/dist/esm/style_handlers/editable.d.ts +3 -2
- package/dist/esm/style_handlers/focus.d.ts +4 -4
- package/dist/esm/style_handlers/group_header.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/boolean.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/cell_flash.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/datetime.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/numeric.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/row_header.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/string.d.ts +1 -1
- package/dist/esm/style_handlers/types.d.ts +0 -4
- package/dist/esm/types.d.ts +10 -17
- package/package.json +2 -4
- package/src/css/regular_table.css +87 -31
- package/src/css/row-hover.css +20 -7
- package/src/css/toolbar.css +11 -0
- package/src/ts/color_utils.ts +144 -16
- package/src/ts/custom_elements/datagrid.ts +11 -12
- package/src/ts/custom_elements/toolbar.ts +4 -5
- package/src/ts/data_listener/format_cell.ts +28 -9
- package/src/ts/data_listener/formatter_cache.ts +1 -1
- package/src/ts/data_listener/index.ts +4 -8
- package/src/ts/event_handlers/click/edit_click.ts +7 -6
- package/src/ts/event_handlers/click.ts +39 -68
- package/src/ts/event_handlers/dispatch_click.ts +24 -25
- package/src/ts/event_handlers/expand_collapse.ts +7 -7
- package/src/ts/event_handlers/focus.ts +38 -35
- package/src/ts/event_handlers/header_click.ts +101 -62
- package/src/ts/event_handlers/keydown/edit_keydown.ts +49 -52
- package/src/ts/event_handlers/select_region.ts +144 -133
- package/src/ts/event_handlers/sort.ts +16 -24
- package/src/ts/model/column_overrides.ts +13 -4
- package/src/ts/model/create.ts +51 -55
- package/src/ts/model/toolbar.ts +23 -7
- package/src/ts/plugin/activate.ts +120 -92
- package/src/ts/plugin/column_style_controls.ts +1 -1
- package/src/ts/plugin/save.ts +1 -0
- package/src/ts/style_handlers/body.ts +44 -51
- package/src/ts/style_handlers/column_header.ts +16 -19
- package/src/ts/style_handlers/consolidated.ts +22 -123
- package/src/ts/style_handlers/editable.ts +10 -8
- package/src/ts/style_handlers/focus.ts +5 -5
- package/src/ts/style_handlers/group_header.ts +3 -2
- package/src/ts/style_handlers/table_cell/boolean.ts +3 -3
- package/src/ts/style_handlers/table_cell/cell_flash.ts +11 -11
- package/src/ts/style_handlers/table_cell/datetime.ts +3 -3
- package/src/ts/style_handlers/table_cell/numeric.ts +24 -25
- package/src/ts/style_handlers/table_cell/row_header.ts +2 -2
- package/src/ts/style_handlers/table_cell/string.ts +20 -18
- package/src/ts/style_handlers/types.ts +0 -10
- package/src/ts/types.ts +28 -20
- package/dist/esm/event_handlers/deselect_all.d.ts +0 -5
- package/dist/esm/event_handlers/row_select_click.d.ts +0 -4
- package/src/ts/event_handlers/deselect_all.ts +0 -28
- package/src/ts/event_handlers/row_select_click.ts +0 -92
package/src/ts/color_utils.ts
CHANGED
|
@@ -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
|
|
148
|
+
return rgbToHex(mixRgb(parseColor(a), [b[0], b[1], b[2]], 0.5));
|
|
18
149
|
}
|
|
19
150
|
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
const [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const [r2, g2, b2] =
|
|
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
|
|
58
|
-
const _neg_grad = make_gradient(
|
|
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 {
|
|
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
|
|
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
|
-
|
|
230
|
+
save(): any {
|
|
229
231
|
return save.call(this);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
|
-
|
|
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(
|
|
241
|
+
await this.draw(view);
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
244
|
|
|
246
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
60
|
+
model: DatagridModel,
|
|
60
61
|
table: RegularTable,
|
|
61
|
-
_viewer:
|
|
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 =
|
|
67
|
-
const is_bool = get_psp_type(
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
18
|
+
import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
|
|
22
19
|
|
|
23
|
-
|
|
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:
|
|
23
|
+
viewer: HTMLPerspectiveViewerElement,
|
|
45
24
|
selected_position_map: SelectedPositionMap,
|
|
46
|
-
|
|
47
|
-
): void {
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
68
|
-
|
|
48
|
+
export function createEditClickListener(
|
|
49
|
+
model: DatagridModel,
|
|
69
50
|
table: RegularTableElement,
|
|
70
|
-
viewer:
|
|
71
|
-
|
|
72
|
-
): void {
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
|
22
|
-
|
|
18
|
+
export function createDispatchClickListener(
|
|
19
|
+
model: DatagridModel,
|
|
23
20
|
table: RegularTableElement,
|
|
24
|
-
viewer:
|
|
25
|
-
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
model._view.collapse(meta.y);
|
|
38
38
|
} else {
|
|
39
|
-
|
|
39
|
+
model._view.expand(meta.y);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
model._num_rows = await model._view.num_rows();
|
|
43
|
+
model._num_columns = await model._view.num_columns();
|
|
44
44
|
regularTable.draw();
|
|
45
45
|
}
|