@perspective-dev/viewer-datagrid 4.0.1 → 4.1.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 -17
- package/dist/cdn/perspective-viewer-datagrid.js.map +4 -4
- package/dist/css/perspective-viewer-datagrid.css +1 -1
- package/dist/esm/color_utils.d.ts +9 -0
- package/dist/esm/custom_elements/datagrid.d.ts +52 -0
- package/dist/esm/custom_elements/toolbar.d.ts +10 -0
- package/dist/esm/data_listener/format_cell.d.ts +8 -0
- package/dist/esm/data_listener/format_tree_header.d.ts +13 -0
- package/dist/esm/data_listener/formatter_cache.d.ts +16 -0
- package/dist/esm/data_listener/index.d.ts +10 -0
- package/dist/esm/event_handlers/click/edit_click.d.ts +3 -0
- package/dist/esm/event_handlers/click.d.ts +7 -0
- package/dist/esm/event_handlers/deselect_all.d.ts +5 -0
- package/dist/esm/event_handlers/dispatch_click.d.ts +3 -0
- package/dist/esm/event_handlers/expand_collapse.d.ts +2 -0
- package/dist/esm/event_handlers/focus.d.ts +5 -0
- package/dist/esm/event_handlers/header_click.d.ts +3 -0
- package/dist/esm/event_handlers/keydown/edit_keydown.d.ts +4 -0
- package/dist/esm/event_handlers/row_select_click.d.ts +4 -0
- package/dist/esm/event_handlers/select_region.d.ts +9 -0
- package/dist/esm/event_handlers/sort.d.ts +7 -0
- package/dist/esm/get_cell_config.d.ts +8 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/model/column_overrides.d.ts +23 -0
- package/dist/esm/model/create.d.ts +3 -0
- package/dist/esm/model/index.d.ts +4 -0
- package/dist/esm/model/toolbar.d.ts +4 -0
- 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 +6 -0
- package/dist/esm/plugin/column_style_controls.d.ts +28 -0
- package/dist/esm/plugin/draw.d.ts +7 -0
- package/dist/esm/plugin/restore.d.ts +10 -0
- package/dist/esm/plugin/save.d.ts +2 -0
- package/dist/esm/style_handlers/body.d.ts +7 -0
- package/dist/esm/style_handlers/column_header.d.ts +13 -0
- package/dist/esm/style_handlers/consolidated.d.ts +57 -0
- package/dist/esm/style_handlers/editable.d.ts +7 -0
- package/dist/esm/style_handlers/focus.d.ts +16 -0
- package/dist/esm/style_handlers/group_header.d.ts +7 -0
- package/dist/esm/style_handlers/table_cell/boolean.d.ts +7 -0
- package/dist/esm/style_handlers/table_cell/cell_flash.d.ts +3 -0
- package/dist/esm/style_handlers/table_cell/datetime.d.ts +7 -0
- package/dist/esm/style_handlers/table_cell/numeric.d.ts +15 -0
- package/dist/esm/style_handlers/table_cell/row_header.d.ts +4 -0
- package/dist/esm/style_handlers/table_cell/string.d.ts +11 -0
- package/dist/esm/style_handlers/types.d.ts +20 -0
- package/dist/esm/types.d.ts +193 -0
- package/package.json +10 -5
- package/src/less/mitered-headers.less +65 -0
- package/src/less/pro.less +196 -0
- package/src/less/regular_table.less +509 -0
- package/src/less/row-hover.less +88 -0
- package/{index.d.ts → src/less/scrollbar.less} +18 -19
- package/src/less/sub-cell-scroll.less +82 -0
- package/src/less/toolbar.less +201 -0
- package/src/ts/color_utils.ts +70 -0
- package/src/ts/custom_elements/datagrid.ts +250 -0
- package/src/ts/custom_elements/toolbar.ts +75 -0
- package/src/ts/data_listener/format_cell.ts +84 -0
- package/src/ts/data_listener/format_tree_header.ts +82 -0
- package/src/ts/data_listener/formatter_cache.ts +191 -0
- package/src/ts/data_listener/index.ts +242 -0
- package/src/ts/event_handlers/click/edit_click.ts +73 -0
- package/src/ts/event_handlers/click.ts +92 -0
- package/src/ts/event_handlers/deselect_all.ts +28 -0
- package/src/ts/event_handlers/dispatch_click.ts +44 -0
- package/src/ts/event_handlers/expand_collapse.ts +44 -0
- package/src/ts/event_handlers/focus.ts +63 -0
- package/src/ts/event_handlers/header_click.ts +85 -0
- package/src/ts/event_handlers/keydown/edit_keydown.ts +213 -0
- package/src/ts/event_handlers/row_select_click.ts +87 -0
- package/src/ts/event_handlers/select_region.ts +427 -0
- package/src/ts/event_handlers/sort.ts +118 -0
- package/src/ts/get_cell_config.ts +68 -0
- package/src/ts/index.ts +49 -0
- package/src/ts/model/column_overrides.ts +112 -0
- package/src/ts/model/create.ts +247 -0
- package/src/ts/model/index.ts +19 -0
- package/src/ts/model/toolbar.ts +64 -0
- package/src/ts/plugin/activate.ts +235 -0
- package/src/ts/plugin/column_style_controls.ts +76 -0
- package/src/ts/plugin/draw.ts +69 -0
- package/src/ts/plugin/restore.ts +110 -0
- package/src/ts/plugin/save.ts +45 -0
- package/src/ts/style_handlers/body.ts +228 -0
- package/src/ts/style_handlers/column_header.ts +183 -0
- package/src/ts/style_handlers/consolidated.ts +223 -0
- package/src/ts/style_handlers/editable.ts +94 -0
- package/src/ts/style_handlers/focus.ts +106 -0
- package/src/ts/style_handlers/group_header.ts +78 -0
- package/src/ts/style_handlers/table_cell/boolean.ts +39 -0
- package/src/ts/style_handlers/table_cell/cell_flash.ts +75 -0
- package/src/ts/style_handlers/table_cell/datetime.ts +64 -0
- package/src/ts/style_handlers/table_cell/numeric.ts +186 -0
- package/src/ts/style_handlers/table_cell/row_header.ts +53 -0
- package/src/ts/style_handlers/table_cell/string.ts +102 -0
- package/src/ts/style_handlers/types.ts +41 -0
- package/src/ts/types.ts +279 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { RegularTableElement } from "regular-table";
|
|
14
|
+
import type { DatagridModel, PerspectiveViewerElement } from "../types.js";
|
|
15
|
+
import { CollectedHeaderRow } from "./types.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Apply selected column styling in response to column settings toggle events.
|
|
19
|
+
* This is called directly (not as a style listener) when the user opens/closes
|
|
20
|
+
* the column settings panel.
|
|
21
|
+
*/
|
|
22
|
+
export function style_selected_column(
|
|
23
|
+
this: DatagridModel,
|
|
24
|
+
regularTable: RegularTableElement,
|
|
25
|
+
viewer: PerspectiveViewerElement,
|
|
26
|
+
selectedColumn: string | undefined,
|
|
27
|
+
): void {
|
|
28
|
+
const group_header_trs = Array.from(
|
|
29
|
+
regularTable.children[0].children[0].children,
|
|
30
|
+
) as HTMLTableRowElement[];
|
|
31
|
+
|
|
32
|
+
const len = group_header_trs.length;
|
|
33
|
+
const settings_open = viewer.hasAttribute("settings");
|
|
34
|
+
if (len <= 1) {
|
|
35
|
+
group_header_trs[0]?.removeAttribute("id");
|
|
36
|
+
} else {
|
|
37
|
+
group_header_trs.forEach((tr, i) => {
|
|
38
|
+
const offset = settings_open ? 1 : 0;
|
|
39
|
+
const id =
|
|
40
|
+
i === len - (offset + 1)
|
|
41
|
+
? "psp-column-titles"
|
|
42
|
+
: i === len - offset
|
|
43
|
+
? "psp-column-edit-buttons"
|
|
44
|
+
: null;
|
|
45
|
+
id ? tr.setAttribute("id", id) : tr.removeAttribute("id");
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
viewer.classList.toggle("psp-menu-open", !!selectedColumn);
|
|
50
|
+
if (settings_open && len >= 2) {
|
|
51
|
+
const titles = Array.from(
|
|
52
|
+
group_header_trs[len - 2].children,
|
|
53
|
+
) as HTMLElement[];
|
|
54
|
+
const editBtns = Array.from(
|
|
55
|
+
group_header_trs[len - 1].children,
|
|
56
|
+
) as HTMLElement[];
|
|
57
|
+
if (titles && editBtns) {
|
|
58
|
+
group_header_trs.slice(0, len - 2).forEach((tr) => {
|
|
59
|
+
Array.from(tr.children).forEach((th) => {
|
|
60
|
+
th.classList.toggle("psp-menu-open", false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < titles.length; i++) {
|
|
65
|
+
const title = titles[i];
|
|
66
|
+
const editBtn = editBtns[i];
|
|
67
|
+
|
|
68
|
+
const open = title.textContent === selectedColumn;
|
|
69
|
+
title.classList.toggle("psp-menu-open", open);
|
|
70
|
+
editBtn.classList.toggle("psp-menu-open", open);
|
|
71
|
+
if (this._config.columns.length > 1) {
|
|
72
|
+
for (const r of regularTable.querySelectorAll("td")) {
|
|
73
|
+
const meta = regularTable.getMeta(r);
|
|
74
|
+
if (!meta?.column_header) continue;
|
|
75
|
+
const isOpen =
|
|
76
|
+
meta.column_header[
|
|
77
|
+
meta.column_header.length - 2
|
|
78
|
+
] === selectedColumn;
|
|
79
|
+
r.classList.toggle("psp-menu-open", isOpen);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Style a single column header row.
|
|
89
|
+
*/
|
|
90
|
+
export function styleColumnHeaderRow(
|
|
91
|
+
this: DatagridModel,
|
|
92
|
+
headerRow: CollectedHeaderRow,
|
|
93
|
+
regularTable: RegularTableElement,
|
|
94
|
+
is_menu_row: boolean,
|
|
95
|
+
): void {
|
|
96
|
+
const header_depth = this._config.group_by.length;
|
|
97
|
+
const selectedColumn = this._column_settings_selected_column;
|
|
98
|
+
|
|
99
|
+
for (const { element: td, metadata } of headerRow.cells) {
|
|
100
|
+
if (!metadata) continue;
|
|
101
|
+
|
|
102
|
+
const column_name =
|
|
103
|
+
metadata.column_header?.[this._config.split_by.length];
|
|
104
|
+
const sort = this._config.sort.find((x) => x[0] === column_name);
|
|
105
|
+
let needs_border = metadata.row_header_x === header_depth;
|
|
106
|
+
const is_corner = typeof metadata.x === "undefined";
|
|
107
|
+
needs_border =
|
|
108
|
+
needs_border ||
|
|
109
|
+
(metadata.x !== undefined &&
|
|
110
|
+
(metadata.x + 1) % this._config.columns.length === 0);
|
|
111
|
+
|
|
112
|
+
td.classList.toggle("psp-header-border", needs_border);
|
|
113
|
+
td.classList.toggle("psp-header-group", false);
|
|
114
|
+
td.classList.toggle("psp-header-leaf", true);
|
|
115
|
+
td.classList.toggle("psp-is-top", false);
|
|
116
|
+
td.classList.toggle("psp-header-corner", is_corner);
|
|
117
|
+
td.classList.toggle(
|
|
118
|
+
"psp-header-sort-asc",
|
|
119
|
+
!is_menu_row && !!sort && sort[1] === "asc",
|
|
120
|
+
);
|
|
121
|
+
td.classList.toggle(
|
|
122
|
+
"psp-header-sort-desc",
|
|
123
|
+
!is_menu_row && !!sort && sort[1] === "desc",
|
|
124
|
+
);
|
|
125
|
+
td.classList.toggle(
|
|
126
|
+
"psp-header-sort-col-asc",
|
|
127
|
+
!is_menu_row && !!sort && sort[1] === "col asc",
|
|
128
|
+
);
|
|
129
|
+
td.classList.toggle(
|
|
130
|
+
"psp-header-sort-col-desc",
|
|
131
|
+
!is_menu_row && !!sort && sort[1] === "col desc",
|
|
132
|
+
);
|
|
133
|
+
td.classList.toggle(
|
|
134
|
+
"psp-header-sort-abs-asc",
|
|
135
|
+
!is_menu_row && !!sort && sort[1] === "asc abs",
|
|
136
|
+
);
|
|
137
|
+
td.classList.toggle(
|
|
138
|
+
"psp-header-sort-abs-desc",
|
|
139
|
+
!is_menu_row && !!sort && sort[1] === "desc abs",
|
|
140
|
+
);
|
|
141
|
+
td.classList.toggle(
|
|
142
|
+
"psp-header-sort-abs-col-asc",
|
|
143
|
+
!is_menu_row && !!sort && sort[1] === "col asc abs",
|
|
144
|
+
);
|
|
145
|
+
td.classList.toggle(
|
|
146
|
+
"psp-header-sort-abs-col-desc",
|
|
147
|
+
!is_menu_row && !!sort && sort[1] === "col desc abs",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const type = this.get_psp_type(metadata);
|
|
151
|
+
const is_numeric = type === "integer" || type === "float";
|
|
152
|
+
const is_string = type === "string";
|
|
153
|
+
const is_date = type === "date";
|
|
154
|
+
const is_datetime = type === "datetime";
|
|
155
|
+
|
|
156
|
+
td.classList.toggle("psp-align-right", is_numeric);
|
|
157
|
+
td.classList.toggle("psp-align-left", !is_numeric);
|
|
158
|
+
td.classList.toggle(
|
|
159
|
+
"psp-menu-enabled",
|
|
160
|
+
(is_string || is_numeric || is_date || is_datetime) &&
|
|
161
|
+
!is_corner &&
|
|
162
|
+
metadata.column_header_y === this._config.split_by.length + 1,
|
|
163
|
+
);
|
|
164
|
+
td.classList.toggle(
|
|
165
|
+
"psp-sort-enabled",
|
|
166
|
+
(is_string || is_numeric || is_date || is_datetime) &&
|
|
167
|
+
!is_corner &&
|
|
168
|
+
metadata.column_header_y === this._config.split_by.length,
|
|
169
|
+
);
|
|
170
|
+
td.classList.toggle(
|
|
171
|
+
"psp-is-width-override",
|
|
172
|
+
regularTable.saveColumnSizes()[metadata.size_key!] !== undefined,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Apply menu-open for selected column
|
|
176
|
+
if (this._config.columns.length > 1 && selectedColumn) {
|
|
177
|
+
const isOpen =
|
|
178
|
+
metadata.column_header?.[metadata.column_header.length - 2] ===
|
|
179
|
+
selectedColumn;
|
|
180
|
+
td.classList.toggle("psp-menu-open", isOpen);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { RegularTableElement } from "regular-table";
|
|
14
|
+
import { CellMetadata } from "regular-table/dist/esm/types.js";
|
|
15
|
+
import { ColumnType } from "@perspective-dev/client";
|
|
16
|
+
import { PRIVATE_PLUGIN_SYMBOL } from "../model/index.js";
|
|
17
|
+
import type {
|
|
18
|
+
DatagridModel,
|
|
19
|
+
PerspectiveViewerElement,
|
|
20
|
+
ColumnsConfig,
|
|
21
|
+
DatagridPluginElement,
|
|
22
|
+
SelectedPosition,
|
|
23
|
+
} from "../types.js";
|
|
24
|
+
|
|
25
|
+
import { applyFocusStyle } from "./focus.js";
|
|
26
|
+
import { styleColumnHeaderRow } from "./column_header.js";
|
|
27
|
+
import { applyColumnHeaderStyles } from "./editable.js";
|
|
28
|
+
import { applyGroupHeaderStyles } from "./group_header.js";
|
|
29
|
+
import { applyBodyCellStyles } from "./body.js";
|
|
30
|
+
|
|
31
|
+
interface CellMetaExtended extends CellMetadata {
|
|
32
|
+
_is_hidden_by_aggregate_depth?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface CollectedCell {
|
|
36
|
+
element: HTMLElement;
|
|
37
|
+
metadata: CellMetaExtended;
|
|
38
|
+
isHeader: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface CollectedHeaderRow {
|
|
42
|
+
row: HTMLTableRowElement;
|
|
43
|
+
cells: Array<{
|
|
44
|
+
element: HTMLTableCellElement;
|
|
45
|
+
metadata: CellMetadata | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Context object passed through consolidated styling
|
|
51
|
+
*/
|
|
52
|
+
export interface StyleContext {
|
|
53
|
+
model: DatagridModel;
|
|
54
|
+
regularTable: RegularTableElement;
|
|
55
|
+
viewer: PerspectiveViewerElement;
|
|
56
|
+
datagrid: DatagridPluginElement;
|
|
57
|
+
plugins: ColumnsConfig;
|
|
58
|
+
isSettingsOpen: boolean;
|
|
59
|
+
isSelectable: boolean;
|
|
60
|
+
isEditable: boolean;
|
|
61
|
+
selectedRowsMap: Map<RegularTableElement, unknown[]>;
|
|
62
|
+
selectedPositionMap: Map<RegularTableElement, SelectedPosition>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Local types for selection maps - match the actual runtime usage
|
|
66
|
+
// (activate.ts uses `as any` casts when passing these)
|
|
67
|
+
type LocalSelectedRowsMap = WeakMap<RegularTableElement, unknown[]>;
|
|
68
|
+
type LocalSelectedPositionMap = WeakMap<RegularTableElement, SelectedPosition>;
|
|
69
|
+
|
|
70
|
+
function isEditableMode(
|
|
71
|
+
model: DatagridModel,
|
|
72
|
+
viewer: PerspectiveViewerElement,
|
|
73
|
+
allowed: boolean = false,
|
|
74
|
+
): boolean {
|
|
75
|
+
const has_pivots =
|
|
76
|
+
model._config.group_by.length === 0 &&
|
|
77
|
+
model._config.split_by.length === 0;
|
|
78
|
+
const selectable = viewer.hasAttribute("selectable");
|
|
79
|
+
const plugin = viewer.children[0] as
|
|
80
|
+
| (DatagridPluginElement & { dataset: DOMStringMap })
|
|
81
|
+
| undefined;
|
|
82
|
+
const editable = allowed || plugin?.dataset?.editMode === "EDIT";
|
|
83
|
+
return has_pivots && !selectable && editable;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Consolidated style listener that handles all cell styling in a single pass.
|
|
88
|
+
* This eliminates redundant DOM traversals and reduces layout thrashing by:
|
|
89
|
+
* 1. Collecting all cell metadata in a read phase
|
|
90
|
+
* 2. Applying all styles in a write phase
|
|
91
|
+
*/
|
|
92
|
+
export function createConsolidatedStyleListener(
|
|
93
|
+
datagrid: DatagridPluginElement,
|
|
94
|
+
selectedRowsMap: LocalSelectedRowsMap,
|
|
95
|
+
selectedPositionMap: LocalSelectedPositionMap,
|
|
96
|
+
): (
|
|
97
|
+
this: DatagridModel,
|
|
98
|
+
regularTable: RegularTableElement,
|
|
99
|
+
viewer: PerspectiveViewerElement,
|
|
100
|
+
) => void {
|
|
101
|
+
return function consolidatedStyleListener(
|
|
102
|
+
this: DatagridModel,
|
|
103
|
+
regularTable: RegularTableElement,
|
|
104
|
+
viewer: PerspectiveViewerElement,
|
|
105
|
+
): void {
|
|
106
|
+
const plugins: ColumnsConfig =
|
|
107
|
+
(regularTable as any)[PRIVATE_PLUGIN_SYMBOL] || {};
|
|
108
|
+
const isSettingsOpen = viewer.hasAttribute("settings");
|
|
109
|
+
const isSelectable = viewer.hasAttribute("selectable");
|
|
110
|
+
const isEditable = isEditableMode(this, viewer);
|
|
111
|
+
const isEditableAllowed = isEditableMode(this, viewer, true);
|
|
112
|
+
|
|
113
|
+
// Toggle edit mode class on datagrid
|
|
114
|
+
datagrid.classList.toggle("edit-mode-allowed", isEditableAllowed);
|
|
115
|
+
const bodyCells: CollectedCell[] = [];
|
|
116
|
+
const groupHeaderRows: CollectedHeaderRow[] = [];
|
|
117
|
+
const tbody = regularTable.children[0]?.children[1];
|
|
118
|
+
if (tbody) {
|
|
119
|
+
for (const tr of tbody.children) {
|
|
120
|
+
for (const cell of tr.children) {
|
|
121
|
+
const metadata = regularTable.getMeta(cell) as
|
|
122
|
+
| CellMetaExtended
|
|
123
|
+
| undefined;
|
|
124
|
+
|
|
125
|
+
if (metadata) {
|
|
126
|
+
const isHeader = cell.tagName === "TH";
|
|
127
|
+
bodyCells.push({
|
|
128
|
+
element: cell as HTMLElement,
|
|
129
|
+
metadata,
|
|
130
|
+
isHeader,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Collect header rows (thead)
|
|
138
|
+
const thead = regularTable.children[0]?.children[0];
|
|
139
|
+
if (thead) {
|
|
140
|
+
for (const tr of thead.children) {
|
|
141
|
+
const rowData: CollectedHeaderRow = {
|
|
142
|
+
row: tr as HTMLTableRowElement,
|
|
143
|
+
cells: [],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
for (const cell of tr.children) {
|
|
147
|
+
const metadata = regularTable.getMeta(cell) as
|
|
148
|
+
| CellMetadata
|
|
149
|
+
| undefined;
|
|
150
|
+
|
|
151
|
+
rowData.cells.push({
|
|
152
|
+
element: cell as HTMLTableCellElement,
|
|
153
|
+
metadata,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
groupHeaderRows.push(rowData);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this._applyBodyCellStyles(
|
|
161
|
+
bodyCells,
|
|
162
|
+
plugins,
|
|
163
|
+
isSettingsOpen,
|
|
164
|
+
isSelectable,
|
|
165
|
+
isEditable,
|
|
166
|
+
regularTable,
|
|
167
|
+
selectedRowsMap,
|
|
168
|
+
selectedPositionMap,
|
|
169
|
+
viewer,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
this._applyGroupHeaderStyles(groupHeaderRows, regularTable);
|
|
173
|
+
this._applyColumnHeaderStyles(groupHeaderRows, regularTable, viewer);
|
|
174
|
+
this._applyFocusStyle(bodyCells, regularTable, selectedPositionMap);
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
declare module "../types.js" {
|
|
179
|
+
interface DatagridModel {
|
|
180
|
+
_applyBodyCellStyles(
|
|
181
|
+
cells: CollectedCell[],
|
|
182
|
+
plugins: ColumnsConfig,
|
|
183
|
+
isSettingsOpen: boolean,
|
|
184
|
+
isSelectable: boolean,
|
|
185
|
+
isEditable: boolean,
|
|
186
|
+
regularTable: RegularTableElement,
|
|
187
|
+
selectedRowsMap: LocalSelectedRowsMap,
|
|
188
|
+
selectedPositionMap: LocalSelectedPositionMap,
|
|
189
|
+
viewer: PerspectiveViewerElement,
|
|
190
|
+
): void;
|
|
191
|
+
_applyGroupHeaderStyles(
|
|
192
|
+
headerRows: CollectedHeaderRow[],
|
|
193
|
+
regularTable: RegularTableElement,
|
|
194
|
+
): void;
|
|
195
|
+
_applyColumnHeaderStyles(
|
|
196
|
+
headerRows: CollectedHeaderRow[],
|
|
197
|
+
regularTable: RegularTableElement,
|
|
198
|
+
viewer: PerspectiveViewerElement,
|
|
199
|
+
): void;
|
|
200
|
+
_applyFocusStyle(
|
|
201
|
+
cells: CollectedCell[],
|
|
202
|
+
regularTable: RegularTableElement,
|
|
203
|
+
selectedPositionMap: LocalSelectedPositionMap,
|
|
204
|
+
): void;
|
|
205
|
+
_styleColumnHeaderRow(
|
|
206
|
+
headerRow: CollectedHeaderRow,
|
|
207
|
+
regularTable: RegularTableElement,
|
|
208
|
+
is_menu_row: boolean,
|
|
209
|
+
): void;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Install the styling methods on the DatagridModel prototype.
|
|
215
|
+
* This should be called once during module initialization.
|
|
216
|
+
*/
|
|
217
|
+
export function installConsolidatedStyleMethods(modelPrototype: any): void {
|
|
218
|
+
modelPrototype._applyBodyCellStyles = applyBodyCellStyles;
|
|
219
|
+
modelPrototype._applyGroupHeaderStyles = applyGroupHeaderStyles;
|
|
220
|
+
modelPrototype._applyColumnHeaderStyles = applyColumnHeaderStyles;
|
|
221
|
+
modelPrototype._applyFocusStyle = applyFocusStyle;
|
|
222
|
+
modelPrototype._styleColumnHeaderRow = styleColumnHeaderRow;
|
|
223
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { RegularTableElement } from "regular-table";
|
|
14
|
+
|
|
15
|
+
import type { DatagridModel, PerspectiveViewerElement } from "../types.js";
|
|
16
|
+
|
|
17
|
+
import { CollectedHeaderRow } from "./types.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Apply styles to column header rows.
|
|
21
|
+
*/
|
|
22
|
+
export function applyColumnHeaderStyles(
|
|
23
|
+
this: DatagridModel,
|
|
24
|
+
headerRows: CollectedHeaderRow[],
|
|
25
|
+
regularTable: RegularTableElement,
|
|
26
|
+
viewer: PerspectiveViewerElement,
|
|
27
|
+
): void {
|
|
28
|
+
if (headerRows.length === 0) return;
|
|
29
|
+
|
|
30
|
+
// Style selected column for settings panel
|
|
31
|
+
const selectedColumn = this._column_settings_selected_column;
|
|
32
|
+
const len = headerRows.length;
|
|
33
|
+
const settings_open = viewer.hasAttribute("settings");
|
|
34
|
+
|
|
35
|
+
// Set row IDs
|
|
36
|
+
if (len <= 1) {
|
|
37
|
+
headerRows[0]?.row.removeAttribute("id");
|
|
38
|
+
} else {
|
|
39
|
+
headerRows.forEach(({ row }, i) => {
|
|
40
|
+
const offset = settings_open ? 1 : 0;
|
|
41
|
+
const id =
|
|
42
|
+
i === len - (offset + 1)
|
|
43
|
+
? "psp-column-titles"
|
|
44
|
+
: i === len - offset
|
|
45
|
+
? "psp-column-edit-buttons"
|
|
46
|
+
: null;
|
|
47
|
+
id ? row.setAttribute("id", id) : row.removeAttribute("id");
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
viewer.classList.toggle("psp-menu-open", !!selectedColumn);
|
|
52
|
+
|
|
53
|
+
// Style column titles and edit buttons when settings open
|
|
54
|
+
if (settings_open && len >= 2) {
|
|
55
|
+
const titlesRow = headerRows[len - 2];
|
|
56
|
+
const editBtnsRow = headerRows[len - 1];
|
|
57
|
+
|
|
58
|
+
if (titlesRow && editBtnsRow) {
|
|
59
|
+
// Clear menu-open from other rows
|
|
60
|
+
headerRows.slice(0, len - 2).forEach(({ cells }) => {
|
|
61
|
+
cells.forEach(({ element }) => {
|
|
62
|
+
element.classList.toggle("psp-menu-open", false);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < titlesRow.cells.length; i++) {
|
|
67
|
+
const title = titlesRow.cells[i]?.element;
|
|
68
|
+
const editBtn = editBtnsRow.cells[i]?.element;
|
|
69
|
+
if (!title || !editBtn) continue;
|
|
70
|
+
|
|
71
|
+
const open = title.textContent === selectedColumn;
|
|
72
|
+
title.classList.toggle("psp-menu-open", open);
|
|
73
|
+
editBtn.classList.toggle("psp-menu-open", open);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Style the actual column header rows
|
|
79
|
+
const colHeadersIndex = this._config.split_by.length;
|
|
80
|
+
if (colHeadersIndex < headerRows.length) {
|
|
81
|
+
const colHeaders = headerRows[colHeadersIndex];
|
|
82
|
+
if (colHeaders) {
|
|
83
|
+
this._styleColumnHeaderRow(colHeaders, regularTable, false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const menuHeadersIndex = this._config.split_by.length + 1;
|
|
88
|
+
if (menuHeadersIndex < headerRows.length) {
|
|
89
|
+
const menuHeaders = headerRows[menuHeadersIndex];
|
|
90
|
+
if (menuHeaders) {
|
|
91
|
+
this._styleColumnHeaderRow(menuHeaders, regularTable, true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { RegularTableElement } from "regular-table";
|
|
14
|
+
import type { DatagridModel, SelectedPosition } from "../types.js";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
CollectedCell,
|
|
18
|
+
LocalSelectedPositionMap,
|
|
19
|
+
CellMetaExtended,
|
|
20
|
+
} from "./types.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply focus style to the selected cell.
|
|
24
|
+
* Optimized to use collected cells instead of querySelectorAll.
|
|
25
|
+
*/
|
|
26
|
+
export function applyFocusStyle(
|
|
27
|
+
this: DatagridModel,
|
|
28
|
+
cells: CollectedCell[],
|
|
29
|
+
regularTable: RegularTableElement,
|
|
30
|
+
selectedPositionMap: LocalSelectedPositionMap,
|
|
31
|
+
): void {
|
|
32
|
+
const selected_position = selectedPositionMap.get(regularTable);
|
|
33
|
+
const host = regularTable.getRootNode() as Document;
|
|
34
|
+
|
|
35
|
+
if (selected_position) {
|
|
36
|
+
for (const { element: td, metadata } of cells) {
|
|
37
|
+
if (
|
|
38
|
+
metadata.x === selected_position.x &&
|
|
39
|
+
metadata.y === selected_position.y
|
|
40
|
+
) {
|
|
41
|
+
if (host.activeElement !== td) {
|
|
42
|
+
td.focus({ preventScroll: true });
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If we didn't find the cell to focus, blur current
|
|
49
|
+
if (
|
|
50
|
+
document.activeElement !== document.body &&
|
|
51
|
+
regularTable.contains(host.activeElement)
|
|
52
|
+
) {
|
|
53
|
+
(host.activeElement as HTMLElement).blur();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Standalone function to focus the selected cell.
|
|
60
|
+
* This collects cells from the table and tries to focus the selected position.
|
|
61
|
+
* Returns true if focus was successful, false otherwise.
|
|
62
|
+
*
|
|
63
|
+
* Used by edit_keydown.ts for keyboard navigation.
|
|
64
|
+
*/
|
|
65
|
+
export function focusSelectedCell(
|
|
66
|
+
regularTable: RegularTableElement,
|
|
67
|
+
selectedPositionMap: Map<RegularTableElement, SelectedPosition>,
|
|
68
|
+
): boolean {
|
|
69
|
+
const selected_position = selectedPositionMap.get(regularTable);
|
|
70
|
+
if (!selected_position) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const host = regularTable.getRootNode() as Document;
|
|
75
|
+
const tbody = regularTable.children[0]?.children[1];
|
|
76
|
+
|
|
77
|
+
if (tbody) {
|
|
78
|
+
for (const tr of tbody.children) {
|
|
79
|
+
for (const cell of tr.children) {
|
|
80
|
+
const metadata = regularTable.getMeta(cell) as
|
|
81
|
+
| CellMetaExtended
|
|
82
|
+
| undefined;
|
|
83
|
+
if (
|
|
84
|
+
metadata &&
|
|
85
|
+
metadata.x === selected_position.x &&
|
|
86
|
+
metadata.y === selected_position.y
|
|
87
|
+
) {
|
|
88
|
+
if (host.activeElement !== cell) {
|
|
89
|
+
(cell as HTMLElement).focus({ preventScroll: true });
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If we didn't find the cell to focus, blur current
|
|
98
|
+
if (
|
|
99
|
+
document.activeElement !== document.body &&
|
|
100
|
+
regularTable.contains(host.activeElement)
|
|
101
|
+
) {
|
|
102
|
+
(host.activeElement as HTMLElement).blur();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return false;
|
|
106
|
+
}
|