argus-table 0.2.2 → 0.4.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/README.md +1 -1
- package/dist/argus-column-group.js +66 -0
- package/dist/argus-column-group.js.map +1 -0
- package/dist/argus-column.js +28 -26
- package/dist/argus-column.js.map +1 -1
- package/dist/argus-table.js +1497 -285
- package/dist/argus-table.js.map +1 -1
- package/dist/attributes.js +21 -0
- package/dist/attributes.js.map +1 -0
- package/dist/cdn/argus-table.js +120 -61
- package/dist/cdn/argus-table.js.map +1 -1
- package/dist/column-registry.js +23 -0
- package/dist/column-registry.js.map +1 -0
- package/dist/controllers/auto-width.js +16 -4
- package/dist/controllers/auto-width.js.map +1 -1
- package/dist/controllers/column-children.js +32 -9
- package/dist/controllers/column-children.js.map +1 -1
- package/dist/controllers/column-window.js +135 -0
- package/dist/controllers/column-window.js.map +1 -0
- package/dist/controllers/data-controller.js +9 -4
- package/dist/controllers/data-controller.js.map +1 -1
- package/dist/controllers/plugins.js +121 -0
- package/dist/controllers/plugins.js.map +1 -0
- package/dist/controllers/reorder-drag.js +19 -17
- package/dist/controllers/reorder-drag.js.map +1 -1
- package/dist/controllers/row-measure.js +122 -0
- package/dist/controllers/row-measure.js.map +1 -0
- package/dist/controllers/virtual-controller.js +315 -21
- package/dist/controllers/virtual-controller.js.map +1 -1
- package/dist/core/src/columns/column-groups.js +129 -0
- package/dist/core/src/columns/column-groups.js.map +1 -0
- package/dist/core/src/columns/column-order.js +125 -27
- package/dist/core/src/columns/column-order.js.map +1 -1
- package/dist/core/src/columns/resolve-columns.js +18 -5
- package/dist/core/src/columns/resolve-columns.js.map +1 -1
- package/dist/core/src/columns/spread-width.js +43 -0
- package/dist/core/src/columns/spread-width.js.map +1 -0
- package/dist/core/src/data/page-cache.js +4 -0
- package/dist/core/src/data/page-cache.js.map +1 -1
- package/dist/core/src/filters/filter.js +183 -0
- package/dist/core/src/filters/filter.js.map +1 -0
- package/dist/core/src/grid/row-layout.js +19 -11
- package/dist/core/src/grid/row-layout.js.map +1 -1
- package/dist/core/src/state/selection.js +71 -0
- package/dist/core/src/state/selection.js.map +1 -0
- package/dist/core/src/table/create-table.js +276 -66
- package/dist/core/src/table/create-table.js.map +1 -1
- package/dist/core/src/table/filtered-row-model.js +48 -0
- package/dist/core/src/table/filtered-row-model.js.map +1 -0
- package/dist/core/src/table/sorted-row-model.js +13 -2
- package/dist/core/src/table/sorted-row-model.js.map +1 -1
- package/dist/core/src/virtual/column-window.js +72 -0
- package/dist/core/src/virtual/column-window.js.map +1 -0
- package/dist/core/src/virtual/row-pool.js +2 -1
- package/dist/core/src/virtual/row-pool.js.map +1 -1
- package/dist/core/src/virtual/row-sizes.js +152 -0
- package/dist/core/src/virtual/row-sizes.js.map +1 -0
- package/dist/core/src/virtual/scroll-scale.js +41 -3
- package/dist/core/src/virtual/scroll-scale.js.map +1 -1
- package/dist/custom-elements.json +1683 -211
- package/dist/events.js +16 -8
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +808 -127
- package/dist/index.js +4 -2
- package/dist/interactive.js +22 -0
- package/dist/interactive.js.map +1 -0
- package/dist/max-height.js +22 -8
- package/dist/max-height.js.map +1 -1
- package/dist/render/body.js +48 -8
- package/dist/render/body.js.map +1 -1
- package/dist/render/cells.js +22 -11
- package/dist/render/cells.js.map +1 -1
- package/dist/render/content-directive.js +115 -0
- package/dist/render/content-directive.js.map +1 -0
- package/dist/render/content.js +33 -10
- package/dist/render/content.js.map +1 -1
- package/dist/render/header.js +104 -22
- package/dist/render/header.js.map +1 -1
- package/dist/render/pinned-rows.js +1 -1
- package/dist/render/pinned-rows.js.map +1 -1
- package/dist/styles/base.js +1 -1
- package/dist/styles/base.js.map +1 -1
- package/package.json +1 -1
package/dist/argus-table.js
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
|
+
import { clickSelection, sameSelection, selectAll, selectRange, toggleRow } from "./core/src/state/selection.js";
|
|
1
2
|
import { controllable } from "./core/src/state/controllable.js";
|
|
3
|
+
import { groupKey } from "./core/src/columns/column-groups.js";
|
|
2
4
|
import { RowLayout } from "./core/src/grid/row-layout.js";
|
|
5
|
+
import { sameUnit } from "./core/src/columns/column-order.js";
|
|
6
|
+
import { spreadWidth } from "./core/src/columns/spread-width.js";
|
|
7
|
+
import "./core/src/table/create-table.js";
|
|
3
8
|
import { announce } from "./announce.js";
|
|
4
9
|
import { AutoWidths, measureColumns } from "./controllers/auto-width.js";
|
|
5
10
|
import { ColumnChildrenController } from "./controllers/column-children.js";
|
|
11
|
+
import { ColumnWindowController } from "./controllers/column-window.js";
|
|
6
12
|
import { DataController } from "./controllers/data-controller.js";
|
|
7
13
|
import { PinScrollController } from "./controllers/pin-scroll-controller.js";
|
|
14
|
+
import { PluginController, sameItems } from "./controllers/plugins.js";
|
|
15
|
+
import { RowMeasure } from "./controllers/row-measure.js";
|
|
8
16
|
import { canStartReorder, dropTarget, startReorderDrag } from "./controllers/reorder-drag.js";
|
|
9
17
|
import { startResizeDrag } from "./controllers/resize-drag.js";
|
|
10
18
|
import { TableController } from "./controllers/table-controller.js";
|
|
11
19
|
import { VirtualController } from "./controllers/virtual-controller.js";
|
|
12
|
-
import { CELL_CLICK,
|
|
20
|
+
import { CELL_CLICK, COLUMN_FILTERS_CHANGE, COLUMN_ORDER_CHANGE, COLUMN_PINNING_CHANGE, COLUMN_SIZING_CHANGE, COLUMN_VISIBILITY_CHANGE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SELECTION_CHANGE, SORTING_CHANGE, fire } from "./events.js";
|
|
21
|
+
import { fromInteractive } from "./interactive.js";
|
|
13
22
|
import { bodyCap, watchMaxElementHeight } from "./max-height.js";
|
|
14
23
|
import { renderBody } from "./render/body.js";
|
|
15
24
|
import { renderLightDom } from "./render/cells.js";
|
|
16
|
-
import {
|
|
25
|
+
import { content, flushContent } from "./render/content-directive.js";
|
|
26
|
+
import { columnKey, renderHeader } from "./render/header.js";
|
|
17
27
|
import { renderPinnedRows } from "./render/pinned-rows.js";
|
|
18
28
|
import { baseStyles } from "./styles/base.js";
|
|
29
|
+
import { switchConverter } from "./attributes.js";
|
|
19
30
|
import __decorate from "./_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.js";
|
|
20
31
|
import { LitElement, html, nothing } from "lit";
|
|
21
32
|
import { customElement, property } from "lit/decorators.js";
|
|
@@ -34,24 +45,34 @@ function attachGridInternals(host) {
|
|
|
34
45
|
}
|
|
35
46
|
/**
|
|
36
47
|
* The sortable column a header click came from, if any. Works for slotted content
|
|
37
|
-
* too. Clicks on a resize handle never sort, so a click that ends a drag doesn't
|
|
48
|
+
* too. Clicks on a resize handle never sort, so a click that ends a drag doesn't,
|
|
49
|
+
* and neither do clicks on interactive content in the header (a button, an input).
|
|
38
50
|
*/
|
|
39
51
|
function sortColumnOf(event) {
|
|
40
52
|
for (const target of event.composedPath()) {
|
|
41
53
|
if (!(target instanceof HTMLElement)) continue;
|
|
42
54
|
if (target.dataset.resizeCol !== void 0) return void 0;
|
|
55
|
+
if (target.matches("button,input,select,textarea,a[href],label,[contenteditable],[popover],dialog")) return void 0;
|
|
43
56
|
if (target.dataset.sortCol !== void 0) return target.dataset.sortCol;
|
|
44
|
-
if (target.getAttribute("
|
|
57
|
+
if (target.getAttribute("role") === "row") return void 0;
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
60
|
var RESIZE_STEP = 10;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
/** `row-height`: px (`48` or `48px`) or `auto`; anything else is unset. */
|
|
62
|
+
var rowHeightConverter = { fromAttribute(value) {
|
|
63
|
+
if (value === null) return void 0;
|
|
64
|
+
const text = value.trim().toLowerCase();
|
|
65
|
+
if (text === "auto") return "auto";
|
|
66
|
+
const px = Number(text.replace(/px$/, ""));
|
|
67
|
+
return text !== "" && px > 0 ? px : void 0;
|
|
68
|
+
} };
|
|
69
|
+
/** Where the rows sit in the grid: header rows, pinned top, body, pinned bottom. */
|
|
53
70
|
function layoutOf(model) {
|
|
54
|
-
return new RowLayout(model.pinnedCount("top"), model.rowCount, model.pinnedCount("bottom"));
|
|
71
|
+
return new RowLayout(model.pinnedCount("top"), model.rowCount, model.pinnedCount("bottom"), model.headerRows);
|
|
72
|
+
}
|
|
73
|
+
/** The drop slot and applied-move key of a column or group (`HeaderDrag.key`). */
|
|
74
|
+
function unitKey(unit) {
|
|
75
|
+
return unit.kind === "column" ? columnKey(unit.id) : groupKey(unit.id, unit.pin);
|
|
55
76
|
}
|
|
56
77
|
/** The grid cell or column header an element is, if any. */
|
|
57
78
|
function gridCell(target) {
|
|
@@ -59,18 +80,26 @@ function gridCell(target) {
|
|
|
59
80
|
const role = target.getAttribute("role");
|
|
60
81
|
return role === "gridcell" || role === "columnheader" ? target : void 0;
|
|
61
82
|
}
|
|
83
|
+
/** The column group header an element is, if any. */
|
|
84
|
+
function groupCell(target) {
|
|
85
|
+
return target instanceof HTMLElement && target.dataset.groupKey !== void 0 ? target : void 0;
|
|
86
|
+
}
|
|
62
87
|
/** The grid row a rendered row element is, from its `aria-rowindex`. */
|
|
63
88
|
function gridRowOf(row, layout) {
|
|
64
89
|
const index = row?.getAttribute("aria-rowindex");
|
|
65
90
|
return index ? layout.fromAriaRowIndex(Number(index)) : void 0;
|
|
66
91
|
}
|
|
92
|
+
/** The grid row a cell or column header is in. */
|
|
93
|
+
function gridRowOfCell(cell, layout) {
|
|
94
|
+
return gridRowOf(cell.parentElement, layout);
|
|
95
|
+
}
|
|
67
96
|
/**
|
|
68
97
|
* The pinned edge's shadow, drawn once per side over the scroller rather than
|
|
69
98
|
* per row: a shadow on every pinned cell repaints on every scroll frame. It
|
|
70
99
|
* covers the pinned columns and casts `--argus-pinned-shadow` onto the rest;
|
|
71
100
|
* PinScrollController sizes it to the visible area.
|
|
72
101
|
*/
|
|
73
|
-
function pinShadow(side, width,
|
|
102
|
+
function pinShadow(side, width, contentPx) {
|
|
74
103
|
if (width === 0) return nothing;
|
|
75
104
|
return html`<div
|
|
76
105
|
class="pin-shadow"
|
|
@@ -78,7 +107,7 @@ function pinShadow(side, width, rows, rowPx) {
|
|
|
78
107
|
aria-hidden="true"
|
|
79
108
|
style=${styleMap({
|
|
80
109
|
inlineSize: `${String(width)}px`,
|
|
81
|
-
"--_content-h": `${String(
|
|
110
|
+
"--_content-h": `${String(contentPx)}px`
|
|
82
111
|
})}
|
|
83
112
|
></div>`;
|
|
84
113
|
}
|
|
@@ -86,18 +115,308 @@ function pinShadow(side, width, rows, rowPx) {
|
|
|
86
115
|
function track(column) {
|
|
87
116
|
return column.flex === void 0 ? `${String(column.width)}px` : `minmax(${String(column.minWidth)}px, ${String(column.flex)}fr)`;
|
|
88
117
|
}
|
|
118
|
+
/** Total width of the columns pinned to one side. */
|
|
119
|
+
function pinnedWidth(columns, side) {
|
|
120
|
+
return columns.reduce((sum, c) => c.pin === side ? sum + c.width : sum, 0);
|
|
121
|
+
}
|
|
89
122
|
var ArgusTable = class ArgusTable extends LitElement {
|
|
90
|
-
|
|
91
|
-
|
|
123
|
+
static {
|
|
124
|
+
this.styles = baseStyles;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Column definitions. Property only: functions can't be attributes. While empty,
|
|
128
|
+
* the table reads its `<argus-column>` children instead. `null` or `undefined`
|
|
129
|
+
* means `[]`.
|
|
130
|
+
*/
|
|
131
|
+
get columns() {
|
|
132
|
+
return this.#columnsProp;
|
|
133
|
+
}
|
|
134
|
+
set columns(value) {
|
|
135
|
+
this.#columnsProp = value ?? [];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Headers over several columns: `{ id, header, columns }`, where `columns` lists
|
|
139
|
+
* column ids and nested groups. Each level of nesting adds a header row. A group's
|
|
140
|
+
* columns stay next to each other: they move within the group, and the group
|
|
141
|
+
* moves as a whole (drag its header, or Alt+Shift+Arrow). A group whose columns
|
|
142
|
+
* are pinned to different sides shows on each side. Property only. While empty
|
|
143
|
+
* and `columns` is too, the table reads its `<argus-column-group>` children.
|
|
144
|
+
* `null` or `undefined` means `[]`.
|
|
145
|
+
*/
|
|
146
|
+
get columnGroups() {
|
|
147
|
+
return this.#columnGroupsProp;
|
|
148
|
+
}
|
|
149
|
+
set columnGroups(value) {
|
|
150
|
+
this.#columnGroupsProp = value ?? [];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The rows to show. Replace the array to update; changes inside it aren't
|
|
154
|
+
* detected. `null` or `undefined` means `[]`.
|
|
155
|
+
*/
|
|
156
|
+
get data() {
|
|
157
|
+
return this.#dataProp;
|
|
158
|
+
}
|
|
159
|
+
set data(value) {
|
|
160
|
+
this.#dataProp = value ?? [];
|
|
161
|
+
}
|
|
162
|
+
/** Rows rendered above and below the visible ones. `null` or `undefined` means the default, 6. */
|
|
163
|
+
get overscan() {
|
|
164
|
+
return this.#overscan;
|
|
165
|
+
}
|
|
166
|
+
set overscan(value) {
|
|
167
|
+
this.#overscan = value ?? 6;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The sort order, e.g. `[{ id: 'total', desc: true }]`; `[]` when unsorted. Unset,
|
|
171
|
+
* the table keeps it itself and header clicks change it. Once set, it is
|
|
172
|
+
* controlled: a click only fires `argus-sorting-change` with the proposed order,
|
|
173
|
+
* and nothing changes until you set the property. Set `undefined` to hand it back.
|
|
174
|
+
*/
|
|
175
|
+
get sorting() {
|
|
176
|
+
return this.#sorting.value;
|
|
177
|
+
}
|
|
178
|
+
set sorting(value) {
|
|
179
|
+
this.#sorting.set(value);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Resized widths in px by column id, e.g. `{ name: 240 }`. A resized flex column
|
|
183
|
+
* becomes fixed. Uncontrolled until set, like `sorting`: once set, a resize only
|
|
184
|
+
* fires `argus-column-sizing-change` with the proposed widths. `undefined` hands it back.
|
|
185
|
+
*/
|
|
186
|
+
get columnSizing() {
|
|
187
|
+
return this.#sizing.value;
|
|
188
|
+
}
|
|
189
|
+
set columnSizing(value) {
|
|
190
|
+
this.#sizing.set(value);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The column order: column ids as the table shows them, e.g.
|
|
194
|
+
* `['id', 'total', 'customer']`. `[]` (the default) keeps the order of `columns`.
|
|
195
|
+
* Each pin group (start-pinned, scrolling, end-pinned) takes its columns in this
|
|
196
|
+
* order; unknown ids are ignored and columns it doesn't list follow the listed
|
|
197
|
+
* ones of their group. After a move it holds every column id. Uncontrolled
|
|
198
|
+
* until set, like `sorting`: once set, a move only fires `argus-column-order-change`
|
|
199
|
+
* with the proposed order. `undefined` hands it back.
|
|
200
|
+
*/
|
|
201
|
+
get columnOrder() {
|
|
202
|
+
return this.#order.value;
|
|
203
|
+
}
|
|
204
|
+
set columnOrder(value) {
|
|
205
|
+
this.#order.set(value);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Pins by column id, over the columns' own `pin`: `'start'` or `'end'` pins a
|
|
209
|
+
* column, `false` unpins one whose definition pins it, e.g.
|
|
210
|
+
* `{ total: 'end', id: false }`. Columns it doesn't list keep their `pin`. A pinned
|
|
211
|
+
* column joins the edge of its group nearest its place in `columnOrder`, and stops
|
|
212
|
+
* flexing. Uncontrolled until set, like `sorting`: once set, pinning from a plugin
|
|
213
|
+
* only fires `argus-column-pinning-change` with the proposed pins. `undefined` hands it back.
|
|
214
|
+
*/
|
|
215
|
+
get columnPinning() {
|
|
216
|
+
return this.#pinning.value;
|
|
217
|
+
}
|
|
218
|
+
set columnPinning(value) {
|
|
219
|
+
this.#pinning.set(value);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Visibility by column id, over the columns' own `hidden`: `false` hides a column,
|
|
223
|
+
* `true` shows one defined as `hidden`, e.g. `{ email: false }`. Columns it
|
|
224
|
+
* doesn't list keep their `hidden`. A hidden column isn't rendered, but sorting by
|
|
225
|
+
* it still works and it keeps its place in `columnOrder`. Uncontrolled until set,
|
|
226
|
+
* like `sorting`: once set, hiding or showing from a plugin only fires
|
|
227
|
+
* `argus-column-visibility-change` with the proposed visibility. `undefined` hands it back.
|
|
228
|
+
*/
|
|
229
|
+
get columnVisibility() {
|
|
230
|
+
return this.#visibility.value;
|
|
231
|
+
}
|
|
232
|
+
set columnVisibility(value) {
|
|
233
|
+
this.#visibility.set(value);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Filters by column id, e.g. `{ city: { kind: 'set', values: ['Berlin'] } }` or
|
|
237
|
+
* `{ total: { kind: 'number', op: 'gt', value: 100 } }`. A row shows when it passes
|
|
238
|
+
* every filter and `quickFilter`. Filters on hidden columns still apply; unknown ids
|
|
239
|
+
* are ignored. A column's `filterFn` replaces the built-in matching. With a
|
|
240
|
+
* `dataSource`, the filters go to `getRows()` and the server filters. Users set
|
|
241
|
+
* filters through plugins (the column menu) on columns with a `filter` kind.
|
|
242
|
+
* Uncontrolled until set, like `sorting`: once set, a change from a plugin only
|
|
243
|
+
* fires `argus-column-filters-change` with the proposed filters. `undefined` hands it back.
|
|
244
|
+
*/
|
|
245
|
+
get columnFilters() {
|
|
246
|
+
return this.#filters.value;
|
|
247
|
+
}
|
|
248
|
+
set columnFilters(value) {
|
|
249
|
+
this.#filters.set(value);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Shows only rows that contain each word of this text in one of their shown
|
|
253
|
+
* columns, ignoring case, e.g. from a search box. Values are matched as text
|
|
254
|
+
* (dates as `2026-11-10`); objects aren't searched. With a `dataSource`, it goes
|
|
255
|
+
* to `getRows()` and the server filters. `''` (the default) shows every row.
|
|
256
|
+
*/
|
|
257
|
+
get quickFilter() {
|
|
258
|
+
return this.#quickFilter;
|
|
259
|
+
}
|
|
260
|
+
set quickFilter(value) {
|
|
261
|
+
this.#quickFilter = value ?? "";
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* The selected rows, as row ids (from `getRowId`, or the row's index as a
|
|
265
|
+
* string), e.g. `['p4', 'p7']`. Uncontrolled until set, like `sorting`: once
|
|
266
|
+
* set, a click only fires `argus-selection-change` with the proposed ids.
|
|
267
|
+
* `undefined` hands it back. The table never drops ids on its own: they stay
|
|
268
|
+
* through sorting, a new `data` or reloaded pages. With a `dataSource`,
|
|
269
|
+
* Ctrl/Cmd+A selects the loaded rows only.
|
|
270
|
+
*/
|
|
271
|
+
get selection() {
|
|
272
|
+
return this.#selection.value;
|
|
273
|
+
}
|
|
274
|
+
set selection(value) {
|
|
275
|
+
this.#selection.set(value);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Add-ons, such as `headerMenu()` from `@argus-table/header-menu`. Each one can
|
|
279
|
+
* change the column definitions, handle keys on a focused header or cell, and
|
|
280
|
+
* use the table through a small API. They run in array order. Replace the
|
|
281
|
+
* array to change them; an array with the same plugins in the same order
|
|
282
|
+
* changes nothing. `null` or `undefined` means `[]`.
|
|
283
|
+
*/
|
|
284
|
+
get plugins() {
|
|
285
|
+
return this.#pluginsProp;
|
|
286
|
+
}
|
|
287
|
+
set plugins(value) {
|
|
288
|
+
const next = value ?? [];
|
|
289
|
+
if (!sameItems(next, this.#pluginsProp)) this.#pluginsProp = next;
|
|
290
|
+
}
|
|
291
|
+
#columnsProp;
|
|
292
|
+
#columnGroupsProp;
|
|
293
|
+
/** The groups last passed to the model: `columnGroups`, or the `<argus-column-group>` children. */
|
|
294
|
+
#groups;
|
|
295
|
+
#groupsChanged;
|
|
296
|
+
#pluginsProp;
|
|
297
|
+
#dataProp;
|
|
298
|
+
#overscan;
|
|
299
|
+
#sorting;
|
|
300
|
+
#sizing;
|
|
301
|
+
#order;
|
|
302
|
+
#pinning;
|
|
303
|
+
#visibility;
|
|
304
|
+
#filters;
|
|
305
|
+
#quickFilter;
|
|
306
|
+
/** Filters the table applied itself, by column; their events fire once the update is done. */
|
|
307
|
+
#appliedFilters;
|
|
308
|
+
/** Announce the row count after the update: the filters changed. */
|
|
309
|
+
#announceRows;
|
|
310
|
+
/** Pins the table applied itself, by column; their events fire once the update is done. */
|
|
311
|
+
#appliedPins;
|
|
312
|
+
/** Hidden or shown columns the table applied itself; their events fire once the update is done. */
|
|
313
|
+
#appliedHides;
|
|
314
|
+
#selection;
|
|
315
|
+
/** `selection` as a set, for the rows rendered; rebuilt when `selection` changes. */
|
|
316
|
+
#selectedIds;
|
|
317
|
+
/** The row a Shift range starts from: the last row clicked or toggled without Shift. */
|
|
318
|
+
#anchor;
|
|
319
|
+
/** A selection the table applied itself; its event fires once the update is done. */
|
|
320
|
+
#appliedSelection;
|
|
321
|
+
/** Whether the checkbox column was last shown; a change re-measures the pinned columns. */
|
|
322
|
+
#selectionColumn;
|
|
323
|
+
#selectionColumnChanged;
|
|
324
|
+
/** The select-all checkbox's state, kept until the selection or the shown rows change. */
|
|
325
|
+
#checkAll;
|
|
326
|
+
/** Moves the table applied itself, by column or group; their events fire once the update is done. */
|
|
327
|
+
#appliedReorders;
|
|
328
|
+
/** The header being dragged to a new position, if any. */
|
|
329
|
+
#drag;
|
|
330
|
+
/** A move from the keyboard, announced once it has rendered: what moved and its old position. */
|
|
331
|
+
#announceMove;
|
|
332
|
+
/** Resizes the table applied itself, by column; their events fire once the update is done. */
|
|
333
|
+
#appliedResizes;
|
|
334
|
+
/** Columns `autosizeColumns()` renders out of view for its measurement. */
|
|
335
|
+
#measureKeep;
|
|
336
|
+
/** Measured widths of `width: 'auto'` columns. */
|
|
337
|
+
#autoWidths;
|
|
338
|
+
/** The auto widths last passed to the model; a change re-measures the pinned columns' offsets. */
|
|
339
|
+
#pushedAutoWidths;
|
|
340
|
+
#autoWidthsChanged;
|
|
341
|
+
/** A sort the table applied itself; its event fires once the update is done. */
|
|
342
|
+
#appliedSort;
|
|
343
|
+
#internals;
|
|
344
|
+
#table;
|
|
345
|
+
#children;
|
|
346
|
+
/** What plugins get: closures over the table's private state. */
|
|
347
|
+
#pluginHost;
|
|
348
|
+
#plugins;
|
|
349
|
+
#columnsById;
|
|
350
|
+
/** The columns last passed to the model: `columns`, or the `<argus-column>` children. */
|
|
351
|
+
#columns;
|
|
352
|
+
#columnsChanged;
|
|
353
|
+
#warnedColumns;
|
|
354
|
+
/** The header row of column headers at the last reconcile (`#reconcileActive`). */
|
|
355
|
+
#leafLevel;
|
|
356
|
+
/** End of the light-DOM part holding cell content, so later children stay outside it. */
|
|
357
|
+
#contentEnd;
|
|
358
|
+
/** The light-DOM part: disconnected with the table, so foreign content is released. */
|
|
359
|
+
#lightPart;
|
|
360
|
+
#lightConnected;
|
|
361
|
+
#data;
|
|
362
|
+
/** Scroll back to the top after the update (a new sort order for a data source). */
|
|
363
|
+
#scrollToTop;
|
|
364
|
+
#virtual;
|
|
365
|
+
#pinScroll;
|
|
366
|
+
#columnWindow;
|
|
367
|
+
/** The columns the last render rendered (all of them, unless `virtualizeColumns`). */
|
|
368
|
+
#rendered;
|
|
369
|
+
#rowPx;
|
|
370
|
+
#probeObserver;
|
|
371
|
+
/** The column template and row height the rows were last read for. */
|
|
372
|
+
#rowsKey;
|
|
373
|
+
/** Rows of different heights: reads the rendered rows (`rowHeight: 'auto'` only). */
|
|
374
|
+
#rowMeasure;
|
|
375
|
+
#window;
|
|
376
|
+
#active;
|
|
377
|
+
/** Move focus to the active cell after the next update (keyboard navigation). */
|
|
378
|
+
#focusPending;
|
|
379
|
+
#lastRange;
|
|
380
|
+
#unwatchMaxHeight;
|
|
381
|
+
/**
|
|
382
|
+
* The row `setState()` still has to scroll to: a data source's rows that far
|
|
383
|
+
* aren't counted yet. Scrolled to again whenever the row count changes.
|
|
384
|
+
*/
|
|
385
|
+
#scrollTarget;
|
|
386
|
+
/** The scroll container (`part="table"`); `null` before the first render. */
|
|
387
|
+
#scroller() {
|
|
388
|
+
return this.renderRoot.querySelector("[part~=\"table\"]");
|
|
389
|
+
}
|
|
390
|
+
constructor() {
|
|
391
|
+
super();
|
|
92
392
|
this.density = "comfortable";
|
|
393
|
+
this.virtualizeColumns = false;
|
|
394
|
+
this.selectionMode = "none";
|
|
395
|
+
this.selectionCheckboxes = false;
|
|
93
396
|
this.#columnsProp = [];
|
|
397
|
+
this.#columnGroupsProp = [];
|
|
398
|
+
this.#groups = [];
|
|
399
|
+
this.#groupsChanged = false;
|
|
400
|
+
this.#pluginsProp = [];
|
|
94
401
|
this.#dataProp = [];
|
|
95
402
|
this.#overscan = 6;
|
|
96
403
|
this.#sorting = controllable([]);
|
|
97
404
|
this.#sizing = controllable({});
|
|
98
405
|
this.#order = controllable([]);
|
|
406
|
+
this.#pinning = controllable({});
|
|
407
|
+
this.#visibility = controllable({});
|
|
408
|
+
this.#filters = controllable({});
|
|
409
|
+
this.#quickFilter = "";
|
|
410
|
+
this.#appliedFilters = /* @__PURE__ */ new Map();
|
|
411
|
+
this.#announceRows = false;
|
|
412
|
+
this.#appliedPins = /* @__PURE__ */ new Map();
|
|
413
|
+
this.#appliedHides = /* @__PURE__ */ new Map();
|
|
414
|
+
this.#selection = controllable([]);
|
|
415
|
+
this.#selectionColumn = false;
|
|
416
|
+
this.#selectionColumnChanged = false;
|
|
99
417
|
this.#appliedReorders = /* @__PURE__ */ new Map();
|
|
100
418
|
this.#appliedResizes = /* @__PURE__ */ new Map();
|
|
419
|
+
this.#measureKeep = [];
|
|
101
420
|
this.#autoWidths = new AutoWidths(() => {
|
|
102
421
|
this.requestUpdate();
|
|
103
422
|
});
|
|
@@ -105,14 +424,67 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
105
424
|
this.#internals = attachGridInternals(this);
|
|
106
425
|
this.#table = new TableController();
|
|
107
426
|
this.#children = new ColumnChildrenController(this);
|
|
427
|
+
this.#pluginHost = {
|
|
428
|
+
element: this,
|
|
429
|
+
column: (id) => this.#columnInfo(id),
|
|
430
|
+
columns: () => this.#table.model.allColumns.flatMap((c) => {
|
|
431
|
+
const info = this.#columnInfo(c.id);
|
|
432
|
+
return info ? [info] : [];
|
|
433
|
+
}),
|
|
434
|
+
setPin: (columnId, pin) => {
|
|
435
|
+
this.#requestPin(columnId, pin);
|
|
436
|
+
},
|
|
437
|
+
setHidden: (columnId, hidden) => {
|
|
438
|
+
this.#requestHide(columnId, hidden);
|
|
439
|
+
},
|
|
440
|
+
setFilter: (columnId, filter) => {
|
|
441
|
+
this.#requestFilter(columnId, filter);
|
|
442
|
+
},
|
|
443
|
+
filterValues: (columnId, limit) => this.#table.model.filterValues(columnId, limit),
|
|
444
|
+
filteredIndexes: (except) => this.#table.model.indexesPassing(except),
|
|
445
|
+
cellOf: (event) => this.#cellTarget(event),
|
|
446
|
+
setSort: (columnId, direction) => {
|
|
447
|
+
this.#setSort(columnId, direction);
|
|
448
|
+
},
|
|
449
|
+
focusHeader: (columnId) => {
|
|
450
|
+
const model = this.#table.model;
|
|
451
|
+
this.#moveTo(layoutOf(model).leafHeader, columnId, model);
|
|
452
|
+
},
|
|
453
|
+
announce: (message) => {
|
|
454
|
+
announce(this.ownerDocument, message);
|
|
455
|
+
},
|
|
456
|
+
invalidateColumns: () => {
|
|
457
|
+
this.#plugins.invalidate();
|
|
458
|
+
this.requestUpdate();
|
|
459
|
+
},
|
|
460
|
+
requestUpdate: () => {
|
|
461
|
+
this.requestUpdate();
|
|
462
|
+
},
|
|
463
|
+
row: (index) => this.#table.model.rowAt(index)?.original,
|
|
464
|
+
content: (value) => content(value)
|
|
465
|
+
};
|
|
466
|
+
this.#plugins = new PluginController(this, this.#pluginHost);
|
|
108
467
|
this.#columns = [];
|
|
109
468
|
this.#columnsChanged = false;
|
|
110
469
|
this.#warnedColumns = false;
|
|
470
|
+
this.#leafLevel = 0;
|
|
471
|
+
this.#lightConnected = true;
|
|
111
472
|
this.#data = new DataController(this);
|
|
112
473
|
this.#scrollToTop = false;
|
|
113
|
-
this.#virtual = new VirtualController(this, () => this
|
|
114
|
-
this.#pinScroll = new PinScrollController(this, () => this
|
|
474
|
+
this.#virtual = new VirtualController(this, () => this.#scroller());
|
|
475
|
+
this.#pinScroll = new PinScrollController(this, () => this.#scroller());
|
|
476
|
+
this.#columnWindow = new ColumnWindowController(this, () => this.#scroller());
|
|
477
|
+
this.#rendered = [];
|
|
115
478
|
this.#rowPx = 40;
|
|
479
|
+
this.#rowsKey = "";
|
|
480
|
+
this.#rowMeasure = new RowMeasure(this, {
|
|
481
|
+
bodyIndex: (row) => {
|
|
482
|
+
const at = gridRowOf(row, layoutOf(this.#table.model));
|
|
483
|
+
return at?.section === "body" && this.#data.stateAt(at.index) === "loaded" ? at.index : void 0;
|
|
484
|
+
},
|
|
485
|
+
sizeOf: (index) => this.#virtual.sizeOf(index),
|
|
486
|
+
minimum: () => this.#rowPx
|
|
487
|
+
});
|
|
116
488
|
this.#window = {
|
|
117
489
|
rows: [],
|
|
118
490
|
totalSize: 0,
|
|
@@ -120,6 +492,9 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
120
492
|
};
|
|
121
493
|
this.#focusPending = false;
|
|
122
494
|
this.#lastRange = "";
|
|
495
|
+
this.#dropScrollTarget = () => {
|
|
496
|
+
this.#scrollTarget = void 0;
|
|
497
|
+
};
|
|
123
498
|
this.#onRetry = (event) => {
|
|
124
499
|
const button = event.currentTarget;
|
|
125
500
|
if (button.matches(":focus")) button.parentElement?.focus({ preventScroll: true });
|
|
@@ -134,6 +509,15 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
134
509
|
if (event.button !== 0) return;
|
|
135
510
|
const handle = event.composedPath()[0];
|
|
136
511
|
if (!(handle instanceof HTMLElement)) return;
|
|
512
|
+
const group = this.#groupCellOf(handle.dataset.resizeGroup);
|
|
513
|
+
if (group) {
|
|
514
|
+
const starts = this.#groupWidths(group.columns);
|
|
515
|
+
const total = [...starts.values()].reduce((sum, w) => sum + w, 0);
|
|
516
|
+
startResizeDrag(handle, event, total, this.#direction(), (w) => {
|
|
517
|
+
this.#requestGroupResize(group.columns, starts, w);
|
|
518
|
+
});
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
137
521
|
const id = handle.dataset.resizeCol;
|
|
138
522
|
const cell = handle.parentElement;
|
|
139
523
|
if (id !== void 0 && cell) {
|
|
@@ -147,15 +531,23 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
147
531
|
/** A double-click on a resize handle fits the column to its rendered content. */
|
|
148
532
|
dblclick: (event) => {
|
|
149
533
|
const handle = event.composedPath()[0];
|
|
150
|
-
|
|
534
|
+
if (!(handle instanceof HTMLElement)) return;
|
|
535
|
+
const group = this.#groupCellOf(handle.dataset.resizeGroup);
|
|
536
|
+
if (group) {
|
|
537
|
+
this.autosizeColumns([...group.columns]);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const id = handle.dataset.resizeCol;
|
|
151
541
|
if (id === void 0) return;
|
|
152
542
|
this.#requestResizes(measureColumns(this.renderRoot, [id]));
|
|
153
543
|
}
|
|
154
544
|
};
|
|
155
545
|
this.#onKeydown = (event) => {
|
|
156
|
-
const
|
|
546
|
+
const origin = event.composedPath()[0] ?? event.target ?? this;
|
|
547
|
+
const group = groupCell(origin);
|
|
548
|
+
const cell = gridCell(origin) ?? group;
|
|
157
549
|
const id = cell?.dataset.col;
|
|
158
|
-
if (!cell || id === void 0) return;
|
|
550
|
+
if (!cell || id === void 0 && !group) return;
|
|
159
551
|
const scroller = event.currentTarget;
|
|
160
552
|
if (event.key === "Tab" && event.shiftKey) {
|
|
161
553
|
scroller.tabIndex = -1;
|
|
@@ -164,19 +556,22 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
164
556
|
}, { once: true });
|
|
165
557
|
return;
|
|
166
558
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const arrow = key === "ArrowLeft" || key === "ArrowRight";
|
|
170
|
-
if (inHeader && arrow && event.altKey && event.shiftKey) {
|
|
171
|
-
event.preventDefault();
|
|
172
|
-
const towardEnd = key === "ArrowRight" === (this.#direction() === 1);
|
|
173
|
-
this.#moveByKey(id, towardEnd ? 1 : -1);
|
|
559
|
+
if (group || id === void 0) {
|
|
560
|
+
this.#groupKey(event, cell, scroller);
|
|
174
561
|
return;
|
|
175
562
|
}
|
|
176
|
-
|
|
563
|
+
const pluginTarget = id === "--argus-selection" ? void 0 : this.#cellTarget(event);
|
|
564
|
+
if (pluginTarget && this.#plugins.keydown(event, pluginTarget)) return;
|
|
565
|
+
const inHeader = cell.getAttribute("role") === "columnheader";
|
|
566
|
+
const { key } = event;
|
|
567
|
+
if (inHeader && (key === "ArrowLeft" || key === "ArrowRight") && event.altKey) {
|
|
177
568
|
event.preventDefault();
|
|
178
569
|
const towardEnd = key === "ArrowRight" === (this.#direction() === 1);
|
|
179
|
-
|
|
570
|
+
if (event.shiftKey) this.#moveByKey({
|
|
571
|
+
kind: "column",
|
|
572
|
+
id
|
|
573
|
+
}, towardEnd ? 1 : -1);
|
|
574
|
+
else this.#requestResize(id, this.#currentWidth(id, cell) + (towardEnd ? RESIZE_STEP : -10));
|
|
180
575
|
return;
|
|
181
576
|
}
|
|
182
577
|
if (inHeader && (key === "Enter" || key === " ") && cell.dataset.sortCol !== void 0) {
|
|
@@ -184,11 +579,12 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
184
579
|
this.#requestSort(id);
|
|
185
580
|
return;
|
|
186
581
|
}
|
|
582
|
+
if (this.#selectionKey(event, cell, id, inHeader, scroller)) return;
|
|
187
583
|
const corner = event.ctrlKey || event.metaKey;
|
|
188
584
|
if (event.altKey || event.shiftKey || corner && key !== "Home" && key !== "End") return;
|
|
189
585
|
const model = this.#table.model;
|
|
190
586
|
const layout = layoutOf(model);
|
|
191
|
-
const from =
|
|
587
|
+
const from = gridRowOfCell(cell, layout);
|
|
192
588
|
if (!from) return;
|
|
193
589
|
if (!inHeader && key === "Enter") {
|
|
194
590
|
event.preventDefault();
|
|
@@ -199,26 +595,13 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
199
595
|
const columns = model.columns;
|
|
200
596
|
let pos = layout.position(from);
|
|
201
597
|
const last = layout.total - 1;
|
|
202
|
-
const bodyFirst = layout.position({
|
|
203
|
-
section: "body",
|
|
204
|
-
index: 0
|
|
205
|
-
});
|
|
206
|
-
const bodyLast = bodyFirst + layout.body - 1;
|
|
207
|
-
const fit = Math.floor(scroller.clientHeight / this.#rowPx + .01);
|
|
208
|
-
const page = Math.max(1, fit - 1 - layout.top - layout.bottom);
|
|
209
598
|
let col = columns.findIndex((c) => c.id === id);
|
|
210
599
|
switch (key) {
|
|
211
600
|
case "PageDown":
|
|
212
|
-
if (layout.body > 0 && pos <= bodyLast) pos = Math.min(Math.max(pos, bodyFirst - 1) + page, bodyLast);
|
|
213
|
-
break;
|
|
214
601
|
case "PageUp":
|
|
215
|
-
if (layout.body > 0 && pos > bodyFirst) pos = Math.max(Math.min(pos, bodyLast + 1) - page, bodyFirst);
|
|
216
|
-
break;
|
|
217
602
|
case "ArrowDown":
|
|
218
|
-
pos = Math.min(pos + 1, last);
|
|
219
|
-
break;
|
|
220
603
|
case "ArrowUp":
|
|
221
|
-
pos =
|
|
604
|
+
pos = this.#verticalStep(key, pos, layout, scroller);
|
|
222
605
|
break;
|
|
223
606
|
case "ArrowRight":
|
|
224
607
|
col += this.#direction();
|
|
@@ -229,7 +612,7 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
229
612
|
case "Home":
|
|
230
613
|
col = 0;
|
|
231
614
|
if (corner) {
|
|
232
|
-
pos = 0;
|
|
615
|
+
pos = this.#headerLevelDown(model, 0, columns[0]?.id ?? id);
|
|
233
616
|
this.#virtual.scrollToEdge("start");
|
|
234
617
|
}
|
|
235
618
|
break;
|
|
@@ -240,11 +623,20 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
240
623
|
default: return;
|
|
241
624
|
}
|
|
242
625
|
event.preventDefault();
|
|
243
|
-
|
|
244
|
-
if (key.startsWith("Page") && from.section === "body" && row.section === "body") this.#virtual.scrollBy((row.index - from.index) * this.#rowPx);
|
|
245
|
-
if (corner && key === "End" && row.section === "bottom") this.#virtual.scrollToEdge("end");
|
|
626
|
+
let row = layout.at(pos) ?? layout.leafHeader;
|
|
246
627
|
const target = columns[Math.min(Math.max(col, 0), columns.length - 1)];
|
|
247
|
-
if (target)
|
|
628
|
+
if (!target) return;
|
|
629
|
+
if (row.section === "header" && row.index < layout.header - 1 && key === "ArrowUp") {
|
|
630
|
+
const level = this.#headerLevelUp(model, row.index, target.id);
|
|
631
|
+
if (level === void 0) return;
|
|
632
|
+
row = {
|
|
633
|
+
section: "header",
|
|
634
|
+
index: level
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
if (key.startsWith("Page") && from.section === "body" && row.section === "body") this.#virtual.scrollBy(this.#virtual.startOf(row.index) - this.#virtual.startOf(from.index));
|
|
638
|
+
if (corner && key === "End" && row.section === "bottom") this.#virtual.scrollToEdge("end");
|
|
639
|
+
this.#moveTo(row, target.id, model);
|
|
248
640
|
};
|
|
249
641
|
this.#onFocusin = (event) => {
|
|
250
642
|
const scroller = event.currentTarget;
|
|
@@ -257,152 +649,99 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
257
649
|
return;
|
|
258
650
|
}
|
|
259
651
|
for (const target of event.composedPath()) {
|
|
260
|
-
const
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
const row = cell.getAttribute("role") === "columnheader" ? HEADER : gridRowOf(cell.parentElement, layoutOf(model));
|
|
264
|
-
const { col } = cell.dataset;
|
|
265
|
-
const prev = this.#active;
|
|
266
|
-
if (row && (row.section !== prev?.section || row.index !== prev.index || col !== prev.col)) {
|
|
267
|
-
this.#active = {
|
|
268
|
-
...row,
|
|
269
|
-
rowId: this.#gridRowId(model, row),
|
|
270
|
-
col
|
|
271
|
-
};
|
|
272
|
-
this.requestUpdate();
|
|
273
|
-
}
|
|
652
|
+
const group = groupCell(target);
|
|
653
|
+
if (group) {
|
|
654
|
+
this.#focusGroup(group);
|
|
274
655
|
return;
|
|
275
656
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
this.#
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
* until set, like `sorting`: once set, a move only fires `argus-column-reorder`
|
|
344
|
-
* with the proposed order. `undefined` hands it back.
|
|
345
|
-
*/
|
|
346
|
-
get columnOrder() {
|
|
347
|
-
return this.#order.value;
|
|
348
|
-
}
|
|
349
|
-
set columnOrder(value) {
|
|
350
|
-
this.#order.set(value);
|
|
351
|
-
}
|
|
352
|
-
#columnsProp;
|
|
353
|
-
#dataProp;
|
|
354
|
-
#overscan;
|
|
355
|
-
#sorting;
|
|
356
|
-
#sizing;
|
|
357
|
-
#order;
|
|
358
|
-
/** Moves the table applied itself, by column; their events fire once the update is done. */
|
|
359
|
-
#appliedReorders;
|
|
360
|
-
/** The header being dragged to a new position, if any. */
|
|
361
|
-
#drag;
|
|
362
|
-
/** A move from the keyboard, announced once it has rendered: the column and its old position. */
|
|
363
|
-
#announceMove;
|
|
364
|
-
/** Resizes the table applied itself, by column; their events fire once the update is done. */
|
|
365
|
-
#appliedResizes;
|
|
366
|
-
/** Measured widths of `width: 'auto'` columns. */
|
|
367
|
-
#autoWidths;
|
|
368
|
-
/** The auto widths last passed to the model; a change re-measures the pinned columns' offsets. */
|
|
369
|
-
#pushedAutoWidths;
|
|
370
|
-
#autoWidthsChanged;
|
|
371
|
-
/** A sort the table applied itself; its event fires once the update is done. */
|
|
372
|
-
#appliedSort;
|
|
373
|
-
#internals;
|
|
374
|
-
#table;
|
|
375
|
-
#children;
|
|
376
|
-
/** The columns last passed to the model: `columns`, or the `<argus-column>` children. */
|
|
377
|
-
#columns;
|
|
378
|
-
#columnsChanged;
|
|
379
|
-
#warnedColumns;
|
|
380
|
-
/** End of the light-DOM part holding cell content, so later children stay outside it. */
|
|
381
|
-
#contentEnd;
|
|
382
|
-
#data;
|
|
383
|
-
/** Scroll back to the top after the update (a new sort order for a data source). */
|
|
384
|
-
#scrollToTop;
|
|
385
|
-
#virtual;
|
|
386
|
-
#pinScroll;
|
|
387
|
-
#rowPx;
|
|
388
|
-
#probeObserver;
|
|
389
|
-
#window;
|
|
390
|
-
#active;
|
|
391
|
-
/** Move focus to the active cell after the next update (keyboard navigation). */
|
|
392
|
-
#focusPending;
|
|
393
|
-
#lastRange;
|
|
394
|
-
#unwatchMaxHeight;
|
|
657
|
+
const cell = gridCell(target);
|
|
658
|
+
if (cell?.dataset.col !== void 0) {
|
|
659
|
+
const model = this.#table.model;
|
|
660
|
+
const row = gridRowOfCell(cell, layoutOf(model));
|
|
661
|
+
const { col } = cell.dataset;
|
|
662
|
+
const prev = this.#active;
|
|
663
|
+
if (row && (row.section !== prev?.section || row.index !== prev.index || col !== prev.col)) {
|
|
664
|
+
this.#active = {
|
|
665
|
+
...row,
|
|
666
|
+
rowId: this.#gridRowId(model, row),
|
|
667
|
+
col
|
|
668
|
+
};
|
|
669
|
+
this.requestUpdate();
|
|
670
|
+
}
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (target === event.currentTarget) return;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
this.#onClick = (event) => {
|
|
677
|
+
const path = event.composedPath();
|
|
678
|
+
this.#activate(path);
|
|
679
|
+
const target = this.#mode() === "none" ? void 0 : this.#selectableRowOf(path);
|
|
680
|
+
if (!target) return;
|
|
681
|
+
const inSelectionCell = path.some((t) => t instanceof HTMLElement && t.dataset.col === "--argus-selection");
|
|
682
|
+
this.#clickRow(target, event, inSelectionCell || event.ctrlKey || event.metaKey);
|
|
683
|
+
};
|
|
684
|
+
this.#checkboxHandlers = {
|
|
685
|
+
row: (event) => {
|
|
686
|
+
const index = Number(event.currentTarget.dataset.row);
|
|
687
|
+
const id = this.#table.model.rowAt(index)?.id;
|
|
688
|
+
if (id !== void 0) this.#clickRow({
|
|
689
|
+
id,
|
|
690
|
+
index
|
|
691
|
+
}, event, true);
|
|
692
|
+
this.requestUpdate();
|
|
693
|
+
},
|
|
694
|
+
all: () => {
|
|
695
|
+
this.#toggleAll();
|
|
696
|
+
this.requestUpdate();
|
|
697
|
+
},
|
|
698
|
+
press: (event) => {
|
|
699
|
+
event.preventDefault();
|
|
700
|
+
event.currentTarget.parentElement?.focus({ preventScroll: true });
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
this.#onMousedown = (event) => {
|
|
704
|
+
if (!event.shiftKey || event.button !== 0 || this.#mode() !== "multi") return;
|
|
705
|
+
const path = event.composedPath();
|
|
706
|
+
if (!this.#selectableRowOf(path)) return;
|
|
707
|
+
event.preventDefault();
|
|
708
|
+
for (const target of path) {
|
|
709
|
+
const cell = gridCell(target);
|
|
710
|
+
if (cell) {
|
|
711
|
+
cell.focus({ preventScroll: true });
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
for (const type of [
|
|
717
|
+
"wheel",
|
|
718
|
+
"touchstart",
|
|
719
|
+
"pointerdown",
|
|
720
|
+
"keydown"
|
|
721
|
+
]) this.addEventListener(type, this.#dropScrollTarget, { passive: true });
|
|
722
|
+
}
|
|
723
|
+
#dropScrollTarget;
|
|
395
724
|
connectedCallback() {
|
|
396
725
|
super.connectedCallback();
|
|
397
726
|
if (!this.hasAttribute("role")) this.setAttribute("role", "grid");
|
|
398
727
|
if (this.hasUpdated) this.#observeProbe();
|
|
728
|
+
this.#setLightConnected(true);
|
|
399
729
|
this.#unwatchMaxHeight = watchMaxElementHeight(() => {
|
|
400
730
|
this.requestUpdate();
|
|
401
731
|
});
|
|
402
732
|
}
|
|
733
|
+
/** Connects or disconnects the cell content (framework content mounts and unmounts with it). */
|
|
734
|
+
#setLightConnected(connected) {
|
|
735
|
+
if (!this.#lightPart || this.#lightConnected === connected) return;
|
|
736
|
+
this.#lightConnected = connected;
|
|
737
|
+
this.#lightPart.setConnected(connected);
|
|
738
|
+
flushContent(this);
|
|
739
|
+
}
|
|
403
740
|
disconnectedCallback() {
|
|
404
741
|
super.disconnectedCallback();
|
|
405
742
|
this.#probeObserver?.disconnect();
|
|
743
|
+
this.#rowMeasure.stop();
|
|
744
|
+
this.#setLightConnected(false);
|
|
406
745
|
this.#unwatchMaxHeight?.();
|
|
407
746
|
this.#unwatchMaxHeight = void 0;
|
|
408
747
|
}
|
|
@@ -419,6 +758,14 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
419
758
|
return this.columns;
|
|
420
759
|
}
|
|
421
760
|
/**
|
|
761
|
+
* `columnGroups` when it has any or `columns` is set (which turns off all
|
|
762
|
+
* children), otherwise the `<argus-column-group>` children.
|
|
763
|
+
*/
|
|
764
|
+
#effectiveGroups() {
|
|
765
|
+
if (this.columnGroups.length > 0 || this.columns.length > 0) return this.columnGroups;
|
|
766
|
+
return this.#children.groups;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
422
769
|
* The node the light-DOM content is rendered before. Lit's part then ends there,
|
|
423
770
|
* so children appended later (an `<argus-column>`) never land inside it. If the
|
|
424
771
|
* marker was removed (say by `innerHTML`), a new one starts a fresh part.
|
|
@@ -444,53 +791,172 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
444
791
|
/**
|
|
445
792
|
* Fits columns to their content: the header, the pinned rows and the body rows
|
|
446
793
|
* rendered now (rows out of view aren't measured). It resizes them like a drag
|
|
447
|
-
* would, so `argus-column-
|
|
794
|
+
* would, so `argus-column-sizing-change` fires for each column that changes, and with a
|
|
448
795
|
* controlled `columnSizing` nothing changes until you set it. Without `ids`, fits
|
|
449
796
|
* every resizable column; columns with `resizable: false` keep their width.
|
|
797
|
+
* With `virtualizeColumns`, columns out of view render for the measurement.
|
|
450
798
|
* Resolves once the table has re-rendered.
|
|
451
799
|
*/
|
|
452
800
|
async autosizeColumns(ids) {
|
|
453
801
|
await this.updateComplete;
|
|
454
802
|
const targets = this.#table.model.columns.filter((c) => c.resizable && (ids === void 0 || ids.includes(c.id))).map((c) => c.id);
|
|
455
|
-
|
|
803
|
+
if (this.virtualizeColumns) {
|
|
804
|
+
this.#measureKeep = targets;
|
|
805
|
+
this.requestUpdate();
|
|
806
|
+
await this.updateComplete;
|
|
807
|
+
}
|
|
808
|
+
const widths = measureColumns(this.renderRoot, targets);
|
|
809
|
+
if (this.#measureKeep.length > 0) {
|
|
810
|
+
this.#measureKeep = [];
|
|
811
|
+
this.requestUpdate();
|
|
812
|
+
}
|
|
813
|
+
this.#requestResizes(widths);
|
|
456
814
|
await this.updateComplete;
|
|
457
815
|
}
|
|
458
816
|
/** Scrolls so that the row at `index` (in the current row order) is at the top. */
|
|
459
817
|
async scrollToRow(index) {
|
|
460
818
|
await this.updateComplete;
|
|
461
819
|
this.#virtual.scrollToIndex(index);
|
|
820
|
+
await this.#settled();
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Waits for the update, and with `rowHeight: 'auto'` for the ones measured
|
|
824
|
+
* rows request (a few at most: heights don't depend on positions).
|
|
825
|
+
*/
|
|
826
|
+
async #settled() {
|
|
827
|
+
let done = await this.updateComplete;
|
|
828
|
+
for (let i = 0; i < 8 && !done && this.rowHeight === "auto"; i++) done = await this.updateComplete;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* The state users change, as one object of plain JSON for saving a user's
|
|
832
|
+
* layout: `sorting`, `columnSizing`, `columnOrder`, `columnPinning`,
|
|
833
|
+
* `columnVisibility`, `columnFilters`, `selection`, `quickFilter`, and `scroll`
|
|
834
|
+
* (the row at the top and the inline offset), with `version: 1`. Controlled properties are
|
|
835
|
+
* included with the values you set. The object is a copy: changing it changes
|
|
836
|
+
* nothing in the table.
|
|
837
|
+
*/
|
|
838
|
+
getState() {
|
|
839
|
+
const scroller = this.#scroller();
|
|
840
|
+
return structuredClone({
|
|
841
|
+
version: 1,
|
|
842
|
+
sorting: this.#sorting.value,
|
|
843
|
+
columnSizing: this.#sizing.value,
|
|
844
|
+
columnOrder: this.#order.value,
|
|
845
|
+
columnPinning: this.#pinning.value,
|
|
846
|
+
columnVisibility: this.#visibility.value,
|
|
847
|
+
columnFilters: this.#filters.value,
|
|
848
|
+
selection: this.#selection.value,
|
|
849
|
+
quickFilter: this.#quickFilter,
|
|
850
|
+
scroll: {
|
|
851
|
+
row: this.#virtual.topRow,
|
|
852
|
+
inline: Math.abs(scroller?.scrollLeft ?? 0)
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Restores state from `getState()`, for example a layout saved in
|
|
858
|
+
* `localStorage`. Only the keys given change; ids of columns that no longer
|
|
859
|
+
* exist are ignored. State of another `version` is ignored as a whole. Properties you control are left alone (set them
|
|
860
|
+
* yourself), and the others stay uncontrolled. No change events fire.
|
|
861
|
+
* `quickFilter` is set like the property: a search box of yours shows it only
|
|
862
|
+
* if you read it back. `scroll` is applied after the rest, so with a
|
|
863
|
+
* `dataSource` the restored rows load where it points. Resolves once the table
|
|
864
|
+
* has re-rendered there.
|
|
865
|
+
*/
|
|
866
|
+
async setState(state) {
|
|
867
|
+
if ((state.version ?? 1) !== 1) return;
|
|
868
|
+
this.#restore("sorting", this.#sorting, state.sorting);
|
|
869
|
+
this.#restore("columnSizing", this.#sizing, state.columnSizing);
|
|
870
|
+
this.#restore("columnOrder", this.#order, state.columnOrder);
|
|
871
|
+
this.#restore("columnPinning", this.#pinning, state.columnPinning);
|
|
872
|
+
this.#restore("columnVisibility", this.#visibility, state.columnVisibility);
|
|
873
|
+
this.#restore("columnFilters", this.#filters, state.columnFilters);
|
|
874
|
+
if (this.#restore("selection", this.#selection, state.selection)) this.#anchor = void 0;
|
|
875
|
+
if (state.quickFilter !== void 0) this.quickFilter = state.quickFilter;
|
|
876
|
+
this.#scrollTarget = void 0;
|
|
462
877
|
await this.updateComplete;
|
|
878
|
+
const { scroll } = state;
|
|
879
|
+
if (!scroll) return;
|
|
880
|
+
this.#virtual.scrollToIndex(scroll.row);
|
|
881
|
+
const count = this.#table.model.rowCount;
|
|
882
|
+
if (this.#data.rows?.totalKnown === false && scroll.row >= count) this.#scrollTarget = {
|
|
883
|
+
row: scroll.row,
|
|
884
|
+
count
|
|
885
|
+
};
|
|
886
|
+
const scroller = this.#scroller();
|
|
887
|
+
if (scroller && scroll.inline >= 0) scroller.scrollLeft = scroll.inline * this.#direction();
|
|
888
|
+
await new Promise(requestAnimationFrame);
|
|
889
|
+
await this.#settled();
|
|
890
|
+
}
|
|
891
|
+
/** Scrolls on toward the row `setState()` restored once more rows are counted. */
|
|
892
|
+
#chaseScrollTarget(count) {
|
|
893
|
+
const target = this.#scrollTarget;
|
|
894
|
+
if (!target || target.count === count) return;
|
|
895
|
+
target.count = count;
|
|
896
|
+
this.#virtual.scrollToIndex(target.row);
|
|
897
|
+
if (target.row < count || this.#data.rows?.totalKnown !== false) this.#scrollTarget = void 0;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Applies a restored value to uncontrolled state without an event, as a user
|
|
901
|
+
* change would without the event. Returns whether it changed.
|
|
902
|
+
*/
|
|
903
|
+
#restore(name, state, value) {
|
|
904
|
+
if (value === void 0 || state.controlled) return false;
|
|
905
|
+
if (JSON.stringify(value) === JSON.stringify(state.value)) return false;
|
|
906
|
+
const old = state.value;
|
|
907
|
+
state.request(structuredClone(value));
|
|
908
|
+
this.requestUpdate(name, old);
|
|
909
|
+
return true;
|
|
463
910
|
}
|
|
464
911
|
willUpdate(changed) {
|
|
465
|
-
|
|
912
|
+
if (changed.has("plugins")) this.#plugins.set(this.plugins);
|
|
913
|
+
this.#plugins.willUpdate(new Set(changed.keys()));
|
|
914
|
+
const columns = this.#plugins.transform(this.#effectiveColumns());
|
|
466
915
|
this.#columnsChanged = columns !== this.#columns;
|
|
467
916
|
this.#columns = columns;
|
|
468
|
-
|
|
917
|
+
const groups = this.#plugins.transformGroups(this.#effectiveGroups());
|
|
918
|
+
this.#groupsChanged = groups !== this.#groups;
|
|
919
|
+
this.#groups = groups;
|
|
920
|
+
const filtersChanged = changed.has("columnFilters") || changed.has("quickFilter");
|
|
921
|
+
if (changed.has("dataSource") || changed.has("sorting") || changed.has("getRowId") || filtersChanged) {
|
|
469
922
|
this.#data.configure({
|
|
470
923
|
source: this.dataSource,
|
|
471
924
|
sorting: this.#sorting.value,
|
|
925
|
+
filters: this.#filters.value,
|
|
926
|
+
quickFilter: this.#quickFilter,
|
|
472
927
|
getRowId: this.getRowId
|
|
473
928
|
});
|
|
474
|
-
if (this.dataSource && this.hasUpdated && changed.has("sorting")) this.#scrollToTop = !changed.has("dataSource");
|
|
929
|
+
if (this.dataSource && this.hasUpdated && (changed.has("sorting") || filtersChanged)) this.#scrollToTop = !changed.has("dataSource");
|
|
930
|
+
this.#scrollTarget = void 0;
|
|
475
931
|
}
|
|
476
|
-
if (
|
|
932
|
+
if (filtersChanged && this.hasUpdated) this.#announceRows = true;
|
|
933
|
+
if (this.#columnsChanged || this.#groupsChanged || changed.has("data") || changed.has("getRowId") || changed.has("sorting") || changed.has("columnSizing") || changed.has("columnOrder") || changed.has("columnPinning") || changed.has("columnVisibility") || changed.has("columnFilters") || changed.has("quickFilter") || changed.has("pinnedRows")) {
|
|
477
934
|
if (this.#columnsChanged) this.#autoWidths.sync(columns.filter((c) => c.width === "auto").map((c) => c.id));
|
|
478
935
|
this.#table.update({
|
|
479
936
|
columns,
|
|
937
|
+
columnGroups: groups,
|
|
480
938
|
data: this.data,
|
|
481
939
|
getRowId: this.getRowId,
|
|
482
940
|
sorting: this.#sorting.value,
|
|
483
941
|
columnSizing: this.#sizing.value,
|
|
484
942
|
columnOrder: this.#order.value,
|
|
943
|
+
columnPinning: this.#pinning.value,
|
|
944
|
+
columnVisibility: this.#visibility.value,
|
|
945
|
+
columnFilters: this.#filters.value,
|
|
946
|
+
quickFilter: this.#quickFilter,
|
|
485
947
|
pinnedRows: this.pinnedRows
|
|
486
948
|
});
|
|
487
949
|
}
|
|
488
950
|
const autoWidths = this.#autoWidths.widths;
|
|
489
951
|
this.#autoWidthsChanged = autoWidths !== this.#pushedAutoWidths;
|
|
490
952
|
this.#pushedAutoWidths = autoWidths;
|
|
953
|
+
const selectionColumn = this.selectionCheckboxes && this.#mode() !== "none";
|
|
954
|
+
this.#selectionColumnChanged = selectionColumn !== this.#selectionColumn;
|
|
955
|
+
this.#selectionColumn = selectionColumn;
|
|
491
956
|
this.#table.update({
|
|
492
957
|
remote: this.#data.rows,
|
|
493
|
-
autoWidths
|
|
958
|
+
autoWidths,
|
|
959
|
+
selectionColumn
|
|
494
960
|
});
|
|
495
961
|
const model = this.#table.model;
|
|
496
962
|
this.#reconcileActive(model);
|
|
@@ -499,11 +965,19 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
499
965
|
const layout = layoutOf(model);
|
|
500
966
|
this.#internals.ariaRowCount = unknownTotal ? "-1" : String(layout.total);
|
|
501
967
|
this.#internals.ariaColCount = String(model.columns.length);
|
|
502
|
-
|
|
968
|
+
this.#internals.ariaMultiSelectable = this.#mode() === "multi" ? "true" : null;
|
|
969
|
+
const extra = this.#headerAndPinnedPx(layout);
|
|
970
|
+
const auto = this.rowHeight === "auto";
|
|
971
|
+
if (!auto) this.#rowMeasure.stop();
|
|
972
|
+
else if (changed.size > 0 || this.#columnsChanged) this.#rowMeasure.invalidate();
|
|
503
973
|
this.#virtual.configure({
|
|
504
974
|
count: model.rowCount,
|
|
505
975
|
rowHeight: this.#rowPx,
|
|
506
|
-
cap:
|
|
976
|
+
cap: (logical) => bodyCap(logical, extra),
|
|
977
|
+
auto: auto ? {
|
|
978
|
+
order: model.rowOrder,
|
|
979
|
+
indexOf: (id) => model.indexOf(id)
|
|
980
|
+
} : void 0,
|
|
507
981
|
overscan: Math.max(0, this.overscan),
|
|
508
982
|
rowIdAt: (index) => this.#rowId(model, index),
|
|
509
983
|
keep: active?.section !== "body" || active.rowId === void 0 ? void 0 : {
|
|
@@ -516,17 +990,31 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
516
990
|
const model = this.#table.model;
|
|
517
991
|
const columns = model.columns;
|
|
518
992
|
this.#window = this.#virtual.computeWindow();
|
|
519
|
-
const
|
|
520
|
-
|
|
993
|
+
const rendered = this.#columnWindow.compute(columns, this.virtualizeColumns, this.virtualizeColumns ? this.#keptColumns(model) : []);
|
|
994
|
+
this.#rendered = rendered;
|
|
995
|
+
const pinStart = pinnedWidth(columns, "start");
|
|
996
|
+
const pinEnd = pinnedWidth(columns, "end");
|
|
997
|
+
const cols = columns.map(track).join(" ");
|
|
521
998
|
const layout = layoutOf(model);
|
|
522
999
|
const style = {
|
|
523
|
-
"--_cols":
|
|
1000
|
+
"--_cols": cols,
|
|
524
1001
|
"--_width": `${String(model.totalWidth)}px`,
|
|
525
|
-
"--_pin-start":
|
|
526
|
-
"--_pin-end":
|
|
1002
|
+
"--_pin-start": `${String(pinStart)}px`,
|
|
1003
|
+
"--_pin-end": `${String(pinEnd)}px`,
|
|
1004
|
+
"--_header-rows": String(layout.header),
|
|
527
1005
|
"--_pin-top-rows": String(layout.top),
|
|
528
1006
|
"--_pin-bottom-rows": String(layout.bottom)
|
|
529
1007
|
};
|
|
1008
|
+
const auto = this.rowHeight === "auto";
|
|
1009
|
+
if (auto) {
|
|
1010
|
+
const key = `${cols}|${String(this.#rowPx)}`;
|
|
1011
|
+
if (key !== this.#rowsKey) {
|
|
1012
|
+
this.#rowsKey = key;
|
|
1013
|
+
this.#rowMeasure.invalidate();
|
|
1014
|
+
}
|
|
1015
|
+
style["--_pin-top-h"] = `${String(this.#pinnedPx("top", layout))}px`;
|
|
1016
|
+
style["--_pin-bottom-h"] = `${String(this.#pinnedPx("bottom", layout))}px`;
|
|
1017
|
+
}
|
|
530
1018
|
const active = this.#active;
|
|
531
1019
|
const activeIn = (section) => active?.section === section ? {
|
|
532
1020
|
index: active.index,
|
|
@@ -536,24 +1024,33 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
536
1024
|
section,
|
|
537
1025
|
index: 0
|
|
538
1026
|
});
|
|
539
|
-
if (this.rowHeight
|
|
1027
|
+
if (typeof this.rowHeight === "number" && this.rowHeight > 0) style["--_row-height"] = `${String(this.rowHeight)}px`;
|
|
1028
|
+
const contentPx = this.#headerAndPinnedPx(layout) + (model.rowCount === 0 ? 0 : this.#window.totalSize);
|
|
540
1029
|
return html`<div
|
|
541
1030
|
part="table"
|
|
542
1031
|
role="presentation"
|
|
543
1032
|
style=${styleMap(style)}
|
|
544
1033
|
?data-dragging=${this.#drag !== void 0}
|
|
1034
|
+
?data-auto-rows=${auto}
|
|
545
1035
|
@keydown=${this.#onKeydown}
|
|
546
1036
|
@focusin=${this.#onFocusin}
|
|
1037
|
+
@mousedown=${this.#onMousedown}
|
|
547
1038
|
@click=${this.#onClick}
|
|
548
1039
|
>
|
|
549
|
-
${renderHeader(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1040
|
+
${renderHeader({
|
|
1041
|
+
columns: rendered,
|
|
1042
|
+
groups: model.headerGroups,
|
|
1043
|
+
handlers: this.#headerHandlers,
|
|
1044
|
+
active: activeIn("header"),
|
|
1045
|
+
drag: this.#drag,
|
|
1046
|
+
checkAll: this.#checkAllOf(model)
|
|
1047
|
+
})}
|
|
1048
|
+
${renderPinnedRows("top", layout.top, rendered, firstIndex("top"), activeIn("top"))}
|
|
1049
|
+
${model.rowCount === 0 ? html`<div part="empty"><slot name="empty">No rows</slot></div>` : renderBody(this.#window.rows, rendered, this.#window.totalSize, activeIn("body"), firstIndex("body"), this.#loading(), this.#selectedAt(model), this.#selectionColumn ? this.#checkboxHandlers : void 0, auto)}
|
|
1050
|
+
${renderPinnedRows("bottom", layout.bottom, rendered, firstIndex("bottom"), activeIn("bottom"))}
|
|
553
1051
|
<div class="probe" aria-hidden="true"></div>
|
|
554
1052
|
</div>
|
|
555
|
-
${pinShadow("start",
|
|
556
|
-
${pinShadow("end", pinnedWidth("end"), layout.total - 1, this.#rowPx)}`;
|
|
1053
|
+
${pinShadow("start", pinStart, contentPx)} ${pinShadow("end", pinEnd, contentPx)}`;
|
|
557
1054
|
}
|
|
558
1055
|
firstUpdated() {
|
|
559
1056
|
this.#observeProbe();
|
|
@@ -561,45 +1058,54 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
561
1058
|
}
|
|
562
1059
|
updated(changed) {
|
|
563
1060
|
const model = this.#table.model;
|
|
564
|
-
renderLightDom(this, model, this.#window.rows, this.#lightDomEnd());
|
|
1061
|
+
const part = renderLightDom(this, model, this.#window.rows, this.#rendered, model.headerGroups, this.#lightDomEnd());
|
|
1062
|
+
if (this.#lightPart && this.#lightPart !== part) this.#lightPart.setConnected(false);
|
|
1063
|
+
this.#lightPart = part;
|
|
1064
|
+
flushContent(this);
|
|
565
1065
|
this.#measureAutoWidths(model);
|
|
1066
|
+
const resized = this.rowHeight === "auto" && this.#measureRows();
|
|
566
1067
|
if (this.#scrollToTop) {
|
|
567
1068
|
this.#scrollToTop = false;
|
|
568
|
-
const scroller = this
|
|
1069
|
+
const scroller = this.#scroller();
|
|
569
1070
|
if (scroller) scroller.scrollTop = 0;
|
|
570
1071
|
}
|
|
571
|
-
|
|
572
|
-
if (this.#
|
|
1072
|
+
this.#chaseScrollTarget(model.rowCount);
|
|
1073
|
+
if (this.#columnsChanged || this.#groupsChanged || changed.has("columnSizing") || changed.has("columnOrder") || changed.has("columnPinning") || changed.has("columnVisibility") || this.#autoWidthsChanged || this.#selectionColumnChanged) this.#pinScroll.measure();
|
|
1074
|
+
if (this.#focusPending && !resized) {
|
|
573
1075
|
this.#focusPending = false;
|
|
574
1076
|
this.#focusActive();
|
|
575
1077
|
}
|
|
576
1078
|
if (changed.has("density") || changed.has("rowHeight")) this.#measureRowHeight();
|
|
577
|
-
|
|
578
|
-
const details = [...this.#appliedResizes.values()];
|
|
579
|
-
this.#appliedResizes.clear();
|
|
580
|
-
for (const detail of details) fire(this, COLUMN_RESIZE, detail);
|
|
581
|
-
}
|
|
1079
|
+
this.#fireApplied(this.#appliedResizes, COLUMN_SIZING_CHANGE);
|
|
582
1080
|
if (this.#announceMove) {
|
|
583
1081
|
this.#announceMoved(model, this.#announceMove);
|
|
584
1082
|
this.#announceMove = void 0;
|
|
585
1083
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1084
|
+
this.#fireApplied(this.#appliedReorders, COLUMN_ORDER_CHANGE);
|
|
1085
|
+
this.#fireApplied(this.#appliedPins, COLUMN_PINNING_CHANGE);
|
|
1086
|
+
this.#fireApplied(this.#appliedHides, COLUMN_VISIBILITY_CHANGE);
|
|
1087
|
+
this.#fireApplied(this.#appliedFilters, COLUMN_FILTERS_CHANGE);
|
|
1088
|
+
if (this.#announceRows) {
|
|
1089
|
+
this.#announceRows = false;
|
|
1090
|
+
this.#announceRowCount(model);
|
|
590
1091
|
}
|
|
591
1092
|
if (this.#appliedSort) {
|
|
592
1093
|
const sorting = this.#appliedSort;
|
|
593
1094
|
this.#appliedSort = void 0;
|
|
594
|
-
fire(this,
|
|
1095
|
+
fire(this, SORTING_CHANGE, { sorting });
|
|
1096
|
+
}
|
|
1097
|
+
if (this.#appliedSelection) {
|
|
1098
|
+
const selection = this.#appliedSelection;
|
|
1099
|
+
this.#appliedSelection = void 0;
|
|
1100
|
+
fire(this, SELECTION_CHANGE, { selection });
|
|
595
1101
|
}
|
|
596
1102
|
const { range } = this.#window;
|
|
597
1103
|
const key = range ? `${String(range.start)}:${String(range.end)}` : "";
|
|
598
1104
|
if (range && key !== this.#lastRange) {
|
|
599
1105
|
this.#lastRange = key;
|
|
600
1106
|
fire(this, RANGE_CHANGE, {
|
|
601
|
-
|
|
602
|
-
|
|
1107
|
+
first: range.start,
|
|
1108
|
+
last: range.end
|
|
603
1109
|
});
|
|
604
1110
|
}
|
|
605
1111
|
if (range) {
|
|
@@ -609,6 +1115,29 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
609
1115
|
for (const detail of this.#data.takeErrors()) fire(this, LOAD_ERROR, detail);
|
|
610
1116
|
}
|
|
611
1117
|
/**
|
|
1118
|
+
* Columns to render wherever they are, with `virtualizeColumns`: the active
|
|
1119
|
+
* cell's (its tab stop and focus survive scrolling sideways, and keyboard
|
|
1120
|
+
* moves land on a rendered cell), one being dragged, auto widths still to
|
|
1121
|
+
* measure, the targets of `autosizeColumns()`, and with a `dataSource` the
|
|
1122
|
+
* first data column, which holds the Retry buttons.
|
|
1123
|
+
*/
|
|
1124
|
+
#keptColumns(model) {
|
|
1125
|
+
return [
|
|
1126
|
+
this.#active?.col,
|
|
1127
|
+
...this.#drag?.key.startsWith("c:") ? this.#drag.columns : [],
|
|
1128
|
+
this.dataSource ? model.columns.find((c) => c.kind === "data")?.id : void 0,
|
|
1129
|
+
...this.#autoWidths.pending(model.columns),
|
|
1130
|
+
...this.#measureKeep
|
|
1131
|
+
];
|
|
1132
|
+
}
|
|
1133
|
+
/** Fires the events of changes the table applied itself, once the update is done. */
|
|
1134
|
+
#fireApplied(applied, name) {
|
|
1135
|
+
if (applied.size === 0) return;
|
|
1136
|
+
const details = [...applied.values()];
|
|
1137
|
+
applied.clear();
|
|
1138
|
+
for (const detail of details) fire(this, name, detail);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
612
1141
|
* Measures the `width: 'auto'` columns once loaded body rows render, or right
|
|
613
1142
|
* away when there are no rows (the header and pinned rows only). While a data
|
|
614
1143
|
* source's first rows load, they keep the default width. A changed width
|
|
@@ -671,14 +1200,393 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
671
1200
|
* cell (an input, a button) are left to that content.
|
|
672
1201
|
*/
|
|
673
1202
|
#onKeydown;
|
|
1203
|
+
/**
|
|
1204
|
+
* The header row to go to from header row `level` down, over a column: the first
|
|
1205
|
+
* group row from `level` with a group over it, else the column headers.
|
|
1206
|
+
*/
|
|
1207
|
+
#headerLevelDown(model, level, columnId) {
|
|
1208
|
+
const leaf = model.headerGroups.length;
|
|
1209
|
+
for (let l = level; l < leaf; l++) if (model.headerCell(l, columnId)?.kind === "group") return l;
|
|
1210
|
+
return leaf;
|
|
1211
|
+
}
|
|
1212
|
+
/** The first group row from `level` up with a group over a column, or `undefined`. */
|
|
1213
|
+
#headerLevelUp(model, level, columnId) {
|
|
1214
|
+
for (let l = Math.min(level, model.headerGroups.length - 1); l >= 0; l--) if (model.headerCell(l, columnId)?.kind === "group") return l;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Keys on a focused group header. Arrows move between group headers in its row
|
|
1218
|
+
* (fillers are skipped) and to the headers above and below it; ArrowDown ends on
|
|
1219
|
+
* the column headers. Home and End go to the row's first and last group,
|
|
1220
|
+
* PageDown into the body, Ctrl+Home and Ctrl+End to the corners, and
|
|
1221
|
+
* Alt+Shift+Arrow moves the group. Groups don't sort or resize.
|
|
1222
|
+
*/
|
|
1223
|
+
#groupKey(event, cell, scroller) {
|
|
1224
|
+
const model = this.#table.model;
|
|
1225
|
+
const layout = layoutOf(model);
|
|
1226
|
+
const at = gridRowOf(cell.parentElement, layout);
|
|
1227
|
+
const row = at?.section === "header" ? model.headerGroups[at.index] : void 0;
|
|
1228
|
+
const here = row?.find((c) => c.key === cell.dataset.groupKey);
|
|
1229
|
+
if (!at || !row || here?.groupId === void 0) return;
|
|
1230
|
+
const level = at.index;
|
|
1231
|
+
const active = this.#active;
|
|
1232
|
+
const anchor = active?.section === "header" && active.index === level && here.columns.includes(active.col) ? active.col : here.columns[0];
|
|
1233
|
+
if (anchor === void 0) return;
|
|
1234
|
+
const { key } = event;
|
|
1235
|
+
const arrow = key === "ArrowLeft" || key === "ArrowRight";
|
|
1236
|
+
const towardEnd = key === "ArrowRight" === (this.#direction() === 1);
|
|
1237
|
+
if (arrow && event.altKey) {
|
|
1238
|
+
event.preventDefault();
|
|
1239
|
+
if (event.shiftKey) this.#moveByKey({
|
|
1240
|
+
kind: "group",
|
|
1241
|
+
id: here.groupId,
|
|
1242
|
+
pin: here.pin
|
|
1243
|
+
}, towardEnd ? 1 : -1);
|
|
1244
|
+
else if (here.resizable) {
|
|
1245
|
+
const starts = this.#groupWidths(here.columns);
|
|
1246
|
+
const total = [...starts.values()].reduce((sum, w) => sum + w, 0);
|
|
1247
|
+
this.#requestGroupResize(here.columns, starts, total + (towardEnd ? RESIZE_STEP : -10));
|
|
1248
|
+
}
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
if (key === "Enter" || key === " ") {
|
|
1252
|
+
event.preventDefault();
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
if (this.#selectionKey(event, cell, anchor, true, scroller)) return;
|
|
1256
|
+
const corner = event.ctrlKey || event.metaKey;
|
|
1257
|
+
if (event.altKey || event.shiftKey || corner && key !== "Home" && key !== "End") return;
|
|
1258
|
+
const groups = row.filter((c) => c.kind === "group");
|
|
1259
|
+
const columns = model.columns;
|
|
1260
|
+
let to;
|
|
1261
|
+
switch (key) {
|
|
1262
|
+
case "ArrowLeft":
|
|
1263
|
+
case "ArrowRight": {
|
|
1264
|
+
const col = groups[groups.indexOf(here) + (towardEnd ? 1 : -1)]?.columns[0];
|
|
1265
|
+
if (col !== void 0) to = {
|
|
1266
|
+
row: at,
|
|
1267
|
+
col
|
|
1268
|
+
};
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1271
|
+
case "Home":
|
|
1272
|
+
case "End":
|
|
1273
|
+
if (corner) {
|
|
1274
|
+
const first = key === "Home";
|
|
1275
|
+
const col = (first ? columns[0] : columns.at(-1))?.id;
|
|
1276
|
+
if (col === void 0) break;
|
|
1277
|
+
if (first) this.#virtual.scrollToEdge("start");
|
|
1278
|
+
else if (layout.bottom > 0) this.#virtual.scrollToEdge("end");
|
|
1279
|
+
const pos = first ? this.#headerLevelDown(model, 0, col) : layout.total - 1;
|
|
1280
|
+
to = {
|
|
1281
|
+
row: layout.at(pos) ?? layout.leafHeader,
|
|
1282
|
+
col
|
|
1283
|
+
};
|
|
1284
|
+
} else {
|
|
1285
|
+
const col = (key === "Home" ? groups[0] : groups.at(-1))?.columns[0];
|
|
1286
|
+
if (col !== void 0) to = {
|
|
1287
|
+
row: at,
|
|
1288
|
+
col
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
break;
|
|
1292
|
+
case "ArrowDown":
|
|
1293
|
+
to = {
|
|
1294
|
+
row: {
|
|
1295
|
+
section: "header",
|
|
1296
|
+
index: this.#headerLevelDown(model, level + 1, anchor)
|
|
1297
|
+
},
|
|
1298
|
+
col: anchor
|
|
1299
|
+
};
|
|
1300
|
+
break;
|
|
1301
|
+
case "ArrowUp": {
|
|
1302
|
+
const up = this.#headerLevelUp(model, level - 1, anchor);
|
|
1303
|
+
if (up !== void 0) to = {
|
|
1304
|
+
row: {
|
|
1305
|
+
section: "header",
|
|
1306
|
+
index: up
|
|
1307
|
+
},
|
|
1308
|
+
col: anchor
|
|
1309
|
+
};
|
|
1310
|
+
break;
|
|
1311
|
+
}
|
|
1312
|
+
case "PageDown": {
|
|
1313
|
+
const pos = this.#verticalStep(key, layout.position(at), layout, scroller);
|
|
1314
|
+
to = {
|
|
1315
|
+
row: layout.at(pos) ?? at,
|
|
1316
|
+
col: anchor
|
|
1317
|
+
};
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
case "PageUp": break;
|
|
1321
|
+
default: return;
|
|
1322
|
+
}
|
|
1323
|
+
event.preventDefault();
|
|
1324
|
+
if (to) this.#moveTo(to.row, to.col, model);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* The grid position ArrowUp/ArrowDown (one row) or PageUp/PageDown (a page of
|
|
1328
|
+
* the body) moves to from `pos`. PageDown from above the body lands a page
|
|
1329
|
+
* into it and stops at its last row; PageUp stays in the body.
|
|
1330
|
+
*/
|
|
1331
|
+
#verticalStep(key, pos, layout, scroller) {
|
|
1332
|
+
const bodyFirst = layout.position({
|
|
1333
|
+
section: "body",
|
|
1334
|
+
index: 0
|
|
1335
|
+
});
|
|
1336
|
+
const bodyLast = bodyFirst + layout.body - 1;
|
|
1337
|
+
const page = this.#pageRows(key, pos, layout, scroller);
|
|
1338
|
+
switch (key) {
|
|
1339
|
+
case "PageDown": return layout.body > 0 && pos <= bodyLast ? Math.min(Math.max(pos, bodyFirst - 1) + page, bodyLast) : pos;
|
|
1340
|
+
case "PageUp": return layout.body > 0 && pos > bodyFirst ? Math.max(Math.min(pos, bodyLast + 1) - page, bodyFirst) : pos;
|
|
1341
|
+
case "ArrowDown": return Math.min(pos + 1, layout.total - 1);
|
|
1342
|
+
case "ArrowUp": return Math.max(pos - 1, 0);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/** How many rows PageUp/PageDown move from grid position `pos`. */
|
|
1346
|
+
#pageRows(key, pos, layout, scroller) {
|
|
1347
|
+
if (this.rowHeight !== "auto") {
|
|
1348
|
+
const fit = Math.floor(scroller.clientHeight / this.#rowPx + .01);
|
|
1349
|
+
return Math.max(1, fit - layout.header - layout.top - layout.bottom);
|
|
1350
|
+
}
|
|
1351
|
+
const view = scroller.clientHeight - this.#headerAndPinnedPx(layout);
|
|
1352
|
+
const bodyFirst = layout.position({
|
|
1353
|
+
section: "body",
|
|
1354
|
+
index: 0
|
|
1355
|
+
});
|
|
1356
|
+
const from = Math.min(Math.max(pos - bodyFirst, 0), Math.max(layout.body - 1, 0));
|
|
1357
|
+
return this.#virtual.pageRows(from, key === "PageDown", view);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Selection keys, with a `selectionMode`: Space (or Shift+Space) toggles the
|
|
1361
|
+
* focused body row; in `multi` mode, Shift+ArrowUp/ArrowDown and
|
|
1362
|
+
* Shift+PageUp/PageDown move focus through the body and select from the
|
|
1363
|
+
* anchor to it, and Ctrl/Cmd+A selects every row. `true` if it took the key.
|
|
1364
|
+
*/
|
|
1365
|
+
#selectionKey(event, cell, col, inHeader, scroller) {
|
|
1366
|
+
const mode = this.#mode();
|
|
1367
|
+
if (mode === "none" || event.altKey) return false;
|
|
1368
|
+
const { key } = event;
|
|
1369
|
+
const command = event.ctrlKey || event.metaKey;
|
|
1370
|
+
const model = this.#table.model;
|
|
1371
|
+
if (command && !event.shiftKey && key.toLowerCase() === "a") {
|
|
1372
|
+
if (mode !== "multi") return false;
|
|
1373
|
+
event.preventDefault();
|
|
1374
|
+
this.#selectAll();
|
|
1375
|
+
return true;
|
|
1376
|
+
}
|
|
1377
|
+
if (command) return false;
|
|
1378
|
+
if (inHeader) {
|
|
1379
|
+
if (key !== " " || col !== "--argus-selection" || mode !== "multi") return false;
|
|
1380
|
+
event.preventDefault();
|
|
1381
|
+
this.#toggleAll();
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
const layout = layoutOf(model);
|
|
1385
|
+
const from = gridRowOf(cell.parentElement, layout);
|
|
1386
|
+
if (from?.section !== "body") return false;
|
|
1387
|
+
if (key === " ") {
|
|
1388
|
+
event.preventDefault();
|
|
1389
|
+
const id = model.rowAt(from.index)?.id;
|
|
1390
|
+
if (id !== void 0) this.#requestSelection(toggleRow(this.#selection.value, mode, id));
|
|
1391
|
+
return true;
|
|
1392
|
+
}
|
|
1393
|
+
if (!event.shiftKey || mode !== "multi" || key !== "ArrowDown" && key !== "ArrowUp" && key !== "PageDown" && key !== "PageUp") return false;
|
|
1394
|
+
event.preventDefault();
|
|
1395
|
+
const bodyFirst = layout.position({
|
|
1396
|
+
section: "body",
|
|
1397
|
+
index: 0
|
|
1398
|
+
});
|
|
1399
|
+
const pos = this.#verticalStep(key, layout.position(from), layout, scroller);
|
|
1400
|
+
const to = layout.at(Math.min(Math.max(pos, bodyFirst), bodyFirst + layout.body - 1));
|
|
1401
|
+
if (to?.section !== "body" || to.index === from.index) return true;
|
|
1402
|
+
if (key.startsWith("Page")) this.#virtual.scrollBy(this.#virtual.startOf(to.index) - this.#virtual.startOf(from.index));
|
|
1403
|
+
const fromId = model.rowAt(from.index)?.id;
|
|
1404
|
+
const anchor = this.#anchorAt(model) ?? (fromId === void 0 ? void 0 : {
|
|
1405
|
+
id: fromId,
|
|
1406
|
+
index: from.index
|
|
1407
|
+
});
|
|
1408
|
+
if (anchor) {
|
|
1409
|
+
const idAt = (index) => model.rowAt(index)?.id;
|
|
1410
|
+
const selection = selectRange([], anchor.index, to.index, idAt, false);
|
|
1411
|
+
this.#requestSelection({
|
|
1412
|
+
selection,
|
|
1413
|
+
anchor: anchor.id
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
this.#moveTo(to, col, model);
|
|
1417
|
+
return true;
|
|
1418
|
+
}
|
|
674
1419
|
/** Focus that lands in a cell (a click, or content inside it) makes that cell active. */
|
|
675
1420
|
#onFocusin;
|
|
676
|
-
/**
|
|
1421
|
+
/** Focus on a group header makes it active, on the column it was active on, else its first. */
|
|
1422
|
+
#focusGroup(cell) {
|
|
1423
|
+
const model = this.#table.model;
|
|
1424
|
+
const row = gridRowOf(cell.parentElement, layoutOf(model));
|
|
1425
|
+
const group = row && model.headerGroups[row.index]?.find((c) => c.key === cell.dataset.groupKey);
|
|
1426
|
+
const first = group?.columns[0];
|
|
1427
|
+
if (!row || !group || first === void 0) return;
|
|
1428
|
+
const prev = this.#active;
|
|
1429
|
+
if (prev?.section === "header" && prev.index === row.index && group.columns.includes(prev.col)) return;
|
|
1430
|
+
this.#active = {
|
|
1431
|
+
...row,
|
|
1432
|
+
rowId: void 0,
|
|
1433
|
+
col: first
|
|
1434
|
+
};
|
|
1435
|
+
this.requestUpdate();
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* `argus-cell-click`, then `argus-row-click`, for clicks in the body; then,
|
|
1439
|
+
* with a `selectionMode`, the click selects its row.
|
|
1440
|
+
*/
|
|
677
1441
|
#onClick;
|
|
1442
|
+
/** Selects for a click on a row, through `clickSelection`. */
|
|
1443
|
+
#clickRow(target, event, toggle) {
|
|
1444
|
+
const model = this.#table.model;
|
|
1445
|
+
this.#requestSelection(clickSelection(this.#selection.value, this.#mode(), target, this.#anchorAt(model), {
|
|
1446
|
+
toggle,
|
|
1447
|
+
range: event.shiftKey
|
|
1448
|
+
}, (index) => model.rowAt(index)?.id));
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* The checkboxes' handlers. The table owns their state: a click asks for a new
|
|
1452
|
+
* selection, and the render that always follows sets every box back to the
|
|
1453
|
+
* selection (`live()`), so a change a controlled host turns down unticks it
|
|
1454
|
+
* again. Not `preventDefault()`: for a real click the browser runs that render
|
|
1455
|
+
* inside the dispatch, then undoes the toggle after it, over the render. A
|
|
1456
|
+
* press leaves focus on the cell, so the grid's keys keep working.
|
|
1457
|
+
*/
|
|
1458
|
+
#checkboxHandlers;
|
|
1459
|
+
/** Selects every selectable row, keeping the others (with a `dataSource`: the loaded ones). */
|
|
1460
|
+
#selectAll() {
|
|
1461
|
+
const ids = this.#table.model.selectableIds();
|
|
1462
|
+
this.#requestSelection({
|
|
1463
|
+
selection: selectAll(this.#selection.value, ids),
|
|
1464
|
+
anchor: this.#anchor
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
/** The select-all checkbox: clears the selection when every row is selected, else selects all. */
|
|
1468
|
+
#toggleAll() {
|
|
1469
|
+
if (this.#checkAllState(this.#table.model) === "all") this.#requestSelection({
|
|
1470
|
+
selection: [],
|
|
1471
|
+
anchor: this.#anchor
|
|
1472
|
+
});
|
|
1473
|
+
else this.#selectAll();
|
|
1474
|
+
}
|
|
1475
|
+
/** The select-all checkbox, in the checkbox column of a `multi` table. */
|
|
1476
|
+
#checkAllOf(model) {
|
|
1477
|
+
if (!this.#selectionColumn || this.#mode() !== "multi") return void 0;
|
|
1478
|
+
return {
|
|
1479
|
+
state: this.#checkAllState(model),
|
|
1480
|
+
handlers: this.#checkboxHandlers
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Whether all, some or none of the selectable rows are selected. With `data`
|
|
1485
|
+
* it's worked out once per selection and rows shown, not on scroll;
|
|
1486
|
+
* with a `dataSource` every time, over the loaded rows, which eviction keeps few.
|
|
1487
|
+
*/
|
|
1488
|
+
#checkAllState(model) {
|
|
1489
|
+
const count = () => {
|
|
1490
|
+
const ids = model.selectableIds();
|
|
1491
|
+
const selected = this.#selectedSet();
|
|
1492
|
+
const n = ids.reduce((sum, id) => selected.has(id) ? sum + 1 : sum, 0);
|
|
1493
|
+
if (n === 0) return "none";
|
|
1494
|
+
return n === ids.length ? "all" : "some";
|
|
1495
|
+
};
|
|
1496
|
+
if (this.#data.rows) return count();
|
|
1497
|
+
const key = [
|
|
1498
|
+
this.#selection.value,
|
|
1499
|
+
this.data,
|
|
1500
|
+
this.getRowId,
|
|
1501
|
+
this.#columns,
|
|
1502
|
+
this.#filters.value,
|
|
1503
|
+
this.#quickFilter,
|
|
1504
|
+
this.#visibility.value
|
|
1505
|
+
];
|
|
1506
|
+
const cached = this.#checkAll;
|
|
1507
|
+
if (cached?.key.length === key.length && key.every((k, i) => k === cached.key[i])) return cached.state;
|
|
1508
|
+
const state = count();
|
|
1509
|
+
this.#checkAll = {
|
|
1510
|
+
key,
|
|
1511
|
+
state
|
|
1512
|
+
};
|
|
1513
|
+
return state;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* A Shift+press on a row in `multi` mode selects a range of rows, not the
|
|
1517
|
+
* page's text from the last click: no text selection, and the cell still
|
|
1518
|
+
* takes focus.
|
|
1519
|
+
*/
|
|
1520
|
+
#onMousedown;
|
|
1521
|
+
/** `selectionMode`, with anything unknown read as `none`. */
|
|
1522
|
+
#mode() {
|
|
1523
|
+
const mode = this.selectionMode;
|
|
1524
|
+
return mode === "single" || mode === "multi" ? mode : "none";
|
|
1525
|
+
}
|
|
1526
|
+
/** Whether each rendered body row is selected; `undefined` without a `selectionMode`. */
|
|
1527
|
+
#selectedAt(model) {
|
|
1528
|
+
if (this.#mode() === "none") return void 0;
|
|
1529
|
+
const ids = this.#selectedSet();
|
|
1530
|
+
return (index) => {
|
|
1531
|
+
const id = model.rowAt(index)?.id;
|
|
1532
|
+
return id === void 0 ? void 0 : ids.has(id);
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
/** `selection` as a set, rebuilt only when `selection` changes. */
|
|
1536
|
+
#selectedSet() {
|
|
1537
|
+
const selection = this.#selection.value;
|
|
1538
|
+
if (this.#selectedIds?.of !== selection) this.#selectedIds = {
|
|
1539
|
+
of: selection,
|
|
1540
|
+
ids: new Set(selection)
|
|
1541
|
+
};
|
|
1542
|
+
return this.#selectedIds.ids;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* The loaded body row a pointer event is on, as a selection target. None for
|
|
1546
|
+
* the header, pinned rows, rows still loading, and presses on interactive
|
|
1547
|
+
* content in a cell (a button, a link, an input).
|
|
1548
|
+
*/
|
|
1549
|
+
#selectableRowOf(path) {
|
|
1550
|
+
const row = path.find((t) => t instanceof HTMLElement && t.getAttribute("role") === "row");
|
|
1551
|
+
if (!row || fromInteractive(path, row)) return void 0;
|
|
1552
|
+
const model = this.#table.model;
|
|
1553
|
+
const at = gridRowOf(row, layoutOf(model));
|
|
1554
|
+
if (at?.section !== "body") return void 0;
|
|
1555
|
+
const id = model.rowAt(at.index)?.id;
|
|
1556
|
+
return id === void 0 ? void 0 : {
|
|
1557
|
+
id,
|
|
1558
|
+
index: at.index
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
/** The anchor row and where it is now; `undefined` if it isn't in the rows (or not loaded). */
|
|
1562
|
+
#anchorAt(model) {
|
|
1563
|
+
const id = this.#anchor;
|
|
1564
|
+
if (id === void 0) return void 0;
|
|
1565
|
+
const index = model.indexOf(id);
|
|
1566
|
+
return index === void 0 ? void 0 : {
|
|
1567
|
+
id,
|
|
1568
|
+
index
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Changes the selection through the controlled/uncontrolled rules, and moves
|
|
1573
|
+
* the anchor. A step that leaves the same rows selected fires nothing.
|
|
1574
|
+
*/
|
|
1575
|
+
#requestSelection(step) {
|
|
1576
|
+
if (!step) return;
|
|
1577
|
+
this.#anchor = step.anchor;
|
|
1578
|
+
const old = this.#selection.value;
|
|
1579
|
+
if (sameSelection(step.selection, old)) return;
|
|
1580
|
+
const { value, applied } = this.#selection.request(step.selection);
|
|
1581
|
+
if (applied) {
|
|
1582
|
+
this.#appliedSelection = value;
|
|
1583
|
+
this.requestUpdate("selection", old);
|
|
1584
|
+
} else fire(this, SELECTION_CHANGE, { selection: value });
|
|
1585
|
+
}
|
|
678
1586
|
/**
|
|
679
1587
|
* Fires `argus-cell-click` (when a cell is on the path), then `argus-row-click`,
|
|
680
1588
|
* for the body or pinned row on an event path. Clicks and Enter on a cell use
|
|
681
|
-
* it. Rows that haven't loaded and the
|
|
1589
|
+
* it. Rows that haven't loaded, the header and the checkbox column fire nothing.
|
|
682
1590
|
*/
|
|
683
1591
|
#activate(path) {
|
|
684
1592
|
let columnId;
|
|
@@ -686,6 +1594,7 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
686
1594
|
if (!(target instanceof HTMLElement)) continue;
|
|
687
1595
|
const role = target.getAttribute("role");
|
|
688
1596
|
if (role === "gridcell") columnId = target.dataset.col;
|
|
1597
|
+
if (columnId === "--argus-selection") return;
|
|
689
1598
|
if (role !== "row") continue;
|
|
690
1599
|
const model = this.#table.model;
|
|
691
1600
|
const at = gridRowOf(target, layoutOf(model));
|
|
@@ -696,6 +1605,8 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
696
1605
|
if (!row) return;
|
|
697
1606
|
if (columnId !== void 0) fire(this, CELL_CLICK, {
|
|
698
1607
|
row: row.original,
|
|
1608
|
+
rowId: row.id,
|
|
1609
|
+
index,
|
|
699
1610
|
columnId,
|
|
700
1611
|
value: model.valueAt(index, columnId)
|
|
701
1612
|
});
|
|
@@ -711,6 +1622,8 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
711
1622
|
if (!row) return;
|
|
712
1623
|
if (columnId !== void 0) fire(this, CELL_CLICK, {
|
|
713
1624
|
row: row.original,
|
|
1625
|
+
rowId: row.id,
|
|
1626
|
+
index,
|
|
714
1627
|
columnId,
|
|
715
1628
|
value: model.pinnedValueAt(pinned, index, columnId),
|
|
716
1629
|
pinned
|
|
@@ -746,14 +1659,28 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
746
1659
|
const active = this.#active;
|
|
747
1660
|
if (!active) return;
|
|
748
1661
|
const col = CSS.escape(active.col);
|
|
749
|
-
const
|
|
750
|
-
const
|
|
1662
|
+
const model = this.#table.model;
|
|
1663
|
+
const layout = layoutOf(model);
|
|
1664
|
+
const index = layout.ariaRowIndex(active);
|
|
1665
|
+
const row = `[role='row'][aria-rowindex="${String(index)}"]`;
|
|
1666
|
+
if (active.section === "header" && active.index < layout.header - 1) {
|
|
1667
|
+
const key = model.headerCell(active.index, active.col)?.key ?? "";
|
|
1668
|
+
const group = this.renderRoot.querySelector(`${row} > [data-group-key="${CSS.escape(key)}"]`);
|
|
1669
|
+
if (!group) return;
|
|
1670
|
+
group.focus({ preventScroll: true });
|
|
1671
|
+
const leaf = `[role='row'][aria-rowindex="${String(layout.header)}"] > [data-col="${col}"]`;
|
|
1672
|
+
this.renderRoot.querySelector(leaf)?.scrollIntoView({
|
|
1673
|
+
block: "nearest",
|
|
1674
|
+
inline: "nearest"
|
|
1675
|
+
});
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const cell = this.renderRoot.querySelector(`${row} > [data-col="${col}"]`);
|
|
751
1679
|
if (!cell) return;
|
|
752
1680
|
cell.focus({ preventScroll: true });
|
|
753
|
-
if (active.section === "body" && this.#virtual.scaled) {
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
if (this.#virtual.reveal(active.index, before, layout.bottom * this.#rowPx)) {
|
|
1681
|
+
if (active.section === "body" && (this.#virtual.scaled || this.rowHeight === "auto")) {
|
|
1682
|
+
const before = layout.header * this.#rowPx + this.#pinnedPx("top", layout);
|
|
1683
|
+
if (this.#virtual.reveal(active.index, before, this.#pinnedPx("bottom", layout))) {
|
|
757
1684
|
this.#focusPending = true;
|
|
758
1685
|
return;
|
|
759
1686
|
}
|
|
@@ -778,14 +1705,24 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
778
1705
|
this.#active = void 0;
|
|
779
1706
|
return;
|
|
780
1707
|
}
|
|
1708
|
+
const layout = layoutOf(model);
|
|
1709
|
+
const leaf = layout.header - 1;
|
|
781
1710
|
const prev = this.#active ?? {
|
|
782
|
-
...
|
|
1711
|
+
...layout.leafHeader,
|
|
783
1712
|
rowId: void 0,
|
|
784
1713
|
col: first.id
|
|
785
1714
|
};
|
|
786
|
-
const col = columns.some((c) => c.id === prev.col) ? prev.col : first.id;
|
|
1715
|
+
const col = columns.some((c) => c.id === prev.col) ? prev.col : this.#columnInPlaceOf(model, prev.col, first.id);
|
|
787
1716
|
let { section, index, rowId } = prev;
|
|
788
|
-
let moved =
|
|
1717
|
+
let moved = col !== prev.col && this.#active !== void 0;
|
|
1718
|
+
if (section === "header") {
|
|
1719
|
+
if (!this.#active || index === this.#leafLevel) index = leaf;
|
|
1720
|
+
else if (index >= leaf || model.headerCell(index, col)?.kind !== "group") {
|
|
1721
|
+
index = leaf;
|
|
1722
|
+
moved = true;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
this.#leafLevel = leaf;
|
|
789
1726
|
if (section === "body" && rowId !== void 0) {
|
|
790
1727
|
const found = model.indexOf(rowId);
|
|
791
1728
|
const unloaded = index < model.rowCount && model.rowAt(index) === void 0;
|
|
@@ -804,7 +1741,7 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
804
1741
|
rowId = index >= 0 ? model.pinnedRowAt(section, index)?.id : void 0;
|
|
805
1742
|
}
|
|
806
1743
|
if (index < 0) {
|
|
807
|
-
({section, index} =
|
|
1744
|
+
({section, index} = layout.leafHeader);
|
|
808
1745
|
rowId = void 0;
|
|
809
1746
|
}
|
|
810
1747
|
if (moved && this.matches(":focus-within")) this.#focusPending = true;
|
|
@@ -816,6 +1753,18 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
816
1753
|
};
|
|
817
1754
|
}
|
|
818
1755
|
/**
|
|
1756
|
+
* The shown column that takes a hidden column's place: the next one in the
|
|
1757
|
+
* column order, else the one before it. `fallback` for a column that is gone
|
|
1758
|
+
* altogether.
|
|
1759
|
+
*/
|
|
1760
|
+
#columnInPlaceOf(model, id, fallback) {
|
|
1761
|
+
const all = model.allColumns;
|
|
1762
|
+
const at = all.findIndex((c) => c.id === id);
|
|
1763
|
+
if (at < 0) return fallback;
|
|
1764
|
+
const shown = (c) => !c.hidden;
|
|
1765
|
+
return (all.slice(at + 1).find(shown) ?? all.slice(0, at).findLast(shown))?.id ?? fallback;
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
819
1768
|
* The width a resize starts from: the latest requested width (key presses can
|
|
820
1769
|
* come faster than renders), else the model's fixed width, else the rendered
|
|
821
1770
|
* width of a flex column.
|
|
@@ -826,6 +1775,49 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
826
1775
|
const view = this.#table.model.columns.find((c) => c.id === columnId);
|
|
827
1776
|
return view && view.flex === void 0 ? view.width : cell.getBoundingClientRect().width;
|
|
828
1777
|
}
|
|
1778
|
+
/** The group header cell with a key, from the model. */
|
|
1779
|
+
#groupCellOf(key) {
|
|
1780
|
+
if (key === void 0) return void 0;
|
|
1781
|
+
return this.#table.model.headerGroups.flat().find((c) => c.key === key);
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* The widths a group resize starts from, by column: the latest requested ones
|
|
1785
|
+
* (key presses can come faster than renders), else the model's, else the
|
|
1786
|
+
* rendered width of a flex column.
|
|
1787
|
+
*/
|
|
1788
|
+
#groupWidths(columnIds) {
|
|
1789
|
+
const widths = /* @__PURE__ */ new Map();
|
|
1790
|
+
for (const id of columnIds) {
|
|
1791
|
+
const header = this.renderRoot.querySelector(`[part~='header-cell'][data-col="${CSS.escape(id)}"]`);
|
|
1792
|
+
const view = this.#table.model.columns.find((c) => c.id === id);
|
|
1793
|
+
const sized = this.#sizing.value[id];
|
|
1794
|
+
widths.set(id, sized ?? (header ? this.#currentWidth(id, header) : view?.width ?? 0));
|
|
1795
|
+
}
|
|
1796
|
+
return widths;
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Resizes a group to `total` px: the change is spread over its resizable columns
|
|
1800
|
+
* in proportion to their widths, within their bounds (one `argus-column-sizing-change`
|
|
1801
|
+
* per column that changes). The other columns keep their width.
|
|
1802
|
+
*/
|
|
1803
|
+
#requestGroupResize(columnIds, starts, total) {
|
|
1804
|
+
const views = new Map(this.#table.model.columns.map((c) => [c.id, c]));
|
|
1805
|
+
let fixed = 0;
|
|
1806
|
+
const spread = [];
|
|
1807
|
+
for (const id of columnIds) {
|
|
1808
|
+
const view = views.get(id);
|
|
1809
|
+
const width = starts.get(id) ?? view?.width ?? 0;
|
|
1810
|
+
if (view?.resizable) spread.push({
|
|
1811
|
+
id,
|
|
1812
|
+
width,
|
|
1813
|
+
minWidth: view.minWidth,
|
|
1814
|
+
maxWidth: view.maxWidth
|
|
1815
|
+
});
|
|
1816
|
+
else fixed += width;
|
|
1817
|
+
}
|
|
1818
|
+
if (spread.length === 0) return;
|
|
1819
|
+
this.#requestResizes(spreadWidth(spread, total - fixed));
|
|
1820
|
+
}
|
|
829
1821
|
#direction() {
|
|
830
1822
|
return getComputedStyle(this).direction === "rtl" ? -1 : 1;
|
|
831
1823
|
}
|
|
@@ -835,8 +1827,8 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
835
1827
|
}
|
|
836
1828
|
/**
|
|
837
1829
|
* Sets column widths (clamped by core) in one change, through the
|
|
838
|
-
* controlled/uncontrolled rules. One `argus-column-
|
|
839
|
-
* changes, each with the whole new sizing.
|
|
1830
|
+
* controlled/uncontrolled rules. One `argus-column-sizing-change` per column
|
|
1831
|
+
* that changes, each with the whole new sizing.
|
|
840
1832
|
*/
|
|
841
1833
|
#requestResizes(widths) {
|
|
842
1834
|
const model = this.#table.model;
|
|
@@ -852,90 +1844,210 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
852
1844
|
...Object.fromEntries(targets)
|
|
853
1845
|
});
|
|
854
1846
|
const details = targets.map(([columnId, width]) => ({
|
|
1847
|
+
columnSizing: value,
|
|
855
1848
|
columnId,
|
|
856
|
-
width
|
|
857
|
-
sizing: value
|
|
1849
|
+
width
|
|
858
1850
|
}));
|
|
859
1851
|
if (applied) {
|
|
860
|
-
for (const detail of this.#appliedResizes.values()) detail.
|
|
1852
|
+
for (const detail of this.#appliedResizes.values()) detail.columnSizing = value;
|
|
861
1853
|
for (const detail of details) this.#appliedResizes.set(detail.columnId, detail);
|
|
862
1854
|
this.requestUpdate("columnSizing", old);
|
|
863
|
-
} else for (const detail of details) fire(this,
|
|
1855
|
+
} else for (const detail of details) fire(this, COLUMN_SIZING_CHANGE, detail);
|
|
864
1856
|
}
|
|
865
1857
|
/**
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* order, so moves can come faster than renders. `false` if it can't move there.
|
|
1858
|
+
* Pins or unpins a column through the controlled/uncontrolled rules. Starts
|
|
1859
|
+
* from the latest requested pins, so requests can come faster than renders.
|
|
869
1860
|
*/
|
|
870
|
-
#
|
|
871
|
-
const old = this.#
|
|
872
|
-
const next = this.#table.model.
|
|
873
|
-
if (!next) return
|
|
874
|
-
const { value, applied } = this.#
|
|
1861
|
+
#requestPin(columnId, pin) {
|
|
1862
|
+
const old = this.#pinning.value;
|
|
1863
|
+
const next = this.#table.model.nextPinning(columnId, pin, old);
|
|
1864
|
+
if (!next) return;
|
|
1865
|
+
const { value, applied } = this.#pinning.request(next);
|
|
875
1866
|
if (applied) {
|
|
876
|
-
for (const detail of this.#
|
|
877
|
-
this.#
|
|
1867
|
+
for (const detail of this.#appliedPins.values()) detail.columnPinning = value;
|
|
1868
|
+
this.#appliedPins.set(columnId, {
|
|
1869
|
+
columnPinning: value,
|
|
878
1870
|
columnId,
|
|
879
|
-
|
|
1871
|
+
pin
|
|
880
1872
|
});
|
|
881
|
-
this.requestUpdate("
|
|
882
|
-
} else fire(this,
|
|
1873
|
+
this.requestUpdate("columnPinning", old);
|
|
1874
|
+
} else fire(this, COLUMN_PINNING_CHANGE, {
|
|
1875
|
+
columnPinning: value,
|
|
1876
|
+
columnId,
|
|
1877
|
+
pin
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
/** Hides or shows a column through the controlled/uncontrolled rules. */
|
|
1881
|
+
#requestHide(columnId, hidden) {
|
|
1882
|
+
const old = this.#visibility.value;
|
|
1883
|
+
const next = this.#table.model.nextVisibility(columnId, hidden, old);
|
|
1884
|
+
if (!next) return;
|
|
1885
|
+
const { value, applied } = this.#visibility.request(next);
|
|
1886
|
+
if (applied) {
|
|
1887
|
+
for (const detail of this.#appliedHides.values()) detail.columnVisibility = value;
|
|
1888
|
+
this.#appliedHides.set(columnId, {
|
|
1889
|
+
columnVisibility: value,
|
|
1890
|
+
columnId,
|
|
1891
|
+
visible: !hidden
|
|
1892
|
+
});
|
|
1893
|
+
this.requestUpdate("columnVisibility", old);
|
|
1894
|
+
} else fire(this, COLUMN_VISIBILITY_CHANGE, {
|
|
1895
|
+
columnVisibility: value,
|
|
883
1896
|
columnId,
|
|
884
|
-
|
|
1897
|
+
visible: !hidden
|
|
885
1898
|
});
|
|
1899
|
+
}
|
|
1900
|
+
/** Sets or clears a column's filter through the controlled/uncontrolled rules. */
|
|
1901
|
+
#requestFilter(columnId, filter) {
|
|
1902
|
+
const old = this.#filters.value;
|
|
1903
|
+
const next = this.#table.model.nextFilters(columnId, filter, old);
|
|
1904
|
+
if (!next) return;
|
|
1905
|
+
const { value, applied } = this.#filters.request(next);
|
|
1906
|
+
if (applied) {
|
|
1907
|
+
for (const detail of this.#appliedFilters.values()) detail.columnFilters = value;
|
|
1908
|
+
this.#appliedFilters.set(columnId, {
|
|
1909
|
+
columnFilters: value,
|
|
1910
|
+
columnId,
|
|
1911
|
+
columnFilter: filter
|
|
1912
|
+
});
|
|
1913
|
+
this.requestUpdate("columnFilters", old);
|
|
1914
|
+
} else fire(this, COLUMN_FILTERS_CHANGE, {
|
|
1915
|
+
columnFilters: value,
|
|
1916
|
+
columnId,
|
|
1917
|
+
columnFilter: filter
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Says how many rows the filters leave ("42 rows"). With a `dataSource` the count
|
|
1922
|
+
* isn't known until the first page answers, so nothing is said.
|
|
1923
|
+
*/
|
|
1924
|
+
#announceRowCount(model) {
|
|
1925
|
+
if (this.#data.rows) return;
|
|
1926
|
+
const n = model.rowCount;
|
|
1927
|
+
announce(this.ownerDocument, n === 1 ? "1 row" : `${String(n)} rows`);
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Moves a column or group to position `to` among its siblings (the columns and
|
|
1931
|
+
* groups of its parent group on its pin side), through the controlled/uncontrolled
|
|
1932
|
+
* rules. Starts from the latest requested order, so moves can come faster than
|
|
1933
|
+
* renders. `false` if it can't move there.
|
|
1934
|
+
*/
|
|
1935
|
+
#requestReorder(unit, to) {
|
|
1936
|
+
const old = this.#order.value;
|
|
1937
|
+
const model = this.#table.model;
|
|
1938
|
+
const moving = model.siblingUnits(unit, old).find((s) => sameUnit(s.unit, unit));
|
|
1939
|
+
const next = model.nextMove(unit, to, old);
|
|
1940
|
+
const columnId = moving?.columns[0];
|
|
1941
|
+
if (!next || columnId === void 0) return false;
|
|
1942
|
+
const detail = (columnOrder) => unit.kind === "group" ? {
|
|
1943
|
+
columnOrder,
|
|
1944
|
+
columnId,
|
|
1945
|
+
groupId: unit.id
|
|
1946
|
+
} : {
|
|
1947
|
+
columnOrder,
|
|
1948
|
+
columnId
|
|
1949
|
+
};
|
|
1950
|
+
const { value, applied } = this.#order.request(next);
|
|
1951
|
+
if (applied) {
|
|
1952
|
+
for (const pending of this.#appliedReorders.values()) pending.columnOrder = value;
|
|
1953
|
+
this.#appliedReorders.set(unitKey(unit), detail(value));
|
|
1954
|
+
this.requestUpdate("columnOrder", old);
|
|
1955
|
+
} else fire(this, COLUMN_ORDER_CHANGE, detail(value));
|
|
886
1956
|
return true;
|
|
887
1957
|
}
|
|
888
|
-
/** Alt+Shift+Arrow on a header: one step toward the start (-1) or the end (1). */
|
|
889
|
-
#moveByKey(
|
|
1958
|
+
/** Alt+Shift+Arrow on a column or group header: one step toward the start (-1) or the end (1). */
|
|
1959
|
+
#moveByKey(unit, step) {
|
|
890
1960
|
const model = this.#table.model;
|
|
891
|
-
const from = model.
|
|
892
|
-
const
|
|
893
|
-
if (from < 0 ||
|
|
894
|
-
if (!this.#requestReorder(
|
|
1961
|
+
const from = model.siblingUnits(unit, this.#order.value).findIndex((s) => sameUnit(s.unit, unit));
|
|
1962
|
+
const at = this.#colIndexOf(model, unit);
|
|
1963
|
+
if (from < 0 || at === void 0) return;
|
|
1964
|
+
if (!this.#requestReorder(unit, from + step)) return;
|
|
895
1965
|
this.#focusPending = true;
|
|
896
1966
|
this.#announceMove ??= {
|
|
897
|
-
|
|
898
|
-
from:
|
|
1967
|
+
unit,
|
|
1968
|
+
from: at
|
|
899
1969
|
};
|
|
900
1970
|
this.requestUpdate();
|
|
901
1971
|
}
|
|
902
|
-
/**
|
|
1972
|
+
/** `aria-colindex` of a column's header, or of a group's first column on its side. */
|
|
1973
|
+
#colIndexOf(model, unit) {
|
|
1974
|
+
if (unit.kind === "column") return model.columns.find((c) => c.id === unit.id)?.colIndex;
|
|
1975
|
+
const key = unitKey(unit);
|
|
1976
|
+
return model.headerGroups.flat().find((c) => c.key === key)?.colIndex;
|
|
1977
|
+
}
|
|
1978
|
+
/** Tells screen readers where a column or group moved from the keyboard went. */
|
|
903
1979
|
#announceMoved(model, move) {
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1980
|
+
const { unit } = move;
|
|
1981
|
+
const total = String(model.columns.length);
|
|
1982
|
+
if (unit.kind === "column") {
|
|
1983
|
+
const view = model.columns.find((c) => c.id === unit.id);
|
|
1984
|
+
if (!view || view.colIndex === move.from) return;
|
|
1985
|
+
const name = this.#slotText(`[role='columnheader'][data-col="${CSS.escape(unit.id)}"] > slot`);
|
|
1986
|
+
announce(this.ownerDocument, `${name || unit.id} moved to column ${String(view.colIndex)} of ${total}`);
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const key = unitKey(unit);
|
|
1990
|
+
const cell = model.headerGroups.flat().find((c) => c.key === key);
|
|
1991
|
+
if (!cell || cell.colIndex === move.from) return;
|
|
1992
|
+
const name = this.#slotText(`[data-group-key="${CSS.escape(key)}"] slot`);
|
|
1993
|
+
const last = cell.colIndex + cell.span - 1;
|
|
1994
|
+
const where = cell.span === 1 ? `column ${String(cell.colIndex)}` : `columns ${String(cell.colIndex)} to ${String(last)}`;
|
|
1995
|
+
announce(this.ownerDocument, `${name || unit.id} moved to ${where} of ${total}`);
|
|
909
1996
|
}
|
|
910
|
-
/**
|
|
1997
|
+
/** The text slotted into a header, read through the slot's assigned nodes. */
|
|
1998
|
+
#slotText(selector) {
|
|
1999
|
+
const slot = this.renderRoot.querySelector(selector);
|
|
2000
|
+
return slot instanceof HTMLSlotElement ? slot.assignedNodes({ flatten: true }).map((n) => n.textContent).join("").trim() : "";
|
|
2001
|
+
}
|
|
2002
|
+
/** A press on a reorderable column or group header: becomes a drag once the pointer moves. */
|
|
911
2003
|
#startReorder(down) {
|
|
912
2004
|
const cell = down.composedPath().find((t) => t instanceof HTMLElement && t.getAttribute("role") === "columnheader");
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if (
|
|
917
|
-
|
|
2005
|
+
if (!cell || !canStartReorder(down, cell)) return;
|
|
2006
|
+
const model = this.#table.model;
|
|
2007
|
+
let unit;
|
|
2008
|
+
if (cell.dataset.moveCol !== void 0) unit = {
|
|
2009
|
+
kind: "column",
|
|
2010
|
+
id: cell.dataset.moveCol
|
|
2011
|
+
};
|
|
2012
|
+
else {
|
|
2013
|
+
const key = cell.dataset.moveGroup;
|
|
2014
|
+
const group = model.headerGroups.flat().find((c) => c.key === key);
|
|
2015
|
+
if (group?.groupId === void 0) return;
|
|
2016
|
+
unit = {
|
|
2017
|
+
kind: "group",
|
|
2018
|
+
id: group.groupId,
|
|
2019
|
+
pin: group.pin
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
const scroller = this.#scroller();
|
|
2023
|
+
const moving = model.siblingUnits(unit).find((s) => sameUnit(s.unit, unit));
|
|
2024
|
+
if (!scroller || !moving) return;
|
|
2025
|
+
const key = unitKey(unit);
|
|
2026
|
+
const columns = new Set(moving.columns);
|
|
2027
|
+
const pin = unit.kind === "group" ? unit.pin : model.columns.find((c) => c.id === unit.id)?.pin;
|
|
918
2028
|
startReorderDrag(cell, down, {
|
|
919
2029
|
scroller,
|
|
920
|
-
targetAt: (x) => dropTarget(this.#dropSlots(
|
|
2030
|
+
targetAt: (x) => dropTarget(this.#dropSlots(unit), key, x, this.#direction()),
|
|
921
2031
|
scrollArea: () => pin === void 0 ? this.#scrollArea(scroller) : void 0,
|
|
922
2032
|
onStart: () => {
|
|
923
2033
|
this.#drag = {
|
|
924
|
-
|
|
2034
|
+
key,
|
|
2035
|
+
columns,
|
|
925
2036
|
target: void 0
|
|
926
2037
|
};
|
|
927
2038
|
this.requestUpdate();
|
|
928
2039
|
},
|
|
929
2040
|
onTarget: (target) => {
|
|
930
2041
|
this.#drag = {
|
|
931
|
-
|
|
2042
|
+
key,
|
|
2043
|
+
columns,
|
|
932
2044
|
target
|
|
933
2045
|
};
|
|
934
2046
|
this.requestUpdate();
|
|
935
2047
|
},
|
|
936
2048
|
onDrop: (target) => {
|
|
937
2049
|
this.#drag = void 0;
|
|
938
|
-
if (target && this.#requestReorder(
|
|
2050
|
+
if (target && this.#requestReorder(unit, target.to)) {
|
|
939
2051
|
if (this.matches(":focus-within")) this.#focusPending = true;
|
|
940
2052
|
}
|
|
941
2053
|
this.requestUpdate();
|
|
@@ -946,14 +2058,18 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
946
2058
|
}
|
|
947
2059
|
});
|
|
948
2060
|
}
|
|
949
|
-
/**
|
|
950
|
-
|
|
2061
|
+
/**
|
|
2062
|
+
* The rendered headers of what a column or group moves among (see
|
|
2063
|
+
* `siblingUnits`), with their positions there. With `virtualizeColumns`, columns
|
|
2064
|
+
* out of view have no header and no slot.
|
|
2065
|
+
*/
|
|
2066
|
+
#dropSlots(unit) {
|
|
951
2067
|
const slots = [];
|
|
952
|
-
this.#table.model.
|
|
953
|
-
|
|
954
|
-
const cell = this.renderRoot.querySelector(`[role='columnheader'][data-col="${CSS.escape(
|
|
2068
|
+
this.#table.model.siblingUnits(unit).forEach((sibling, index) => {
|
|
2069
|
+
const id = unitKey(sibling.unit);
|
|
2070
|
+
const cell = this.renderRoot.querySelector(sibling.unit.kind === "column" ? `[role='columnheader'][data-col="${CSS.escape(sibling.unit.id)}"]` : `[data-group-key="${CSS.escape(id)}"]`);
|
|
955
2071
|
if (cell) slots.push({
|
|
956
|
-
id
|
|
2072
|
+
id,
|
|
957
2073
|
index,
|
|
958
2074
|
rect: cell.getBoundingClientRect()
|
|
959
2075
|
});
|
|
@@ -962,8 +2078,7 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
962
2078
|
}
|
|
963
2079
|
/** Where the scrolling columns show: the scroller's inside, less the pinned columns. */
|
|
964
2080
|
#scrollArea(scroller) {
|
|
965
|
-
const
|
|
966
|
-
const width = (side) => columns.reduce((sum, c) => c.pin === side ? sum + c.width : sum, 0);
|
|
2081
|
+
const width = (side) => pinnedWidth(this.#table.model.columns, side);
|
|
967
2082
|
const ltr = this.#direction() === 1;
|
|
968
2083
|
const left = scroller.getBoundingClientRect().left + scroller.clientLeft;
|
|
969
2084
|
return {
|
|
@@ -974,13 +2089,73 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
974
2089
|
/** asc → desc → unsorted, through the controlled/uncontrolled rules. */
|
|
975
2090
|
#requestSort(columnId) {
|
|
976
2091
|
const next = this.#table.model.nextSorting(columnId);
|
|
977
|
-
if (
|
|
2092
|
+
if (next) this.#applySorting(next);
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* A plugin's sort: by a column in a direction, or (`null`) cleared if it is by
|
|
2096
|
+
* that column. Only sortable columns, and only a change.
|
|
2097
|
+
*/
|
|
2098
|
+
#setSort(columnId, direction) {
|
|
2099
|
+
const view = this.#table.model.columns.find((c) => c.id === columnId);
|
|
2100
|
+
if (view?.kind !== "data" || !view.sortable) return;
|
|
2101
|
+
if (direction === (view.sorted === false ? void 0 : view.sorted)) return;
|
|
2102
|
+
if (direction === void 0) this.#applySorting([]);
|
|
2103
|
+
else this.#applySorting([{
|
|
2104
|
+
id: columnId,
|
|
2105
|
+
desc: direction === "desc"
|
|
2106
|
+
}]);
|
|
2107
|
+
}
|
|
2108
|
+
/** A new sort order, through the controlled/uncontrolled rules. */
|
|
2109
|
+
#applySorting(next) {
|
|
978
2110
|
const old = this.#sorting.value;
|
|
979
2111
|
const { value, applied } = this.#sorting.request(next);
|
|
980
2112
|
if (applied) {
|
|
981
2113
|
this.#appliedSort = value;
|
|
982
2114
|
this.requestUpdate("sorting", old);
|
|
983
|
-
} else fire(this,
|
|
2115
|
+
} else fire(this, SORTING_CHANGE, { sorting: value });
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* A data column as plugins see it. Header functions call this on every
|
|
2119
|
+
* render, so the views are indexed by id once per new column list.
|
|
2120
|
+
*/
|
|
2121
|
+
#columnInfo(id) {
|
|
2122
|
+
const columns = this.#table.model.allColumns;
|
|
2123
|
+
if (this.#columnsById?.of !== columns) this.#columnsById = {
|
|
2124
|
+
of: columns,
|
|
2125
|
+
byId: new Map(columns.map((c) => [c.id, c]))
|
|
2126
|
+
};
|
|
2127
|
+
const view = this.#columnsById.byId.get(id);
|
|
2128
|
+
if (view?.kind !== "data") return void 0;
|
|
2129
|
+
return {
|
|
2130
|
+
id,
|
|
2131
|
+
sortable: view.sortable,
|
|
2132
|
+
resizable: view.resizable,
|
|
2133
|
+
pinnable: view.pinnable,
|
|
2134
|
+
hideable: view.hideable,
|
|
2135
|
+
pin: view.pin,
|
|
2136
|
+
hidden: view.hidden,
|
|
2137
|
+
width: view.width,
|
|
2138
|
+
sorted: view.sorted === false ? void 0 : view.sorted,
|
|
2139
|
+
filter: view.filter,
|
|
2140
|
+
columnFilter: this.#filters.value[id]
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
/** The header or cell an event happened in, for plugins. */
|
|
2144
|
+
#cellTarget(event) {
|
|
2145
|
+
for (const target of event.composedPath()) {
|
|
2146
|
+
const cell = gridCell(target);
|
|
2147
|
+
const columnId = cell?.dataset.col;
|
|
2148
|
+
if (!cell || columnId === void 0) continue;
|
|
2149
|
+
const model = this.#table.model;
|
|
2150
|
+
const row = gridRowOfCell(cell, layoutOf(model));
|
|
2151
|
+
if (!row) return void 0;
|
|
2152
|
+
return {
|
|
2153
|
+
section: row.section,
|
|
2154
|
+
columnId,
|
|
2155
|
+
rowIndex: row.section === "header" ? 0 : row.index,
|
|
2156
|
+
rowId: this.#gridRowId(model, row)
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
984
2159
|
}
|
|
985
2160
|
#observeProbe() {
|
|
986
2161
|
const probe = this.renderRoot.querySelector(".probe");
|
|
@@ -990,6 +2165,26 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
990
2165
|
});
|
|
991
2166
|
this.#probeObserver.observe(probe);
|
|
992
2167
|
}
|
|
2168
|
+
/** The height of the rows pinned to one side: one row each, or measured with `rowHeight: 'auto'`. */
|
|
2169
|
+
#pinnedPx(side, layout) {
|
|
2170
|
+
const count = side === "top" ? layout.top : layout.bottom;
|
|
2171
|
+
if (this.rowHeight !== "auto" || count === 0) return count * this.#rowPx;
|
|
2172
|
+
return Math.max(this.#rowMeasure.pinned[side], count * this.#rowPx);
|
|
2173
|
+
}
|
|
2174
|
+
/** The header rows and the pinned rows: the rows in the scroller besides the body. */
|
|
2175
|
+
#headerAndPinnedPx(layout) {
|
|
2176
|
+
return layout.header * this.#rowPx + this.#pinnedPx("top", layout) + this.#pinnedPx("bottom", layout);
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* `rowHeight: 'auto'`: reads the heights of the rendered rows, after their
|
|
2180
|
+
* content has rendered. Returns whether any changed (an update is requested).
|
|
2181
|
+
*/
|
|
2182
|
+
#measureRows() {
|
|
2183
|
+
const { rows, pinnedChanged } = this.#rowMeasure.read(this.renderRoot);
|
|
2184
|
+
const resized = this.#virtual.measureRows(rows);
|
|
2185
|
+
if (pinnedChanged) this.requestUpdate();
|
|
2186
|
+
return resized || pinnedChanged;
|
|
2187
|
+
}
|
|
993
2188
|
/** Reads the resolved row height, so themes and density change the virtualizer too. */
|
|
994
2189
|
#measureRowHeight() {
|
|
995
2190
|
const px = this.renderRoot.querySelector(".probe")?.getBoundingClientRect().height ?? 0;
|
|
@@ -1000,19 +2195,36 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
1000
2195
|
}
|
|
1001
2196
|
};
|
|
1002
2197
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "columns", null);
|
|
2198
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnGroups", null);
|
|
1003
2199
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "data", null);
|
|
1004
2200
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "dataSource", void 0);
|
|
1005
2201
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "pinnedRows", void 0);
|
|
1006
2202
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "getRowId", void 0);
|
|
1007
2203
|
__decorate([property({
|
|
1008
2204
|
attribute: "row-height",
|
|
1009
|
-
|
|
2205
|
+
converter: rowHeightConverter
|
|
1010
2206
|
})], ArgusTable.prototype, "rowHeight", void 0);
|
|
1011
2207
|
__decorate([property({ reflect: true })], ArgusTable.prototype, "density", void 0);
|
|
1012
2208
|
__decorate([property({ type: Number })], ArgusTable.prototype, "overscan", null);
|
|
2209
|
+
__decorate([property({
|
|
2210
|
+
attribute: "virtualize-columns",
|
|
2211
|
+
converter: switchConverter
|
|
2212
|
+
})], ArgusTable.prototype, "virtualizeColumns", void 0);
|
|
1013
2213
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "sorting", null);
|
|
1014
2214
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnSizing", null);
|
|
1015
2215
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnOrder", null);
|
|
2216
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnPinning", null);
|
|
2217
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnVisibility", null);
|
|
2218
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnFilters", null);
|
|
2219
|
+
__decorate([property({ attribute: "quick-filter" })], ArgusTable.prototype, "quickFilter", null);
|
|
2220
|
+
__decorate([property({ attribute: "selection-mode" })], ArgusTable.prototype, "selectionMode", void 0);
|
|
2221
|
+
__decorate([property({
|
|
2222
|
+
attribute: "selection-checkboxes",
|
|
2223
|
+
converter: switchConverter
|
|
2224
|
+
})], ArgusTable.prototype, "selectionCheckboxes", void 0);
|
|
2225
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "selection", null);
|
|
2226
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "contentRenderer", void 0);
|
|
2227
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "plugins", null);
|
|
1016
2228
|
ArgusTable = __decorate([customElement("argus-table")], ArgusTable);
|
|
1017
2229
|
export { ArgusTable };
|
|
1018
2230
|
|