@perspective-dev/viewer-datagrid 4.0.1 → 4.1.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 (99) hide show
  1. package/dist/cdn/perspective-viewer-datagrid.js +4 -17
  2. package/dist/cdn/perspective-viewer-datagrid.js.map +4 -4
  3. package/dist/css/perspective-viewer-datagrid.css +1 -1
  4. package/dist/esm/color_utils.d.ts +9 -0
  5. package/dist/esm/custom_elements/datagrid.d.ts +52 -0
  6. package/dist/esm/custom_elements/toolbar.d.ts +10 -0
  7. package/dist/esm/data_listener/format_cell.d.ts +8 -0
  8. package/dist/esm/data_listener/format_tree_header.d.ts +13 -0
  9. package/dist/esm/data_listener/formatter_cache.d.ts +16 -0
  10. package/dist/esm/data_listener/index.d.ts +10 -0
  11. package/dist/esm/event_handlers/click/edit_click.d.ts +3 -0
  12. package/dist/esm/event_handlers/click.d.ts +7 -0
  13. package/dist/esm/event_handlers/deselect_all.d.ts +5 -0
  14. package/dist/esm/event_handlers/dispatch_click.d.ts +3 -0
  15. package/dist/esm/event_handlers/expand_collapse.d.ts +2 -0
  16. package/dist/esm/event_handlers/focus.d.ts +5 -0
  17. package/dist/esm/event_handlers/header_click.d.ts +3 -0
  18. package/dist/esm/event_handlers/keydown/edit_keydown.d.ts +4 -0
  19. package/dist/esm/event_handlers/row_select_click.d.ts +4 -0
  20. package/dist/esm/event_handlers/select_region.d.ts +9 -0
  21. package/dist/esm/event_handlers/sort.d.ts +7 -0
  22. package/dist/esm/get_cell_config.d.ts +8 -0
  23. package/dist/esm/index.d.ts +6 -0
  24. package/dist/esm/model/column_overrides.d.ts +23 -0
  25. package/dist/esm/model/create.d.ts +3 -0
  26. package/dist/esm/model/index.d.ts +4 -0
  27. package/dist/esm/model/toolbar.d.ts +4 -0
  28. package/dist/esm/perspective-viewer-datagrid.js +3 -3
  29. package/dist/esm/perspective-viewer-datagrid.js.map +4 -4
  30. package/dist/esm/plugin/activate.d.ts +6 -0
  31. package/dist/esm/plugin/column_style_controls.d.ts +28 -0
  32. package/dist/esm/plugin/draw.d.ts +7 -0
  33. package/dist/esm/plugin/restore.d.ts +10 -0
  34. package/dist/esm/plugin/save.d.ts +2 -0
  35. package/dist/esm/style_handlers/body.d.ts +7 -0
  36. package/dist/esm/style_handlers/column_header.d.ts +13 -0
  37. package/dist/esm/style_handlers/consolidated.d.ts +57 -0
  38. package/dist/esm/style_handlers/editable.d.ts +7 -0
  39. package/dist/esm/style_handlers/focus.d.ts +16 -0
  40. package/dist/esm/style_handlers/group_header.d.ts +7 -0
  41. package/dist/esm/style_handlers/table_cell/boolean.d.ts +7 -0
  42. package/dist/esm/style_handlers/table_cell/cell_flash.d.ts +3 -0
  43. package/dist/esm/style_handlers/table_cell/datetime.d.ts +7 -0
  44. package/dist/esm/style_handlers/table_cell/numeric.d.ts +15 -0
  45. package/dist/esm/style_handlers/table_cell/row_header.d.ts +4 -0
  46. package/dist/esm/style_handlers/table_cell/string.d.ts +11 -0
  47. package/dist/esm/style_handlers/types.d.ts +20 -0
  48. package/dist/esm/types.d.ts +193 -0
  49. package/package.json +10 -5
  50. package/src/less/mitered-headers.less +65 -0
  51. package/src/less/pro.less +196 -0
  52. package/src/less/regular_table.less +509 -0
  53. package/src/less/row-hover.less +88 -0
  54. package/{index.d.ts → src/less/scrollbar.less} +18 -19
  55. package/src/less/sub-cell-scroll.less +82 -0
  56. package/src/less/toolbar.less +201 -0
  57. package/src/ts/color_utils.ts +70 -0
  58. package/src/ts/custom_elements/datagrid.ts +250 -0
  59. package/src/ts/custom_elements/toolbar.ts +75 -0
  60. package/src/ts/data_listener/format_cell.ts +84 -0
  61. package/src/ts/data_listener/format_tree_header.ts +82 -0
  62. package/src/ts/data_listener/formatter_cache.ts +191 -0
  63. package/src/ts/data_listener/index.ts +242 -0
  64. package/src/ts/event_handlers/click/edit_click.ts +73 -0
  65. package/src/ts/event_handlers/click.ts +92 -0
  66. package/src/ts/event_handlers/deselect_all.ts +28 -0
  67. package/src/ts/event_handlers/dispatch_click.ts +44 -0
  68. package/src/ts/event_handlers/expand_collapse.ts +44 -0
  69. package/src/ts/event_handlers/focus.ts +63 -0
  70. package/src/ts/event_handlers/header_click.ts +85 -0
  71. package/src/ts/event_handlers/keydown/edit_keydown.ts +213 -0
  72. package/src/ts/event_handlers/row_select_click.ts +87 -0
  73. package/src/ts/event_handlers/select_region.ts +427 -0
  74. package/src/ts/event_handlers/sort.ts +118 -0
  75. package/src/ts/get_cell_config.ts +68 -0
  76. package/src/ts/index.ts +49 -0
  77. package/src/ts/model/column_overrides.ts +112 -0
  78. package/src/ts/model/create.ts +247 -0
  79. package/src/ts/model/index.ts +19 -0
  80. package/src/ts/model/toolbar.ts +64 -0
  81. package/src/ts/plugin/activate.ts +235 -0
  82. package/src/ts/plugin/column_style_controls.ts +76 -0
  83. package/src/ts/plugin/draw.ts +69 -0
  84. package/src/ts/plugin/restore.ts +110 -0
  85. package/src/ts/plugin/save.ts +45 -0
  86. package/src/ts/style_handlers/body.ts +228 -0
  87. package/src/ts/style_handlers/column_header.ts +183 -0
  88. package/src/ts/style_handlers/consolidated.ts +223 -0
  89. package/src/ts/style_handlers/editable.ts +94 -0
  90. package/src/ts/style_handlers/focus.ts +106 -0
  91. package/src/ts/style_handlers/group_header.ts +78 -0
  92. package/src/ts/style_handlers/table_cell/boolean.ts +39 -0
  93. package/src/ts/style_handlers/table_cell/cell_flash.ts +75 -0
  94. package/src/ts/style_handlers/table_cell/datetime.ts +64 -0
  95. package/src/ts/style_handlers/table_cell/numeric.ts +186 -0
  96. package/src/ts/style_handlers/table_cell/row_header.ts +53 -0
  97. package/src/ts/style_handlers/table_cell/string.ts +102 -0
  98. package/src/ts/style_handlers/types.ts +41 -0
  99. 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
+ }