@perspective-dev/viewer-datagrid 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer-datagrid.js +4 -4
- package/dist/cdn/perspective-viewer-datagrid.js.map +4 -4
- package/dist/esm/custom_elements/datagrid.d.ts +12 -17
- package/dist/esm/model/meta_columns.d.ts +1 -0
- package/dist/esm/perspective-viewer-datagrid.js +3 -3
- package/dist/esm/perspective-viewer-datagrid.js.map +4 -4
- package/dist/esm/plugin/column_config_schema.d.ts +31 -0
- package/package.json +1 -1
- package/src/ts/color_utils.ts +50 -13
- package/src/ts/custom_elements/datagrid.ts +63 -48
- package/src/ts/data_listener/format_tree_header.ts +2 -2
- package/src/ts/data_listener/formatter_cache.ts +8 -95
- package/src/ts/data_listener/index.ts +9 -3
- package/src/ts/event_handlers/click/edit_click.ts +3 -0
- package/src/ts/event_handlers/dispatch_click.ts +4 -1
- package/src/ts/event_handlers/expand_collapse.ts +4 -1
- package/src/ts/event_handlers/header_click.ts +9 -3
- package/src/ts/event_handlers/keydown/edit_keydown.ts +11 -2
- package/src/ts/event_handlers/select_region.ts +15 -4
- package/src/ts/event_handlers/sort.ts +4 -1
- package/src/ts/get_cell_config.ts +10 -3
- package/src/ts/model/column_overrides.ts +3 -5
- package/src/ts/model/create.ts +22 -5
- package/src/ts/{plugin/column_style_controls.ts → model/meta_columns.ts} +33 -62
- package/src/ts/model/toolbar.ts +11 -2
- package/src/ts/plugin/activate.ts +3 -1
- package/src/ts/plugin/column_config_schema.ts +187 -0
- package/src/ts/plugin/draw.ts +1 -0
- package/src/ts/plugin/restore.ts +6 -2
- package/src/ts/plugin/save.ts +1 -5
- package/src/ts/style_handlers/body.ts +6 -2
- package/src/ts/style_handlers/column_header.ts +6 -2
- package/src/ts/style_handlers/consolidated.ts +1 -0
- package/src/ts/style_handlers/editable.ts +6 -2
- package/src/ts/style_handlers/focus.ts +2 -0
- package/src/ts/style_handlers/group_header.ts +10 -4
- package/dist/esm/plugin/column_style_controls.d.ts +0 -28
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ColumnType } from "@perspective-dev/client";
|
|
2
|
+
import type { DatagridPluginElement } from "../types.js";
|
|
3
|
+
interface ViewerConfigLike {
|
|
4
|
+
group_by?: string[];
|
|
5
|
+
group_rollup_mode?: string;
|
|
6
|
+
}
|
|
7
|
+
type ControlSpec = Record<string, unknown> & {
|
|
8
|
+
kind: string;
|
|
9
|
+
};
|
|
10
|
+
export interface ColumnConfigSchema {
|
|
11
|
+
fields: ControlSpec[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Plugin schema for the Datagrid column-settings sidebar. Returns the
|
|
15
|
+
* controls the viewer should render in the Style tab for a given column.
|
|
16
|
+
*
|
|
17
|
+
* Each entry in `fields` is a `ControlSpec` discriminated by `kind`.
|
|
18
|
+
* Composite kinds (`NumberStyle`, `DatetimeFormat`, `StringFormat`,
|
|
19
|
+
* `NumberFormat`, `AggregateDepth`) own a fixed key namespace and
|
|
20
|
+
* carry only their `default`. Primitive kinds (`Enum`, `Bool`, `Color`,
|
|
21
|
+
* etc.) carry their own `key` (storage) and `label` (UI) inline.
|
|
22
|
+
*
|
|
23
|
+
* Aggregate Depth is plugin-owned — surfaced only inside the Datagrid
|
|
24
|
+
* because rollup-mode pivots are a Datagrid concern. Emitted only when
|
|
25
|
+
* the active view has a non-empty `group_by` and rollup mode is `Rollup`.
|
|
26
|
+
*/
|
|
27
|
+
interface ColumnStats {
|
|
28
|
+
abs_max?: number;
|
|
29
|
+
}
|
|
30
|
+
export default function column_config_schema(this: DatagridPluginElement, type: ColumnType, _group: string | undefined, _column_name: string, current_value: Record<string, unknown> | null, viewer_config?: ViewerConfigLike, column_stats?: ColumnStats): ColumnConfigSchema;
|
|
31
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perspective-dev/viewer-datagrid",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.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",
|
package/src/ts/color_utils.ts
CHANGED
|
@@ -28,13 +28,18 @@ function parse_hex(input: string): RGB | null {
|
|
|
28
28
|
const r = parseInt(s[0] + s[0], 16);
|
|
29
29
|
const g = parseInt(s[1] + s[1], 16);
|
|
30
30
|
const b = parseInt(s[2] + s[2], 16);
|
|
31
|
-
if (!isNaN(r) && !isNaN(g) && !isNaN(b))
|
|
31
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
|
|
32
|
+
return [r, g, b];
|
|
33
|
+
}
|
|
32
34
|
} else if (s.length === 6 || s.length === 8) {
|
|
33
35
|
const r = parseInt(s.slice(0, 2), 16);
|
|
34
36
|
const g = parseInt(s.slice(2, 4), 16);
|
|
35
37
|
const b = parseInt(s.slice(4, 6), 16);
|
|
36
|
-
if (!isNaN(r) && !isNaN(g) && !isNaN(b))
|
|
38
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
|
|
39
|
+
return [r, g, b];
|
|
40
|
+
}
|
|
37
41
|
}
|
|
42
|
+
|
|
38
43
|
return null;
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -43,7 +48,10 @@ function parse_rgb_fn(input: string): RGB | null {
|
|
|
43
48
|
const m = input.match(
|
|
44
49
|
/^rgba?\(\s*([\d.]+)\s*[, ]\s*([\d.]+)\s*[, ]\s*([\d.]+)/i,
|
|
45
50
|
);
|
|
46
|
-
if (!m)
|
|
51
|
+
if (!m) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
return [
|
|
48
56
|
Math.round(parseFloat(m[1])),
|
|
49
57
|
Math.round(parseFloat(m[2])),
|
|
@@ -58,7 +66,11 @@ function parse_via_canvas(input: string): RGB {
|
|
|
58
66
|
canvas.width = canvas.height = 1;
|
|
59
67
|
parse_ctx = canvas.getContext("2d");
|
|
60
68
|
}
|
|
61
|
-
|
|
69
|
+
|
|
70
|
+
if (!parse_ctx) {
|
|
71
|
+
return [0, 0, 0];
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
parse_ctx.fillStyle = "#000";
|
|
63
75
|
parse_ctx.fillStyle = input;
|
|
64
76
|
const normalized = parse_ctx.fillStyle as string;
|
|
@@ -69,7 +81,10 @@ function parse_via_canvas(input: string): RGB {
|
|
|
69
81
|
export function parseColor(input: string): RGB {
|
|
70
82
|
const key = input.trim();
|
|
71
83
|
const cached = parse_cache.get(key);
|
|
72
|
-
if (cached)
|
|
84
|
+
if (cached) {
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
const rgb = parse_hex(key) ?? parse_rgb_fn(key) ?? parse_via_canvas(key);
|
|
74
89
|
parse_cache.set(key, rgb);
|
|
75
90
|
return rgb;
|
|
@@ -99,10 +114,15 @@ export function rgbToHsl([r, g, b]: RGB): HSL {
|
|
|
99
114
|
let s = 0;
|
|
100
115
|
if (d !== 0) {
|
|
101
116
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
102
|
-
if (max === rn)
|
|
103
|
-
|
|
104
|
-
else
|
|
117
|
+
if (max === rn) {
|
|
118
|
+
h = ((gn - bn) / d + (gn < bn ? 6 : 0)) * 60;
|
|
119
|
+
} else if (max === gn) {
|
|
120
|
+
h = ((bn - rn) / d + 2) * 60;
|
|
121
|
+
} else {
|
|
122
|
+
h = ((rn - gn) / d + 4) * 60;
|
|
123
|
+
}
|
|
105
124
|
}
|
|
125
|
+
|
|
106
126
|
return [h, s, l];
|
|
107
127
|
}
|
|
108
128
|
|
|
@@ -113,16 +133,33 @@ export function hslToRgb([h, s, l]: HSL): RGB {
|
|
|
113
133
|
const v = Math.round(l * 255);
|
|
114
134
|
return [v, v, v];
|
|
115
135
|
}
|
|
136
|
+
|
|
116
137
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
117
138
|
const p = 2 * l - q;
|
|
118
139
|
const f = (t: number): number => {
|
|
119
|
-
if (t < 0)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (t
|
|
140
|
+
if (t < 0) {
|
|
141
|
+
t += 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (t > 1) {
|
|
145
|
+
t -= 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (t < 1 / 6) {
|
|
149
|
+
return p + (q - p) * 6 * t;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (t < 1 / 2) {
|
|
153
|
+
return q;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (t < 2 / 3) {
|
|
157
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
158
|
+
}
|
|
159
|
+
|
|
124
160
|
return p;
|
|
125
161
|
};
|
|
162
|
+
|
|
126
163
|
return [
|
|
127
164
|
Math.round(f(hn + 1 / 3) * 255),
|
|
128
165
|
Math.round(f(hn) * 255),
|
|
@@ -15,16 +15,17 @@ import { activate } from "../plugin/activate.js";
|
|
|
15
15
|
import { restore } from "../plugin/restore.js";
|
|
16
16
|
import { save } from "../plugin/save.js";
|
|
17
17
|
import { draw } from "../plugin/draw.js";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
} from "../plugin/
|
|
18
|
+
import column_config_schema, {
|
|
19
|
+
ColumnConfigSchema,
|
|
20
|
+
} from "../plugin/column_config_schema.js";
|
|
21
21
|
import datagridStyles from "../../../dist/css/perspective-viewer-datagrid.css";
|
|
22
22
|
import { format_raw } from "../data_listener/format_cell.js";
|
|
23
|
+
import { sourceColumn } from "@perspective-dev/viewer/src/ts/column-format.js";
|
|
23
24
|
|
|
24
25
|
import type { View, ViewWindow } from "@perspective-dev/client";
|
|
25
26
|
import type {
|
|
26
|
-
HTMLPerspectiveViewerElement,
|
|
27
27
|
IPerspectiveViewerPlugin,
|
|
28
|
+
PluginStaticConfig,
|
|
28
29
|
} from "@perspective-dev/viewer";
|
|
29
30
|
import type {
|
|
30
31
|
DatagridModel,
|
|
@@ -115,44 +116,64 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
115
116
|
return await activate.call(this, view);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
return
|
|
119
|
+
get_static_config(): PluginStaticConfig {
|
|
120
|
+
return {
|
|
121
|
+
name: "Datagrid",
|
|
122
|
+
category: "Basic",
|
|
123
|
+
select_mode: "toggle",
|
|
124
|
+
config_column_names: ["Columns"],
|
|
125
|
+
group_rollup_modes: ["rollup", "flat", "total"],
|
|
126
|
+
// Higher priority than the chart plugins so the Datagrid is
|
|
127
|
+
// loaded by default.
|
|
128
|
+
priority: 1,
|
|
129
|
+
can_render_column_styles: true,
|
|
130
|
+
};
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
plugin_config_schema(): ColumnConfigSchema {
|
|
134
|
+
const fields = [];
|
|
135
|
+
fields.push({
|
|
136
|
+
kind: "Enum",
|
|
137
|
+
key: "edit_mode",
|
|
138
|
+
default: "READ_ONLY",
|
|
139
|
+
variants: [
|
|
140
|
+
{ value: "EDIT", label: "Edit" },
|
|
141
|
+
{ value: "READ_ONLY", label: "Read-only" },
|
|
142
|
+
{ value: "SELECT_ROW", label: "Row Select" },
|
|
143
|
+
{ value: "SELECT_COLUMN", label: "Column Select" },
|
|
144
|
+
{ value: "SELECT_REGION", label: "Region Select" },
|
|
145
|
+
{ value: "SELECT_ROW_TREE", label: "Tree Select" },
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
fields.push({
|
|
150
|
+
kind: "Bool",
|
|
151
|
+
key: "scroll_lock",
|
|
152
|
+
default: false,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
fields,
|
|
157
|
+
};
|
|
124
158
|
}
|
|
125
159
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* Give the Datagrid a higher priority so it is loaded
|
|
144
|
-
* over the default charts by default.
|
|
145
|
-
*/
|
|
146
|
-
get priority(): number {
|
|
147
|
-
return 1;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
can_render_column_styles(type: string, _group: string): boolean {
|
|
151
|
-
return type !== "boolean";
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
column_style_controls(type: string, group: string): ColumnStyleOpts {
|
|
155
|
-
return column_style_controls.call(this, type as any, group);
|
|
160
|
+
column_config_schema(
|
|
161
|
+
type: string,
|
|
162
|
+
group: string | undefined,
|
|
163
|
+
column_name: string,
|
|
164
|
+
current_value: Record<string, unknown> | null,
|
|
165
|
+
viewer_config?: { group_by?: string[]; group_rollup_mode?: string },
|
|
166
|
+
column_stats?: { abs_max: number },
|
|
167
|
+
): ColumnConfigSchema {
|
|
168
|
+
return column_config_schema.call(
|
|
169
|
+
this,
|
|
170
|
+
type as any,
|
|
171
|
+
group,
|
|
172
|
+
column_name,
|
|
173
|
+
current_value,
|
|
174
|
+
viewer_config,
|
|
175
|
+
column_stats,
|
|
176
|
+
);
|
|
156
177
|
}
|
|
157
178
|
|
|
158
179
|
async draw(view: View): Promise<void> {
|
|
@@ -172,9 +193,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
172
193
|
}
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
async render(viewport?: ViewWindow): Promise<string> {
|
|
176
|
-
const viewer = this.parentElement as HTMLPerspectiveViewerElement;
|
|
177
|
-
const view = await viewer.getView();
|
|
196
|
+
async render(view: View, viewport?: ViewWindow): Promise<string> {
|
|
178
197
|
const json = await view.to_columns(viewport as any);
|
|
179
198
|
const cols = await view.column_paths(viewport as any);
|
|
180
199
|
|
|
@@ -194,7 +213,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
194
213
|
const pluginConfig = (this.regular_table as any)[
|
|
195
214
|
PRIVATE_PLUGIN_SYMBOL
|
|
196
215
|
] as ColumnsConfig | undefined;
|
|
197
|
-
const columnName = col_name
|
|
216
|
+
const columnName = sourceColumn(col_name);
|
|
198
217
|
const formatter = format_raw(
|
|
199
218
|
type,
|
|
200
219
|
pluginConfig?.[columnName] || {},
|
|
@@ -206,6 +225,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
206
225
|
out += col[ridx] + "\t";
|
|
207
226
|
}
|
|
208
227
|
}
|
|
228
|
+
|
|
209
229
|
out += "\n";
|
|
210
230
|
}
|
|
211
231
|
|
|
@@ -235,12 +255,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
235
255
|
return restore.call(this, token, columns_config ?? {});
|
|
236
256
|
}
|
|
237
257
|
|
|
238
|
-
|
|
239
|
-
// Get view from model if available, otherwise no-op
|
|
240
|
-
if (this.model?._view) {
|
|
241
|
-
await this.draw(view);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
258
|
+
restyle() {}
|
|
244
259
|
|
|
245
260
|
delete(): void {
|
|
246
261
|
this.disconnectedCallback();
|
|
@@ -27,7 +27,7 @@ export function* format_tree_header_row_path(
|
|
|
27
27
|
): Generator<RowHeaderCell[]> {
|
|
28
28
|
const plugins: ColumnsConfig =
|
|
29
29
|
(regularTable as any)[PRIVATE_PLUGIN_SYMBOL] || {};
|
|
30
|
-
for (
|
|
30
|
+
for (const path of paths) {
|
|
31
31
|
const fullPath: unknown[] = ["TOTAL", ...path];
|
|
32
32
|
const last = fullPath[fullPath.length - 1];
|
|
33
33
|
let newPath: RowHeaderCell[] = fullPath
|
|
@@ -61,7 +61,7 @@ export function* format_flat_header_row_path(
|
|
|
61
61
|
const plugins: ColumnsConfig =
|
|
62
62
|
(regularTable as any)[PRIVATE_PLUGIN_SYMBOL] || {};
|
|
63
63
|
|
|
64
|
-
for (
|
|
64
|
+
for (const path of paths) {
|
|
65
65
|
yield path.map((part, i) =>
|
|
66
66
|
format_cell.call(this, row_headers[i], part, plugins, true),
|
|
67
67
|
) as RowHeaderCell[];
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
import type { ColumnType } from "@perspective-dev/client";
|
|
14
|
+
import {
|
|
15
|
+
createDateFormatter,
|
|
16
|
+
createDatetimeFormatter,
|
|
17
|
+
createNumberFormatter,
|
|
18
|
+
} from "@perspective-dev/viewer/src/ts/column-format.js";
|
|
14
19
|
import type { ColumnConfig } from "../types.js";
|
|
15
20
|
|
|
16
21
|
export interface Formatter {
|
|
@@ -26,30 +31,6 @@ class BooleanFormatter implements Formatter {
|
|
|
26
31
|
// PluginConfig is a subset of ColumnConfig with the formatting properties
|
|
27
32
|
type PluginConfig = Pick<ColumnConfig, "date_format" | "number_format">;
|
|
28
33
|
|
|
29
|
-
const LEGACY_CONFIG: Record<
|
|
30
|
-
string,
|
|
31
|
-
{ format: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions }
|
|
32
|
-
> = {
|
|
33
|
-
float: {
|
|
34
|
-
format: {
|
|
35
|
-
style: "decimal",
|
|
36
|
-
minimumFractionDigits: 2,
|
|
37
|
-
maximumFractionDigits: 2,
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
datetime: {
|
|
41
|
-
format: {
|
|
42
|
-
dateStyle: "short",
|
|
43
|
-
timeStyle: "medium",
|
|
44
|
-
} as Intl.DateTimeFormatOptions,
|
|
45
|
-
},
|
|
46
|
-
date: {
|
|
47
|
-
format: {
|
|
48
|
-
dateStyle: "short",
|
|
49
|
-
} as Intl.DateTimeFormatOptions,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
34
|
export class FormatterCache {
|
|
54
35
|
private _formatters: Map<string, Formatter | false>;
|
|
55
36
|
|
|
@@ -61,89 +42,21 @@ export class FormatterCache {
|
|
|
61
42
|
_type: ColumnType,
|
|
62
43
|
plugin: PluginConfig,
|
|
63
44
|
): Intl.DateTimeFormat {
|
|
64
|
-
|
|
65
|
-
const options: Intl.DateTimeFormatOptions = {
|
|
66
|
-
timeZone: plugin.date_format?.timeZone,
|
|
67
|
-
dateStyle:
|
|
68
|
-
plugin.date_format?.dateStyle === "disabled"
|
|
69
|
-
? undefined
|
|
70
|
-
: (plugin.date_format?.dateStyle ?? "short"),
|
|
71
|
-
timeStyle:
|
|
72
|
-
plugin.date_format?.timeStyle === "disabled"
|
|
73
|
-
? undefined
|
|
74
|
-
: (plugin.date_format?.timeStyle ?? "medium"),
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
return new Intl.DateTimeFormat(
|
|
78
|
-
navigator.languages as string[],
|
|
79
|
-
options,
|
|
80
|
-
);
|
|
81
|
-
} else {
|
|
82
|
-
const options: Intl.DateTimeFormatOptions = {
|
|
83
|
-
timeZone: plugin.date_format?.timeZone,
|
|
84
|
-
hour12: plugin.date_format?.hour12 ?? true,
|
|
85
|
-
fractionalSecondDigits:
|
|
86
|
-
plugin.date_format?.fractionalSecondDigits,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
if (plugin.date_format?.year !== "disabled") {
|
|
90
|
-
options.year = plugin.date_format?.year ?? "2-digit";
|
|
91
|
-
}
|
|
92
|
-
if (plugin.date_format?.month !== "disabled") {
|
|
93
|
-
options.month = plugin.date_format?.month ?? "numeric";
|
|
94
|
-
}
|
|
95
|
-
if (plugin.date_format?.day !== "disabled") {
|
|
96
|
-
options.day = plugin.date_format?.day ?? "numeric";
|
|
97
|
-
}
|
|
98
|
-
if (
|
|
99
|
-
plugin.date_format?.weekday &&
|
|
100
|
-
plugin.date_format?.weekday !== "disabled"
|
|
101
|
-
) {
|
|
102
|
-
options.weekday = plugin.date_format.weekday;
|
|
103
|
-
}
|
|
104
|
-
if (plugin.date_format?.hour !== "disabled") {
|
|
105
|
-
options.hour = plugin.date_format?.hour ?? "numeric";
|
|
106
|
-
}
|
|
107
|
-
if (plugin.date_format?.minute !== "disabled") {
|
|
108
|
-
options.minute = plugin.date_format?.minute ?? "numeric";
|
|
109
|
-
}
|
|
110
|
-
if (plugin.date_format?.second !== "disabled") {
|
|
111
|
-
options.second = plugin.date_format?.second ?? "numeric";
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return new Intl.DateTimeFormat(
|
|
115
|
-
navigator.languages as string[],
|
|
116
|
-
options,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
45
|
+
return createDatetimeFormatter(plugin.date_format);
|
|
119
46
|
}
|
|
120
47
|
|
|
121
48
|
private create_date_formatter(
|
|
122
49
|
_type: ColumnType,
|
|
123
50
|
plugin: PluginConfig,
|
|
124
51
|
): Intl.DateTimeFormat {
|
|
125
|
-
|
|
126
|
-
timeZone: "utc",
|
|
127
|
-
dateStyle:
|
|
128
|
-
plugin.date_format?.dateStyle === "disabled"
|
|
129
|
-
? undefined
|
|
130
|
-
: (plugin.date_format?.dateStyle ?? "short"),
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return new Intl.DateTimeFormat(
|
|
134
|
-
navigator.languages as string[],
|
|
135
|
-
options,
|
|
136
|
-
);
|
|
52
|
+
return createDateFormatter(plugin.date_format);
|
|
137
53
|
}
|
|
138
54
|
|
|
139
55
|
private create_number_formatter(
|
|
140
56
|
type: ColumnType,
|
|
141
57
|
plugin: PluginConfig,
|
|
142
58
|
): Intl.NumberFormat {
|
|
143
|
-
|
|
144
|
-
plugin.number_format ??
|
|
145
|
-
(LEGACY_CONFIG[type]?.format as Intl.NumberFormatOptions);
|
|
146
|
-
return new Intl.NumberFormat(navigator.languages as string[], format);
|
|
59
|
+
return createNumberFormatter(type, plugin.number_format);
|
|
147
60
|
}
|
|
148
61
|
|
|
149
62
|
private create_boolean_formatter(
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
import { PRIVATE_PLUGIN_SYMBOL } from "../types.js";
|
|
14
|
+
import { isMetaColumn } from "../model/meta_columns.js";
|
|
14
15
|
import { format_cell } from "./format_cell.js";
|
|
15
16
|
import {
|
|
16
17
|
format_flat_header_row_path,
|
|
@@ -84,9 +85,14 @@ export function createDataListener(
|
|
|
84
85
|
num_columns = z;
|
|
85
86
|
columns = JSON.parse(x as string) as ColumnData;
|
|
86
87
|
const y = Object.keys(columns);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// `isMetaColumn` covers `__ROW_PATH__`, `__ID__`,
|
|
89
|
+
// `__GROUPING_ID__`, and the per-level `__ROW_PATH_<n>__`
|
|
90
|
+
// columns the DuckDB virtual server emits inline alongside
|
|
91
|
+
// the JSON sidecar. Exact-match against `__ROW_PATH__` /
|
|
92
|
+
// `__ID__` (the previous filter) misses the per-level
|
|
93
|
+
// form and the virtual server's columns leak into the
|
|
94
|
+
// visible grid.
|
|
95
|
+
const new_col_paths = y.filter((x) => !isMetaColumn(x));
|
|
90
96
|
|
|
91
97
|
let changed_cols = false;
|
|
92
98
|
for (let i = 0; i < new_col_paths.length; i++) {
|
|
@@ -28,6 +28,7 @@ export function write_cell(
|
|
|
28
28
|
if (!meta) {
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
|
+
|
|
31
32
|
const type = model._schema[model._column_paths[meta.x!]];
|
|
32
33
|
let text: string | number | boolean | null = active_cell.textContent || "";
|
|
33
34
|
const id = model._ids[meta.y! - meta.y0][0];
|
|
@@ -36,12 +37,14 @@ export function write_cell(
|
|
|
36
37
|
if (isNaN(parsed)) {
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
40
|
+
|
|
39
41
|
text = parsed;
|
|
40
42
|
} else if (type === "date" || type === "datetime") {
|
|
41
43
|
const parsed = Date.parse(text);
|
|
42
44
|
if (isNaN(parsed)) {
|
|
43
45
|
return false;
|
|
44
46
|
}
|
|
47
|
+
|
|
45
48
|
text = parsed;
|
|
46
49
|
} else if (type === "boolean") {
|
|
47
50
|
text = text === "true" ? false : text === "false" ? true : null;
|
|
@@ -23,7 +23,10 @@ export function createDispatchClickListener(
|
|
|
23
23
|
return async (event: Event): Promise<void> => {
|
|
24
24
|
const mouseEvent = event as MouseEvent;
|
|
25
25
|
const meta = table.getMeta(mouseEvent.target as HTMLElement);
|
|
26
|
-
if (!meta || meta.type !== "body")
|
|
26
|
+
if (!meta || meta.type !== "body") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
27
30
|
const { x, y } = meta;
|
|
28
31
|
const { row, column_names, config } = await getCellConfig(model, y, x);
|
|
29
32
|
viewer.dispatchEvent(
|
|
@@ -18,7 +18,10 @@ export async function expandCollapseHandler(
|
|
|
18
18
|
event: MouseEvent,
|
|
19
19
|
): Promise<void> {
|
|
20
20
|
const meta = regularTable.getMeta(event.target as HTMLElement);
|
|
21
|
-
if (!meta || meta.type !== "row_header")
|
|
21
|
+
if (!meta || meta.type !== "row_header") {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
22
25
|
const is_collapse = (event.target as Element).classList.contains(
|
|
23
26
|
"psp-tree-label-collapse",
|
|
24
27
|
);
|
|
@@ -38,7 +38,9 @@ export function createMousedownListener(
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (!target)
|
|
41
|
+
if (!target) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
42
44
|
|
|
43
45
|
if (target.classList.contains("psp-tree-label")) {
|
|
44
46
|
if (model._edit_mode !== "SELECT_ROW_TREE") {
|
|
@@ -82,7 +84,9 @@ export function createDblclickListener(
|
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
if (!target)
|
|
87
|
+
if (!target) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
86
90
|
|
|
87
91
|
if (target.classList.contains("psp-tree-label")) {
|
|
88
92
|
if (model._edit_mode === "SELECT_ROW_TREE") {
|
|
@@ -107,7 +111,9 @@ export function createClickListener(regularTable: RegularTable): EventListener {
|
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
if (!target)
|
|
114
|
+
if (!target) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
111
117
|
|
|
112
118
|
if (
|
|
113
119
|
target.classList.contains("psp-tree-label") &&
|
|
@@ -68,7 +68,10 @@ function getPos(elem: ContentEditableElement): number {
|
|
|
68
68
|
const _range = (elem.getRootNode() as Document)
|
|
69
69
|
.getSelection()
|
|
70
70
|
?.getRangeAt(0);
|
|
71
|
-
if (!_range)
|
|
71
|
+
if (!_range) {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
72
75
|
const range = _range.cloneRange();
|
|
73
76
|
range.selectNodeContents(elem);
|
|
74
77
|
range.setEnd(_range.endContainer, _range.endOffset);
|
|
@@ -87,7 +90,10 @@ const moveSelection = lock(async function (
|
|
|
87
90
|
dy: number,
|
|
88
91
|
): Promise<void> {
|
|
89
92
|
const meta = table.getMeta(active_cell);
|
|
90
|
-
if (!meta || meta.type !== "body")
|
|
93
|
+
if (!meta || meta.type !== "body") {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
const num_columns = model._column_paths.length;
|
|
92
98
|
const num_rows = model._num_rows;
|
|
93
99
|
const selected_position = selected_position_map.get(table);
|
|
@@ -167,6 +173,7 @@ export function keydownListener(
|
|
|
167
173
|
1,
|
|
168
174
|
);
|
|
169
175
|
}
|
|
176
|
+
|
|
170
177
|
break;
|
|
171
178
|
case "ArrowLeft":
|
|
172
179
|
if (getPos(target as ContentEditableElement) === 0) {
|
|
@@ -180,6 +187,7 @@ export function keydownListener(
|
|
|
180
187
|
0,
|
|
181
188
|
);
|
|
182
189
|
}
|
|
190
|
+
|
|
183
191
|
break;
|
|
184
192
|
case "ArrowUp":
|
|
185
193
|
event.preventDefault();
|
|
@@ -200,6 +208,7 @@ export function keydownListener(
|
|
|
200
208
|
0,
|
|
201
209
|
);
|
|
202
210
|
}
|
|
211
|
+
|
|
203
212
|
break;
|
|
204
213
|
case "ArrowDown":
|
|
205
214
|
event.preventDefault();
|
|
@@ -85,7 +85,10 @@ const getMousedownListener =
|
|
|
85
85
|
mouseEvent.button === 0 &&
|
|
86
86
|
isSelectionMode(datagrid.model!._edit_mode)
|
|
87
87
|
) {
|
|
88
|
-
if (isSingleClickMode(datagrid.model!._edit_mode))
|
|
88
|
+
if (isSingleClickMode(datagrid.model!._edit_mode)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
89
92
|
datagrid.model!._selection_state.CURRENT_MOUSEDOWN_COORDINATES = {};
|
|
90
93
|
const meta = table.getMeta(mouseEvent.target as HTMLElement);
|
|
91
94
|
if (
|
|
@@ -199,7 +202,9 @@ const getMouseupListener =
|
|
|
199
202
|
const mode = datagrid.model!._edit_mode;
|
|
200
203
|
if (isSelectionMode(mode)) {
|
|
201
204
|
const meta = table.getMeta(mouseEvent.target as HTMLElement);
|
|
202
|
-
if (!meta)
|
|
205
|
+
if (!meta) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
203
208
|
|
|
204
209
|
// For single-click modes (SELECT_ROW_TREE), handle toggle
|
|
205
210
|
if (isSingleClickMode(mode)) {
|
|
@@ -423,11 +428,17 @@ const applyMouseAreaSelection = (
|
|
|
423
428
|
className: string,
|
|
424
429
|
): void => {
|
|
425
430
|
const predicate = SELECTION_PREDICATES[datagrid.model!._edit_mode];
|
|
426
|
-
if (!predicate || selected.length === 0)
|
|
431
|
+
if (!predicate || selected.length === 0) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
427
435
|
const tds = table.querySelectorAll("tbody td");
|
|
428
436
|
for (const td of tds) {
|
|
429
437
|
const meta = table.getMeta(td as HTMLElement);
|
|
430
|
-
if (!meta || meta.type !== "body")
|
|
438
|
+
if (!meta || meta.type !== "body") {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
431
442
|
let rendered = false;
|
|
432
443
|
for (const area of selected) {
|
|
433
444
|
if (
|