flexdesk 0.16.1 → 0.17.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/CHANGELOG.md +93 -0
- package/LICENSE +0 -0
- package/README.md +2217 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3146 -164
- package/css/overrides.css +16 -0
- package/css/reset.css +0 -0
- package/css/tokens.css +0 -0
- package/dist/canvas.js +6 -239
- package/dist/canvas.js.map +3 -3
- package/dist/charts.js +709 -18
- package/dist/charts.js.map +4 -4
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-4A7SJC3T.js +503 -0
- package/dist/chunk-4A7SJC3T.js.map +7 -0
- package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
- package/dist/chunk-CVMCZZX2.js.map +7 -0
- package/dist/chunk-D3COLZZ2.js +1311 -0
- package/dist/chunk-D3COLZZ2.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
- package/dist/chunk-GOQLR5WR.js.map +7 -0
- package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
- package/dist/chunk-HNU6MM2B.js.map +7 -0
- package/dist/chunk-HYJYYEPK.js +375 -0
- package/dist/chunk-HYJYYEPK.js.map +7 -0
- package/dist/chunk-JKZLNKG6.js +2041 -0
- package/dist/chunk-JKZLNKG6.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-RQM5Z2MU.js → chunk-MCIXFNVF.js} +633 -311
- package/dist/chunk-MCIXFNVF.js.map +7 -0
- package/dist/chunk-N7HC3ADV.js +30 -0
- package/dist/chunk-N7HC3ADV.js.map +7 -0
- package/dist/{chunk-FBGP5KN5.js → chunk-Q36EPT2R.js} +3268 -203
- package/dist/chunk-Q36EPT2R.js.map +7 -0
- package/dist/chunk-QWQJUDPD.js +610 -0
- package/dist/chunk-QWQJUDPD.js.map +7 -0
- package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
- package/dist/chunk-UURZ37SD.js.map +7 -0
- package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
- package/dist/chunk-UWO43BZU.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +0 -0
- package/dist/chunk-WVFGV5FT.js.map +0 -0
- package/dist/core.js +170 -118
- package/dist/core.js.map +2 -2
- package/dist/editor.js +156 -70
- package/dist/editor.js.map +2 -2
- package/dist/flexdesk.css +3146 -164
- package/dist/flow.js +2789 -306
- package/dist/flow.js.map +4 -4
- package/dist/host.js +109 -3
- package/dist/host.js.map +3 -3
- package/dist/reset.css +0 -0
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tiles.js +657 -234
- package/dist/tiles.js.map +4 -4
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +3252 -2124
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -347
- package/dist/wm.js.map +4 -4
- package/package.json +3 -1
- package/src/canvas/canvas_adapter.js +5 -0
- package/src/canvas/edge_router.js +0 -0
- package/src/canvas/graph/connector_router.js +0 -0
- package/src/canvas/graph/history_service.js +0 -0
- package/src/canvas/graph/node_platform.js +0 -0
- package/src/canvas/index.js +1 -0
- package/src/canvas/layered_layout.js +244 -0
- package/src/charts/chart_types.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +2 -2
- package/src/charts/plot_config.js +53 -29
- package/src/charts/plot_config_editor.js +627 -0
- package/src/charts/plot_grid.js +449 -0
- package/src/charts/plot_popout_window.js +103 -317
- package/src/charts/plotly_wrapper.js +22 -2
- package/src/charts/series_transforms.js +206 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +1 -1
- package/src/core/settings.js +105 -19
- package/src/core/state_guard.js +93 -51
- package/src/core/state_guard_config.js +85 -74
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +3 -3
- package/src/editor/monaco_editor_factory.js +124 -60
- package/src/editor/monaco_loader.js +103 -28
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_tab_bar.js +38 -52
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +6 -3
- package/src/flow/canvas_editor/connections.js +90 -0
- package/src/flow/canvas_editor/editor.js +2351 -153
- package/src/flow/canvas_editor/index.js +2 -1
- package/src/flow/canvas_editor/layout.js +119 -12
- package/src/flow/canvas_editor/strings.js +80 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +97 -3
- package/src/flow/kit/chip_input.js +0 -0
- package/src/flow/kit/dom.js +0 -0
- package/src/flow/kit/findings.js +0 -0
- package/src/flow/kit/history.js +0 -0
- package/src/flow/kit/index.js +3 -3
- package/src/flow/kit/keys.js +7 -1
- package/src/flow/kit/logic_graph.js +97 -15
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +2 -1
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +3 -1
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +86 -109
- package/src/flow/lanes/editor.js +136 -14
- package/src/flow/lanes/index.js +3 -1
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +44 -2
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +1 -0
- package/src/flow/outline/editor.js +0 -0
- package/src/flow/outline/index.js +0 -0
- package/src/flow/outline/mapping.js +0 -0
- package/src/flow/outline/menu.js +0 -0
- package/src/flow/outline/operations.js +0 -0
- package/src/flow/outline/recognise.js +0 -0
- package/src/flow/outline/strings.js +0 -0
- package/src/help/help_modal.js +168 -42
- package/src/help/help_registry.js +12 -1
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +117 -0
- package/src/host/host.js +10 -2
- package/src/host/photino_host.js +9 -0
- package/src/host/photino_host.md +21 -3
- package/src/host/pywebview_host.js +10 -0
- package/src/tiles/chart_tiles.js +53 -245
- package/src/tiles/config_schema.js +8 -0
- package/src/tiles/figure_tile.js +322 -0
- package/src/tiles/layout_persistence.js +3 -3
- package/src/tiles/list_tiles.js +7 -7
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_base.js +1 -1
- package/src/tiles/tile_config_form.js +442 -0
- package/src/tiles/tile_grid.js +2 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +67 -99
- package/src/tiling/command_palette.js +64 -19
- package/src/tiling/content_registry.js +4 -1
- package/src/tiling/desktops.js +11 -1
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +403 -128
- package/src/tiling/kind_taxonomy.js +78 -0
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +59 -10
- package/src/tiling/page_factory.js +5 -5
- package/src/tiling/panel_availability.js +49 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +114 -12
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_breadcrumb.js +0 -0
- package/src/tiling/tile_renderer.js +105 -9
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/tile_tree.js +0 -0
- package/src/tiling/wm.js +562 -30
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/action_dropdown.js +30 -2
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/attribute_list_editor.js +0 -0
- package/src/ui/components/autocomplete_field.js +0 -0
- package/src/ui/components/cell_editor_host.js +211 -0
- package/src/ui/components/cell_grid.js +648 -0
- package/src/ui/components/cell_selection.js +220 -0
- package/src/ui/components/choice_controls.js +351 -0
- package/src/ui/components/color_field.js +211 -0
- package/src/ui/components/color_list_field.js +210 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +314 -0
- package/src/ui/components/column_order.js +142 -0
- package/src/ui/components/computing_status_window.js +98 -25
- package/src/ui/components/confirm_dialog.js +81 -38
- package/src/ui/components/context_menu.js +2 -2
- package/src/ui/components/conversation_view.js +440 -0
- package/src/ui/components/crosstab.js +289 -0
- package/src/ui/components/data_table.js +601 -62
- package/src/ui/components/detail_header.js +22 -11
- package/src/ui/components/document_outline.js +397 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/fact_table.js +0 -0
- package/src/ui/components/fill_handle.js +322 -0
- package/src/ui/components/gallery_picker.js +19 -8
- package/src/ui/components/header_actions.js +55 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +51 -0
- package/src/ui/components/managed_window.js +27 -4
- package/src/ui/components/menu_bar.js +745 -0
- package/src/ui/components/modal.js +449 -75
- package/src/ui/components/notification_history.js +3 -3
- package/src/ui/components/progress.js +483 -0
- package/src/ui/components/record_list.js +0 -0
- package/src/ui/components/row_activation.js +0 -0
- package/src/ui/components/section_stack.js +298 -0
- package/src/ui/components/series.js +173 -0
- package/src/ui/components/settings_view.js +634 -0
- package/src/ui/components/slide_out_panel.js +43 -6
- package/src/ui/components/slider_field.js +275 -18
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +343 -0
- package/src/ui/components/staged_history.js +172 -0
- package/src/ui/components/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_structure.js +182 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +497 -0
- package/src/ui/components/tag_input.js +215 -0
- package/src/ui/components/toast.js +25 -8
- package/src/ui/components/toggle_field.js +175 -0
- package/src/ui/components/tree_view.js +55 -34
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/wizard.js +563 -0
- package/src/ui/controllers/window_chrome_controller.js +2 -2
- package/src/ui/notification_center.js +375 -119
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +709 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +16 -12
- package/src/ui/utils/raf_resize_observer.js +0 -0
- package/src/ui/utils/scroll_lock.js +0 -0
- package/src/ui/utils/strip_scroll.js +55 -0
- package/src/ui/utils/tooltip_service.js +0 -8
- package/dist/chunk-6JSOVNID.js.map +0 -7
- package/dist/chunk-B5Q53LAJ.js.map +0 -7
- package/dist/chunk-FBGP5KN5.js.map +0 -7
- package/dist/chunk-FL5KFNQH.js.map +0 -7
- package/dist/chunk-NLTOY57M.js.map +0 -7
- package/dist/chunk-NNFTETK3.js +0 -633
- package/dist/chunk-NNFTETK3.js.map +0 -7
- package/dist/chunk-O5OHMWBB.js +0 -284
- package/dist/chunk-O5OHMWBB.js.map +0 -7
- package/dist/chunk-RQM5Z2MU.js.map +0 -7
- package/dist/chunk-SN4LSOAR.js.map +0 -7
- package/dist/chunk-W6FBJLPW.js +0 -367
- package/dist/chunk-W6FBJLPW.js.map +0 -7
- package/src/ui/controllers/panel_state_machine.js +0 -634
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crosstab.js — STORY-0091. DataTable's CROSSTAB LAYOUT (`crosstab`).
|
|
3
|
+
* INTERNAL to the table: imported by `data_table.js` only, and not part of the
|
|
4
|
+
* widgets barrel — it reads the table's private fields.
|
|
5
|
+
*
|
|
6
|
+
* `crosstab: {labelColumns, headerRows, emptyLabel}` draws the table as a
|
|
7
|
+
* matrix: k rows of spanned headers over the body columns, the leading
|
|
8
|
+
* `labelColumns` drawn data columns as row labels, and the whole of it in ONE
|
|
9
|
+
* `<table>` in ONE scroller. The consumer hands over every value and every
|
|
10
|
+
* label — rows and columns that add others up, their words and their classes
|
|
11
|
+
* (`rowClass`, `cellClass`, a header cell's `className`) are the consumer's;
|
|
12
|
+
* this module only lays the cells out and pins them.
|
|
13
|
+
*
|
|
14
|
+
* ── ONE TABLE, ONE SCROLLER ───────────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* The ordinary DataTable lifts its `<thead>` into a header table of its own
|
|
17
|
+
* above the body's scroll box and copies the body's widths onto it. A header
|
|
18
|
+
* of spans breaks that copy (every width pass reads the header CELL BY CELL at
|
|
19
|
+
* the column's index), and two scrollers cannot pin a label column in both the
|
|
20
|
+
* header and the body. So here the `<thead>` stays in the body table and
|
|
21
|
+
* pinning is `position: sticky` against the body's scroll box (`_tableWrapEl`),
|
|
22
|
+
* the only box that scrolls: the header rows to the top, the label columns
|
|
23
|
+
* (with the row number and select columns in front of them) to the left, and
|
|
24
|
+
* the corner — the header cells over the label columns — to both. No header
|
|
25
|
+
* table is built, so no width pass, resize grip, header menu or group row of
|
|
26
|
+
* the split layout runs.
|
|
27
|
+
*
|
|
28
|
+
* ── OFFSETS ARE MEASURED, NOT ASSUMED ─────────────────────────────────────
|
|
29
|
+
*
|
|
30
|
+
* A sticky header row needs its own `top`: the height of every header row
|
|
31
|
+
* above it. A sticky label column needs its own `left`: the width of every
|
|
32
|
+
* pinned column before it. Both are read after layout (`pin()`, on the frame
|
|
33
|
+
* after a render) and again whenever a header row or a pinned header cell
|
|
34
|
+
* changes size (a label that wraps, a font, a zoom) through a ResizeObserver.
|
|
35
|
+
* They are read with `getBoundingClientRect`, which keeps the fraction
|
|
36
|
+
* `offsetHeight` rounds away, and scaled back out of any CSS zoom, because
|
|
37
|
+
* `style.top`/`style.left` are written in the unzoomed space inside it.
|
|
38
|
+
*
|
|
39
|
+
* ── AN EMPTY AXIS ─────────────────────────────────────────────────────────
|
|
40
|
+
*
|
|
41
|
+
* With no body column at all the table still draws: one column headed
|
|
42
|
+
* `emptyLabel` ('(none)'), an empty cell under it in every row, and no spanned
|
|
43
|
+
* header rows. It is no data column (`_dataIdxOfDom` answers null for it).
|
|
44
|
+
*
|
|
45
|
+
* A table without `crosstab` never builds this.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { createRafResizeObserver } from '../utils/raf_resize_observer.js';
|
|
49
|
+
import { normalizeSpans, blankSpanCell, labelSpanCell } from './column_groups.js';
|
|
50
|
+
|
|
51
|
+
const DEFAULT_EMPTY_LABEL = '(none)';
|
|
52
|
+
|
|
53
|
+
/** The CSS zoom an element is drawn in, as the factor that takes a measured
|
|
54
|
+
* (zoomed) length back to the space `style` lengths are written in. */
|
|
55
|
+
function unzoom(el) {
|
|
56
|
+
return 1 / (el?.currentCSSZoom || 1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class Crosstab {
|
|
60
|
+
/** @param {import('./data_table.js').DataTable} table */
|
|
61
|
+
constructor(table) {
|
|
62
|
+
this.table = table;
|
|
63
|
+
/** The pinned columns' `left`, by drawn index, as last measured. */
|
|
64
|
+
this._lefts = [];
|
|
65
|
+
this._leafRow = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_spec() {
|
|
69
|
+
const s = this.table.config.crosstab;
|
|
70
|
+
return s && typeof s === 'object' ? s : {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** How many of the drawn data columns are row labels: the leading ones. */
|
|
74
|
+
labelCount() {
|
|
75
|
+
const k = Math.floor(Number(this._spec().labelColumns) || 0);
|
|
76
|
+
return Math.max(0, Math.min(k, this.table._shownColumns().length));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The drawn columns that pin to the left: the select and row-number
|
|
80
|
+
* columns, and the label columns. */
|
|
81
|
+
pinnedCount() {
|
|
82
|
+
return this.table._lead().all + this.labelCount();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The drawn data columns after the label columns, in order. */
|
|
86
|
+
bodyColumns() {
|
|
87
|
+
return this.table._shownColumns().slice(this.labelCount());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Columns drawn that are no data column: the empty axis's one. */
|
|
91
|
+
extraColumns() {
|
|
92
|
+
return this.bodyColumns().length ? 0 : 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The header rows as drawn: per row, `{cell, span}` runs over the body
|
|
96
|
+
* columns (`cell` null for a run in no cell of that row). None for an
|
|
97
|
+
* empty axis. */
|
|
98
|
+
headerRuns() {
|
|
99
|
+
const body = this.bodyColumns();
|
|
100
|
+
if (!body.length) return [];
|
|
101
|
+
const spec = this._spec().headerRows;
|
|
102
|
+
const n = this.table.config.headers?.length || 0;
|
|
103
|
+
return (Array.isArray(spec) ? spec : []).map((rowSpec) => {
|
|
104
|
+
const cells = normalizeSpans(rowSpec, n);
|
|
105
|
+
const cellOf = new Map();
|
|
106
|
+
cells.forEach((c) => { for (const col of c.columns) cellOf.set(col, c); });
|
|
107
|
+
const runs = [];
|
|
108
|
+
for (const col of body) {
|
|
109
|
+
const cell = cellOf.get(col) || null;
|
|
110
|
+
const last = runs[runs.length - 1];
|
|
111
|
+
if (last && last.cell === cell) last.span++;
|
|
112
|
+
else runs.push({ cell, span: 1, rowSpec });
|
|
113
|
+
}
|
|
114
|
+
return runs;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** STORY-0091. A body row: its pinned cells marked and put at the measured
|
|
119
|
+
* offsets (a row the virtual window draws later lines up at once), and
|
|
120
|
+
* the empty axis's cell. */
|
|
121
|
+
fillRow(tr) {
|
|
122
|
+
if (this.extraColumns()) {
|
|
123
|
+
const td = document.createElement('td');
|
|
124
|
+
td.className = 'twm-dt-crosstab__none';
|
|
125
|
+
tr.appendChild(td);
|
|
126
|
+
}
|
|
127
|
+
this._pinRowCells(tr);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_pinRowCells(tr) {
|
|
131
|
+
const p = this.pinnedCount();
|
|
132
|
+
for (let j = 0; j < p && j < tr.children.length; j++) {
|
|
133
|
+
const cell = tr.children[j];
|
|
134
|
+
cell.classList.add('twm-dt-pin');
|
|
135
|
+
if (j === p - 1) cell.classList.add('twm-dt-pin--last');
|
|
136
|
+
cell.__pinCol = j;
|
|
137
|
+
if (this._lefts[j] != null) cell.style.left = `${this._lefts[j]}px`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* After `_createTable`: the header rows above the column headers, the
|
|
143
|
+
* corner, the pinned header cells and the empty axis's header — all in the
|
|
144
|
+
* body table's own `<thead>`.
|
|
145
|
+
*/
|
|
146
|
+
decorate(tableEl) {
|
|
147
|
+
this._leafRow = null;
|
|
148
|
+
const thead = tableEl?.querySelector('thead');
|
|
149
|
+
const leaf = thead?.querySelector('tr');
|
|
150
|
+
if (!leaf) return;
|
|
151
|
+
tableEl.classList.add('twm-dt-crosstab');
|
|
152
|
+
this._leafRow = leaf;
|
|
153
|
+
leaf.classList.add('twm-dt-crosstab__leaf');
|
|
154
|
+
const filters = [...thead.querySelectorAll('tr.twm-data-table__filter-row')];
|
|
155
|
+
if (this.extraColumns()) {
|
|
156
|
+
const th = document.createElement('th');
|
|
157
|
+
th.className = 'text twm-dt-crosstab__none';
|
|
158
|
+
const label = this._spec().emptyLabel;
|
|
159
|
+
th.textContent = label == null ? DEFAULT_EMPTY_LABEL : String(label);
|
|
160
|
+
leaf.appendChild(th);
|
|
161
|
+
for (const tr of filters) {
|
|
162
|
+
const cell = document.createElement('th');
|
|
163
|
+
cell.className = 'data-table__filter-cell data-table__filter-cell--empty twm-dt-crosstab__none';
|
|
164
|
+
tr.appendChild(cell);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const tr of [leaf, ...filters]) this._pinRowCells(tr);
|
|
168
|
+
|
|
169
|
+
const levels = this.headerRuns();
|
|
170
|
+
const p = this.pinnedCount();
|
|
171
|
+
levels.forEach((runs, r) => {
|
|
172
|
+
const tr = document.createElement('tr');
|
|
173
|
+
tr.className = 'twm-dt-crosstab__band';
|
|
174
|
+
// THE CORNER: one cell per pinned column, spanning every header
|
|
175
|
+
// row down to the column headers, so each pins at its own `left`.
|
|
176
|
+
if (r === 0) {
|
|
177
|
+
for (let j = 0; j < p; j++) {
|
|
178
|
+
const th = document.createElement('th');
|
|
179
|
+
th.className = 'twm-dt-crosstab__corner twm-dt-pin';
|
|
180
|
+
if (j === p - 1) th.classList.add('twm-dt-pin--last');
|
|
181
|
+
th.rowSpan = levels.length;
|
|
182
|
+
th.__pinCol = j;
|
|
183
|
+
tr.appendChild(th);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const run of runs) tr.appendChild(this._bandCell(run));
|
|
187
|
+
thead.insertBefore(tr, leaf);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** A header row's cell: STORY-0066's spanning cells, the consumer's
|
|
192
|
+
* `className` and `title` on a labelled one. */
|
|
193
|
+
_bandCell({ cell, span, rowSpec }) {
|
|
194
|
+
if (!cell) return blankSpanCell(span);
|
|
195
|
+
const own = rowSpec[cell.index];
|
|
196
|
+
const th = labelSpanCell(span, cell.label, own?.title == null ? cell.label : String(own.title));
|
|
197
|
+
if (own?.className) this.table._addClasses(th, own.className);
|
|
198
|
+
return th;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Once the table is in the document: pin now (a layout already there is
|
|
203
|
+
* used at once), on the next frame (when it has settled), and whenever a
|
|
204
|
+
* header row, a pinned header cell or the table's box changes size. The
|
|
205
|
+
* observer goes with the render (`_disposers`).
|
|
206
|
+
*
|
|
207
|
+
* The split layout's own resize observer and settle frame hang off its
|
|
208
|
+
* header table, which a crosstab has none of; so the virtual window's
|
|
209
|
+
* re-measure and resize (`_virtRemeasure`, `_virtUpdate`) run from here.
|
|
210
|
+
*/
|
|
211
|
+
install() {
|
|
212
|
+
const table = this.table;
|
|
213
|
+
const tableEl = table._tableEl;
|
|
214
|
+
if (!tableEl || !this._leafRow) return;
|
|
215
|
+
this.pin();
|
|
216
|
+
requestAnimationFrame(() => {
|
|
217
|
+
if (table._tableEl !== tableEl) return;
|
|
218
|
+
this.pin();
|
|
219
|
+
table._virtRemeasure();
|
|
220
|
+
});
|
|
221
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
222
|
+
const observer = createRafResizeObserver(() => {
|
|
223
|
+
if (table._tableEl !== tableEl) return;
|
|
224
|
+
this.pin();
|
|
225
|
+
table._virtUpdate();
|
|
226
|
+
});
|
|
227
|
+
if (table._wrapperEl) observer.observe(table._wrapperEl);
|
|
228
|
+
const thead = tableEl.querySelector('thead');
|
|
229
|
+
for (const tr of thead.querySelectorAll('tr')) observer.observe(tr);
|
|
230
|
+
for (let j = 0; j < this.pinnedCount(); j++) {
|
|
231
|
+
const cell = this._leafRow.children[j];
|
|
232
|
+
if (cell) observer.observe(cell);
|
|
233
|
+
}
|
|
234
|
+
table._disposers.push(() => {
|
|
235
|
+
try { observer.disconnect(); } catch (_) { /* gone */ }
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Measure and write every offset: each header row's `top`, each pinned
|
|
240
|
+
* column's `left`. */
|
|
241
|
+
pin() {
|
|
242
|
+
const tableEl = this.table._tableEl;
|
|
243
|
+
const thead = tableEl?.querySelector('thead');
|
|
244
|
+
if (!thead || !this._leafRow) return;
|
|
245
|
+
const rows = [...thead.rows];
|
|
246
|
+
const scale = unzoom(thead);
|
|
247
|
+
const origin = rows[0].getBoundingClientRect().top;
|
|
248
|
+
for (const tr of rows) {
|
|
249
|
+
const top = Math.max(0, (tr.getBoundingClientRect().top - origin) * scale);
|
|
250
|
+
const css = `${+top.toFixed(2)}px`;
|
|
251
|
+
for (const th of tr.cells) th.style.top = css;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const p = this.pinnedCount();
|
|
255
|
+
const lefts = [0];
|
|
256
|
+
for (let j = 1; j < p; j++) {
|
|
257
|
+
const cell = this._leafRow.children[j - 1];
|
|
258
|
+
const w = cell ? cell.getBoundingClientRect().width * scale : 0;
|
|
259
|
+
lefts[j] = +(lefts[j - 1] + w).toFixed(2);
|
|
260
|
+
}
|
|
261
|
+
this._lefts = p ? lefts : [];
|
|
262
|
+
for (const cell of tableEl.querySelectorAll('.twm-dt-pin')) {
|
|
263
|
+
const left = this._lefts[cell.__pinCol];
|
|
264
|
+
if (left != null) cell.style.left = `${left}px`;
|
|
265
|
+
}
|
|
266
|
+
this._lockColumns(scale);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Under `virtualize` the rows drawn change with every scroll, and with
|
|
271
|
+
* them the widths an automatic layout gives the columns. The first pass
|
|
272
|
+
* that sees a layout writes the widths it measured on the column headers
|
|
273
|
+
* into the `<colgroup>` and fixes the layout, so the columns hold still.
|
|
274
|
+
* No layout (every width 0) is no measurement, and nothing is written.
|
|
275
|
+
*/
|
|
276
|
+
_lockColumns(scale) {
|
|
277
|
+
const cols = this.table._cols();
|
|
278
|
+
const tableEl = this.table._tableEl;
|
|
279
|
+
if (!cols || !tableEl || tableEl.__crosstabLocked) return;
|
|
280
|
+
const cells = this._leafRow.children;
|
|
281
|
+
if (cells.length !== cols.length) return;
|
|
282
|
+
const widths = [...cells].map((c) => c.getBoundingClientRect().width * scale);
|
|
283
|
+
if (!widths.every((w) => w > 0)) return;
|
|
284
|
+
widths.forEach((w, i) => { cols[i].style.width = `${+w.toFixed(2)}px`; });
|
|
285
|
+
tableEl.style.tableLayout = 'fixed';
|
|
286
|
+
tableEl.style.width = `${+widths.reduce((a, b) => a + b, 0).toFixed(2)}px`;
|
|
287
|
+
tableEl.__crosstabLocked = true;
|
|
288
|
+
}
|
|
289
|
+
}
|