argus-table 0.0.1 → 0.2.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 +25 -2
- package/dist/announce.js +27 -0
- package/dist/announce.js.map +1 -0
- package/dist/argus-column.js +121 -0
- package/dist/argus-column.js.map +1 -0
- package/dist/argus-table.js +618 -91
- package/dist/argus-table.js.map +1 -1
- package/dist/cdn/argus-table.js +87 -4715
- package/dist/cdn/argus-table.js.map +1 -1
- package/dist/controllers/auto-width.js +89 -0
- package/dist/controllers/auto-width.js.map +1 -0
- package/dist/controllers/column-children.js +41 -0
- package/dist/controllers/column-children.js.map +1 -0
- package/dist/controllers/data-controller.js +177 -0
- package/dist/controllers/data-controller.js.map +1 -0
- package/dist/controllers/pin-scroll-controller.js +78 -0
- package/dist/controllers/pin-scroll-controller.js.map +1 -0
- package/dist/controllers/reorder-drag.js +160 -0
- package/dist/controllers/reorder-drag.js.map +1 -0
- package/dist/controllers/resize-drag.js +3 -0
- package/dist/controllers/resize-drag.js.map +1 -1
- package/dist/controllers/table-controller.js +2 -1
- package/dist/controllers/table-controller.js.map +1 -1
- package/dist/controllers/virtual-controller.js +146 -9
- package/dist/controllers/virtual-controller.js.map +1 -1
- package/dist/core/src/columns/column-order.js +50 -0
- package/dist/core/src/columns/column-order.js.map +1 -0
- package/dist/core/src/columns/resolve-columns.js +5 -1
- package/dist/core/src/columns/resolve-columns.js.map +1 -1
- package/dist/core/src/data/page-cache.js +143 -0
- package/dist/core/src/data/page-cache.js.map +1 -0
- package/dist/core/src/grid/row-layout.js +58 -0
- package/dist/core/src/grid/row-layout.js.map +1 -0
- package/dist/core/src/table/create-table.js +112 -14
- package/dist/core/src/table/create-table.js.map +1 -1
- package/dist/core/src/table/sorted-row-model.js +86 -0
- package/dist/core/src/table/sorted-row-model.js.map +1 -0
- package/dist/core/src/virtual/scroll-scale.js +74 -0
- package/dist/core/src/virtual/scroll-scale.js.map +1 -0
- package/dist/custom-elements.json +1510 -49
- package/dist/events.js +5 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +252 -27
- package/dist/index.js +3 -2
- package/dist/max-height.js +68 -0
- package/dist/max-height.js.map +1 -0
- package/dist/render/body.js +53 -21
- package/dist/render/body.js.map +1 -1
- package/dist/render/cells.js +21 -4
- package/dist/render/cells.js.map +1 -1
- package/dist/render/content.js +12 -5
- package/dist/render/content.js.map +1 -1
- package/dist/render/header.js +21 -5
- package/dist/render/header.js.map +1 -1
- package/dist/render/pinned-rows.js +29 -0
- package/dist/render/pinned-rows.js.map +1 -0
- package/dist/styles/base.js +1 -225
- package/dist/styles/base.js.map +1 -1
- package/package.json +3 -1
package/dist/argus-table.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { controllable } from "./core/src/state/controllable.js";
|
|
2
|
+
import { RowLayout } from "./core/src/grid/row-layout.js";
|
|
3
|
+
import { announce } from "./announce.js";
|
|
4
|
+
import { AutoWidths, measureColumns } from "./controllers/auto-width.js";
|
|
5
|
+
import { ColumnChildrenController } from "./controllers/column-children.js";
|
|
6
|
+
import { DataController } from "./controllers/data-controller.js";
|
|
7
|
+
import { PinScrollController } from "./controllers/pin-scroll-controller.js";
|
|
8
|
+
import { canStartReorder, dropTarget, startReorderDrag } from "./controllers/reorder-drag.js";
|
|
2
9
|
import { startResizeDrag } from "./controllers/resize-drag.js";
|
|
3
10
|
import { TableController } from "./controllers/table-controller.js";
|
|
4
11
|
import { VirtualController } from "./controllers/virtual-controller.js";
|
|
5
|
-
import { CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE, fire } from "./events.js";
|
|
12
|
+
import { CELL_CLICK, COLUMN_REORDER, COLUMN_RESIZE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE, fire } from "./events.js";
|
|
13
|
+
import { bodyCap, watchMaxElementHeight } from "./max-height.js";
|
|
6
14
|
import { renderBody } from "./render/body.js";
|
|
7
15
|
import { renderLightDom } from "./render/cells.js";
|
|
8
16
|
import { renderHeader } from "./render/header.js";
|
|
17
|
+
import { renderPinnedRows } from "./render/pinned-rows.js";
|
|
9
18
|
import { baseStyles } from "./styles/base.js";
|
|
10
19
|
import __decorate from "./_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.js";
|
|
11
|
-
import { LitElement, html } from "lit";
|
|
20
|
+
import { LitElement, html, nothing } from "lit";
|
|
12
21
|
import { customElement, property } from "lit/decorators.js";
|
|
13
22
|
import { styleMap } from "lit/directives/style-map.js";
|
|
14
23
|
// src/argus-table.ts
|
|
@@ -36,15 +45,42 @@ function sortColumnOf(event) {
|
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
47
|
var RESIZE_STEP = 10;
|
|
48
|
+
var HEADER = {
|
|
49
|
+
section: "header",
|
|
50
|
+
index: 0
|
|
51
|
+
};
|
|
52
|
+
/** Where the rows sit in the grid: header, pinned top, body, pinned bottom. */
|
|
53
|
+
function layoutOf(model) {
|
|
54
|
+
return new RowLayout(model.pinnedCount("top"), model.rowCount, model.pinnedCount("bottom"));
|
|
55
|
+
}
|
|
39
56
|
/** The grid cell or column header an element is, if any. */
|
|
40
57
|
function gridCell(target) {
|
|
41
58
|
if (!(target instanceof HTMLElement) || target.dataset.col === void 0) return void 0;
|
|
42
59
|
const role = target.getAttribute("role");
|
|
43
60
|
return role === "gridcell" || role === "columnheader" ? target : void 0;
|
|
44
61
|
}
|
|
45
|
-
/**
|
|
46
|
-
function
|
|
47
|
-
|
|
62
|
+
/** The grid row a rendered row element is, from its `aria-rowindex`. */
|
|
63
|
+
function gridRowOf(row, layout) {
|
|
64
|
+
const index = row?.getAttribute("aria-rowindex");
|
|
65
|
+
return index ? layout.fromAriaRowIndex(Number(index)) : void 0;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The pinned edge's shadow, drawn once per side over the scroller rather than
|
|
69
|
+
* per row: a shadow on every pinned cell repaints on every scroll frame. It
|
|
70
|
+
* covers the pinned columns and casts `--argus-pinned-shadow` onto the rest;
|
|
71
|
+
* PinScrollController sizes it to the visible area.
|
|
72
|
+
*/
|
|
73
|
+
function pinShadow(side, width, rows, rowPx) {
|
|
74
|
+
if (width === 0) return nothing;
|
|
75
|
+
return html`<div
|
|
76
|
+
class="pin-shadow"
|
|
77
|
+
data-side=${side}
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
style=${styleMap({
|
|
80
|
+
inlineSize: `${String(width)}px`,
|
|
81
|
+
"--_content-h": `${String((rows + 1) * rowPx)}px`
|
|
82
|
+
})}
|
|
83
|
+
></div>`;
|
|
48
84
|
}
|
|
49
85
|
/** CSS grid track for a column: fixed px, or a flex share with the min width as floor. */
|
|
50
86
|
function track(column) {
|
|
@@ -53,15 +89,29 @@ function track(column) {
|
|
|
53
89
|
var ArgusTable = class ArgusTable extends LitElement {
|
|
54
90
|
constructor(..._args) {
|
|
55
91
|
super(..._args);
|
|
56
|
-
this.columns = [];
|
|
57
|
-
this.data = [];
|
|
58
92
|
this.density = "comfortable";
|
|
59
|
-
this
|
|
93
|
+
this.#columnsProp = [];
|
|
94
|
+
this.#dataProp = [];
|
|
95
|
+
this.#overscan = 6;
|
|
60
96
|
this.#sorting = controllable([]);
|
|
61
97
|
this.#sizing = controllable({});
|
|
98
|
+
this.#order = controllable([]);
|
|
99
|
+
this.#appliedReorders = /* @__PURE__ */ new Map();
|
|
100
|
+
this.#appliedResizes = /* @__PURE__ */ new Map();
|
|
101
|
+
this.#autoWidths = new AutoWidths(() => {
|
|
102
|
+
this.requestUpdate();
|
|
103
|
+
});
|
|
104
|
+
this.#autoWidthsChanged = false;
|
|
62
105
|
this.#internals = attachGridInternals(this);
|
|
63
106
|
this.#table = new TableController();
|
|
107
|
+
this.#children = new ColumnChildrenController(this);
|
|
108
|
+
this.#columns = [];
|
|
109
|
+
this.#columnsChanged = false;
|
|
110
|
+
this.#warnedColumns = false;
|
|
111
|
+
this.#data = new DataController(this);
|
|
112
|
+
this.#scrollToTop = false;
|
|
64
113
|
this.#virtual = new VirtualController(this, () => this.renderRoot.querySelector("[part~=\"table\"]"));
|
|
114
|
+
this.#pinScroll = new PinScrollController(this, () => this.renderRoot.querySelector("[part~=\"table\"]"));
|
|
65
115
|
this.#rowPx = 40;
|
|
66
116
|
this.#window = {
|
|
67
117
|
rows: [],
|
|
@@ -70,6 +120,11 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
70
120
|
};
|
|
71
121
|
this.#focusPending = false;
|
|
72
122
|
this.#lastRange = "";
|
|
123
|
+
this.#onRetry = (event) => {
|
|
124
|
+
const button = event.currentTarget;
|
|
125
|
+
if (button.matches(":focus")) button.parentElement?.focus({ preventScroll: true });
|
|
126
|
+
this.#data.retry(Number(button.dataset.row));
|
|
127
|
+
};
|
|
73
128
|
this.#headerHandlers = {
|
|
74
129
|
click: (event) => {
|
|
75
130
|
const id = sortColumnOf(event);
|
|
@@ -81,18 +136,28 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
81
136
|
if (!(handle instanceof HTMLElement)) return;
|
|
82
137
|
const id = handle.dataset.resizeCol;
|
|
83
138
|
const cell = handle.parentElement;
|
|
84
|
-
if (id
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
139
|
+
if (id !== void 0 && cell) {
|
|
140
|
+
startResizeDrag(handle, event, cell.getBoundingClientRect().width, this.#direction(), (w) => {
|
|
141
|
+
this.#requestResize(id, w);
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.#startReorder(event);
|
|
146
|
+
},
|
|
147
|
+
/** A double-click on a resize handle fits the column to its rendered content. */
|
|
148
|
+
dblclick: (event) => {
|
|
149
|
+
const handle = event.composedPath()[0];
|
|
150
|
+
const id = handle instanceof HTMLElement ? handle.dataset.resizeCol : void 0;
|
|
151
|
+
if (id === void 0) return;
|
|
152
|
+
this.#requestResizes(measureColumns(this.renderRoot, [id]));
|
|
88
153
|
}
|
|
89
154
|
};
|
|
90
155
|
this.#onKeydown = (event) => {
|
|
91
156
|
const cell = gridCell(event.composedPath()[0] ?? event.target ?? this);
|
|
92
157
|
const id = cell?.dataset.col;
|
|
93
158
|
if (!cell || id === void 0) return;
|
|
159
|
+
const scroller = event.currentTarget;
|
|
94
160
|
if (event.key === "Tab" && event.shiftKey) {
|
|
95
|
-
const scroller = event.currentTarget;
|
|
96
161
|
scroller.tabIndex = -1;
|
|
97
162
|
cell.addEventListener("focusout", () => {
|
|
98
163
|
scroller.removeAttribute("tabindex");
|
|
@@ -101,7 +166,14 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
101
166
|
}
|
|
102
167
|
const inHeader = cell.getAttribute("role") === "columnheader";
|
|
103
168
|
const { key } = event;
|
|
104
|
-
|
|
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);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (inHeader && event.altKey && arrow) {
|
|
105
177
|
event.preventDefault();
|
|
106
178
|
const towardEnd = key === "ArrowRight" === (this.#direction() === 1);
|
|
107
179
|
this.#requestResize(id, this.#currentWidth(id, cell) + (towardEnd ? RESIZE_STEP : -10));
|
|
@@ -112,22 +184,41 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
112
184
|
this.#requestSort(id);
|
|
113
185
|
return;
|
|
114
186
|
}
|
|
115
|
-
|
|
187
|
+
const corner = event.ctrlKey || event.metaKey;
|
|
188
|
+
if (event.altKey || event.shiftKey || corner && key !== "Home" && key !== "End") return;
|
|
189
|
+
const model = this.#table.model;
|
|
190
|
+
const layout = layoutOf(model);
|
|
191
|
+
const from = inHeader ? HEADER : gridRowOf(cell.parentElement, layout);
|
|
192
|
+
if (!from) return;
|
|
116
193
|
if (!inHeader && key === "Enter") {
|
|
117
194
|
event.preventDefault();
|
|
118
|
-
this.#
|
|
195
|
+
if (from.section === "body" && this.#data.stateAt(from.index) === "error") this.#data.retry(from.index);
|
|
196
|
+
else this.#activate(event.composedPath());
|
|
119
197
|
return;
|
|
120
198
|
}
|
|
121
|
-
const model = this.#table.model;
|
|
122
199
|
const columns = model.columns;
|
|
123
|
-
let
|
|
200
|
+
let pos = layout.position(from);
|
|
201
|
+
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);
|
|
124
209
|
let col = columns.findIndex((c) => c.id === id);
|
|
125
210
|
switch (key) {
|
|
211
|
+
case "PageDown":
|
|
212
|
+
if (layout.body > 0 && pos <= bodyLast) pos = Math.min(Math.max(pos, bodyFirst - 1) + page, bodyLast);
|
|
213
|
+
break;
|
|
214
|
+
case "PageUp":
|
|
215
|
+
if (layout.body > 0 && pos > bodyFirst) pos = Math.max(Math.min(pos, bodyLast + 1) - page, bodyFirst);
|
|
216
|
+
break;
|
|
126
217
|
case "ArrowDown":
|
|
127
|
-
|
|
218
|
+
pos = Math.min(pos + 1, last);
|
|
128
219
|
break;
|
|
129
220
|
case "ArrowUp":
|
|
130
|
-
|
|
221
|
+
pos = Math.max(pos - 1, 0);
|
|
131
222
|
break;
|
|
132
223
|
case "ArrowRight":
|
|
133
224
|
col += this.#direction();
|
|
@@ -137,13 +228,21 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
137
228
|
break;
|
|
138
229
|
case "Home":
|
|
139
230
|
col = 0;
|
|
231
|
+
if (corner) {
|
|
232
|
+
pos = 0;
|
|
233
|
+
scroller.scrollTop = 0;
|
|
234
|
+
}
|
|
140
235
|
break;
|
|
141
236
|
case "End":
|
|
142
237
|
col = columns.length - 1;
|
|
238
|
+
if (corner) pos = last;
|
|
143
239
|
break;
|
|
144
240
|
default: return;
|
|
145
241
|
}
|
|
146
242
|
event.preventDefault();
|
|
243
|
+
const row = layout.at(pos) ?? HEADER;
|
|
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") scroller.scrollTop = scroller.scrollHeight;
|
|
147
246
|
const target = columns[Math.min(Math.max(col, 0), columns.length - 1)];
|
|
148
247
|
if (target) this.#moveTo(row, target.id, model);
|
|
149
248
|
};
|
|
@@ -160,12 +259,14 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
160
259
|
for (const target of event.composedPath()) {
|
|
161
260
|
const cell = gridCell(target);
|
|
162
261
|
if (cell?.dataset.col !== void 0) {
|
|
163
|
-
const
|
|
262
|
+
const model = this.#table.model;
|
|
263
|
+
const row = cell.getAttribute("role") === "columnheader" ? HEADER : gridRowOf(cell.parentElement, layoutOf(model));
|
|
164
264
|
const { col } = cell.dataset;
|
|
165
|
-
|
|
265
|
+
const prev = this.#active;
|
|
266
|
+
if (row && (row.section !== prev?.section || row.index !== prev.index || col !== prev.col)) {
|
|
166
267
|
this.#active = {
|
|
167
|
-
|
|
168
|
-
rowId: this.#
|
|
268
|
+
...row,
|
|
269
|
+
rowId: this.#gridRowId(model, row),
|
|
169
270
|
col
|
|
170
271
|
};
|
|
171
272
|
this.requestUpdate();
|
|
@@ -183,6 +284,34 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
183
284
|
this.styles = baseStyles;
|
|
184
285
|
}
|
|
185
286
|
/**
|
|
287
|
+
* Column definitions. Property only: functions can't be attributes. While empty,
|
|
288
|
+
* the table reads its `<argus-column>` children instead. `null` or `undefined`
|
|
289
|
+
* means `[]`.
|
|
290
|
+
*/
|
|
291
|
+
get columns() {
|
|
292
|
+
return this.#columnsProp;
|
|
293
|
+
}
|
|
294
|
+
set columns(value) {
|
|
295
|
+
this.#columnsProp = value ?? [];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The rows to show. Replace the array to update; changes inside it aren't
|
|
299
|
+
* detected. `null` or `undefined` means `[]`.
|
|
300
|
+
*/
|
|
301
|
+
get data() {
|
|
302
|
+
return this.#dataProp;
|
|
303
|
+
}
|
|
304
|
+
set data(value) {
|
|
305
|
+
this.#dataProp = value ?? [];
|
|
306
|
+
}
|
|
307
|
+
/** Rows rendered above and below the visible ones. `null` or `undefined` means the default, 6. */
|
|
308
|
+
get overscan() {
|
|
309
|
+
return this.#overscan;
|
|
310
|
+
}
|
|
311
|
+
set overscan(value) {
|
|
312
|
+
this.#overscan = value ?? 6;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
186
315
|
* The sort order, e.g. `[{ id: 'total', desc: true }]`; `[]` when unsorted. Unset,
|
|
187
316
|
* the table keeps it itself and header clicks change it. Once set, it is
|
|
188
317
|
* controlled: a click only fires `argus-sort-change` with the proposed order,
|
|
@@ -205,15 +334,56 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
205
334
|
set columnSizing(value) {
|
|
206
335
|
this.#sizing.set(value);
|
|
207
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* The column order: column ids as the table shows them, e.g.
|
|
339
|
+
* `['id', 'total', 'customer']`. `[]` (the default) keeps the order of `columns`.
|
|
340
|
+
* Each pin group (start-pinned, scrolling, end-pinned) takes its columns in this
|
|
341
|
+
* order; unknown ids are ignored and columns it doesn't list follow the listed
|
|
342
|
+
* ones of their group. After a move it holds every column id. Uncontrolled
|
|
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;
|
|
208
355
|
#sorting;
|
|
209
356
|
#sizing;
|
|
210
|
-
|
|
211
|
-
|
|
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;
|
|
212
371
|
/** A sort the table applied itself; its event fires once the update is done. */
|
|
213
372
|
#appliedSort;
|
|
214
373
|
#internals;
|
|
215
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;
|
|
216
385
|
#virtual;
|
|
386
|
+
#pinScroll;
|
|
217
387
|
#rowPx;
|
|
218
388
|
#probeObserver;
|
|
219
389
|
#window;
|
|
@@ -221,14 +391,69 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
221
391
|
/** Move focus to the active cell after the next update (keyboard navigation). */
|
|
222
392
|
#focusPending;
|
|
223
393
|
#lastRange;
|
|
394
|
+
#unwatchMaxHeight;
|
|
224
395
|
connectedCallback() {
|
|
225
396
|
super.connectedCallback();
|
|
226
397
|
if (!this.hasAttribute("role")) this.setAttribute("role", "grid");
|
|
227
398
|
if (this.hasUpdated) this.#observeProbe();
|
|
399
|
+
this.#unwatchMaxHeight = watchMaxElementHeight(() => {
|
|
400
|
+
this.requestUpdate();
|
|
401
|
+
});
|
|
228
402
|
}
|
|
229
403
|
disconnectedCallback() {
|
|
230
404
|
super.disconnectedCallback();
|
|
231
405
|
this.#probeObserver?.disconnect();
|
|
406
|
+
this.#unwatchMaxHeight?.();
|
|
407
|
+
this.#unwatchMaxHeight = void 0;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* `columns` when it has any, otherwise the `<argus-column>` children. Keeps its
|
|
411
|
+
* identity while neither changes, so the model is only rebuilt on a change.
|
|
412
|
+
*/
|
|
413
|
+
#effectiveColumns() {
|
|
414
|
+
if (this.columns.length === 0) return this.#children.defs;
|
|
415
|
+
if (!this.#warnedColumns && this.#children.present) {
|
|
416
|
+
this.#warnedColumns = true;
|
|
417
|
+
console.warn("argus-table: the columns property is set, so <argus-column> children are ignored.");
|
|
418
|
+
}
|
|
419
|
+
return this.columns;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* The node the light-DOM content is rendered before. Lit's part then ends there,
|
|
423
|
+
* so children appended later (an `<argus-column>`) never land inside it. If the
|
|
424
|
+
* marker was removed (say by `innerHTML`), a new one starts a fresh part.
|
|
425
|
+
*/
|
|
426
|
+
#lightDomEnd() {
|
|
427
|
+
if (this.#contentEnd?.parentNode !== this) {
|
|
428
|
+
this.#contentEnd = document.createComment("");
|
|
429
|
+
this.append(this.#contentEnd);
|
|
430
|
+
}
|
|
431
|
+
return this.#contentEnd;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* With a `dataSource`, drops every loaded page and loads the rows in view
|
|
435
|
+
* again, keeping the scroll position. With `data`, re-renders the cells (after
|
|
436
|
+
* changing row objects in place). Resolves once the table has re-rendered;
|
|
437
|
+
* reloaded rows arrive as their pages load.
|
|
438
|
+
*/
|
|
439
|
+
async refresh() {
|
|
440
|
+
this.#data.refresh();
|
|
441
|
+
this.requestUpdate();
|
|
442
|
+
await this.updateComplete;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Fits columns to their content: the header, the pinned rows and the body rows
|
|
446
|
+
* rendered now (rows out of view aren't measured). It resizes them like a drag
|
|
447
|
+
* would, so `argus-column-resize` fires for each column that changes, and with a
|
|
448
|
+
* controlled `columnSizing` nothing changes until you set it. Without `ids`, fits
|
|
449
|
+
* every resizable column; columns with `resizable: false` keep their width.
|
|
450
|
+
* Resolves once the table has re-rendered.
|
|
451
|
+
*/
|
|
452
|
+
async autosizeColumns(ids) {
|
|
453
|
+
await this.updateComplete;
|
|
454
|
+
const targets = this.#table.model.columns.filter((c) => c.resizable && (ids === void 0 || ids.includes(c.id))).map((c) => c.id);
|
|
455
|
+
this.#requestResizes(measureColumns(this.renderRoot, targets));
|
|
456
|
+
await this.updateComplete;
|
|
232
457
|
}
|
|
233
458
|
/** Scrolls so that the row at `index` (in the current row order) is at the top. */
|
|
234
459
|
async scrollToRow(index) {
|
|
@@ -237,24 +462,51 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
237
462
|
await this.updateComplete;
|
|
238
463
|
}
|
|
239
464
|
willUpdate(changed) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
465
|
+
const columns = this.#effectiveColumns();
|
|
466
|
+
this.#columnsChanged = columns !== this.#columns;
|
|
467
|
+
this.#columns = columns;
|
|
468
|
+
if (changed.has("dataSource") || changed.has("sorting") || changed.has("getRowId")) {
|
|
469
|
+
this.#data.configure({
|
|
470
|
+
source: this.dataSource,
|
|
471
|
+
sorting: this.#sorting.value,
|
|
472
|
+
getRowId: this.getRowId
|
|
473
|
+
});
|
|
474
|
+
if (this.dataSource && this.hasUpdated && changed.has("sorting")) this.#scrollToTop = !changed.has("dataSource");
|
|
475
|
+
}
|
|
476
|
+
if (this.#columnsChanged || changed.has("data") || changed.has("getRowId") || changed.has("sorting") || changed.has("columnSizing") || changed.has("columnOrder") || changed.has("pinnedRows")) {
|
|
477
|
+
if (this.#columnsChanged) this.#autoWidths.sync(columns.filter((c) => c.width === "auto").map((c) => c.id));
|
|
478
|
+
this.#table.update({
|
|
479
|
+
columns,
|
|
480
|
+
data: this.data,
|
|
481
|
+
getRowId: this.getRowId,
|
|
482
|
+
sorting: this.#sorting.value,
|
|
483
|
+
columnSizing: this.#sizing.value,
|
|
484
|
+
columnOrder: this.#order.value,
|
|
485
|
+
pinnedRows: this.pinnedRows
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const autoWidths = this.#autoWidths.widths;
|
|
489
|
+
this.#autoWidthsChanged = autoWidths !== this.#pushedAutoWidths;
|
|
490
|
+
this.#pushedAutoWidths = autoWidths;
|
|
491
|
+
this.#table.update({
|
|
492
|
+
remote: this.#data.rows,
|
|
493
|
+
autoWidths
|
|
246
494
|
});
|
|
247
495
|
const model = this.#table.model;
|
|
248
496
|
this.#reconcileActive(model);
|
|
249
497
|
const active = this.#active;
|
|
250
|
-
|
|
498
|
+
const unknownTotal = this.#data.rows?.totalKnown === false;
|
|
499
|
+
const layout = layoutOf(model);
|
|
500
|
+
this.#internals.ariaRowCount = unknownTotal ? "-1" : String(layout.total);
|
|
251
501
|
this.#internals.ariaColCount = String(model.columns.length);
|
|
502
|
+
const extra = (1 + layout.top + layout.bottom) * this.#rowPx;
|
|
252
503
|
this.#virtual.configure({
|
|
253
504
|
count: model.rowCount,
|
|
254
505
|
rowHeight: this.#rowPx,
|
|
506
|
+
cap: bodyCap(model.rowCount * this.#rowPx, extra),
|
|
255
507
|
overscan: Math.max(0, this.overscan),
|
|
256
|
-
rowIdAt: (index) => model
|
|
257
|
-
keep: active?.rowId === void 0 ? void 0 : {
|
|
508
|
+
rowIdAt: (index) => this.#rowId(model, index),
|
|
509
|
+
keep: active?.section !== "body" || active.rowId === void 0 ? void 0 : {
|
|
258
510
|
id: active.rowId,
|
|
259
511
|
index: active.index
|
|
260
512
|
}
|
|
@@ -264,47 +516,77 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
264
516
|
const model = this.#table.model;
|
|
265
517
|
const columns = model.columns;
|
|
266
518
|
this.#window = this.#virtual.computeWindow();
|
|
267
|
-
const
|
|
519
|
+
const pinnedWidth = (side) => columns.reduce((sum, c) => c.pin === side ? sum + c.width : sum, 0);
|
|
520
|
+
const pinned = (side) => `${String(pinnedWidth(side))}px`;
|
|
521
|
+
const layout = layoutOf(model);
|
|
268
522
|
const style = {
|
|
269
523
|
"--_cols": columns.map(track).join(" "),
|
|
270
524
|
"--_width": `${String(model.totalWidth)}px`,
|
|
271
525
|
"--_pin-start": pinned("start"),
|
|
272
|
-
"--_pin-end": pinned("end")
|
|
526
|
+
"--_pin-end": pinned("end"),
|
|
527
|
+
"--_pin-top-rows": String(layout.top),
|
|
528
|
+
"--_pin-bottom-rows": String(layout.bottom)
|
|
273
529
|
};
|
|
274
530
|
const active = this.#active;
|
|
275
|
-
const
|
|
531
|
+
const activeIn = (section) => active?.section === section ? {
|
|
276
532
|
index: active.index,
|
|
277
533
|
col: active.col
|
|
278
534
|
} : void 0;
|
|
535
|
+
const firstIndex = (section) => layout.ariaRowIndex({
|
|
536
|
+
section,
|
|
537
|
+
index: 0
|
|
538
|
+
});
|
|
279
539
|
if (this.rowHeight !== void 0 && this.rowHeight > 0) style["--_row-height"] = `${String(this.rowHeight)}px`;
|
|
280
540
|
return html`<div
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
541
|
+
part="table"
|
|
542
|
+
role="presentation"
|
|
543
|
+
style=${styleMap(style)}
|
|
544
|
+
?data-dragging=${this.#drag !== void 0}
|
|
545
|
+
@keydown=${this.#onKeydown}
|
|
546
|
+
@focusin=${this.#onFocusin}
|
|
547
|
+
@click=${this.#onClick}
|
|
548
|
+
>
|
|
549
|
+
${renderHeader(columns, this.#headerHandlers, activeIn("header")?.col, this.#drag)}
|
|
550
|
+
${renderPinnedRows("top", layout.top, columns, firstIndex("top"), activeIn("top"))}
|
|
551
|
+
${model.rowCount === 0 ? html`<div part="empty"><slot name="empty">No rows</slot></div>` : renderBody(this.#window.rows, columns, this.#window.totalSize, activeIn("body"), firstIndex("body"), this.#loading())}
|
|
552
|
+
${renderPinnedRows("bottom", layout.bottom, columns, firstIndex("bottom"), activeIn("bottom"))}
|
|
553
|
+
<div class="probe" aria-hidden="true"></div>
|
|
554
|
+
</div>
|
|
555
|
+
${pinShadow("start", pinnedWidth("start"), layout.total - 1, this.#rowPx)}
|
|
556
|
+
${pinShadow("end", pinnedWidth("end"), layout.total - 1, this.#rowPx)}`;
|
|
292
557
|
}
|
|
293
558
|
firstUpdated() {
|
|
294
559
|
this.#observeProbe();
|
|
295
560
|
this.#measureRowHeight();
|
|
296
561
|
}
|
|
297
562
|
updated(changed) {
|
|
298
|
-
|
|
563
|
+
const model = this.#table.model;
|
|
564
|
+
renderLightDom(this, model, this.#window.rows, this.#lightDomEnd());
|
|
565
|
+
this.#measureAutoWidths(model);
|
|
566
|
+
if (this.#scrollToTop) {
|
|
567
|
+
this.#scrollToTop = false;
|
|
568
|
+
const scroller = this.renderRoot.querySelector("[part~=\"table\"]");
|
|
569
|
+
if (scroller) scroller.scrollTop = 0;
|
|
570
|
+
}
|
|
571
|
+
if (this.#columnsChanged || changed.has("columnSizing") || changed.has("columnOrder") || this.#autoWidthsChanged) this.#pinScroll.measure();
|
|
299
572
|
if (this.#focusPending) {
|
|
300
573
|
this.#focusPending = false;
|
|
301
574
|
this.#focusActive();
|
|
302
575
|
}
|
|
303
576
|
if (changed.has("density") || changed.has("rowHeight")) this.#measureRowHeight();
|
|
304
|
-
if (this.#
|
|
305
|
-
const
|
|
306
|
-
this.#
|
|
307
|
-
fire(this, COLUMN_RESIZE, detail);
|
|
577
|
+
if (this.#appliedResizes.size > 0) {
|
|
578
|
+
const details = [...this.#appliedResizes.values()];
|
|
579
|
+
this.#appliedResizes.clear();
|
|
580
|
+
for (const detail of details) fire(this, COLUMN_RESIZE, detail);
|
|
581
|
+
}
|
|
582
|
+
if (this.#announceMove) {
|
|
583
|
+
this.#announceMoved(model, this.#announceMove);
|
|
584
|
+
this.#announceMove = void 0;
|
|
585
|
+
}
|
|
586
|
+
if (this.#appliedReorders.size > 0) {
|
|
587
|
+
const details = [...this.#appliedReorders.values()];
|
|
588
|
+
this.#appliedReorders.clear();
|
|
589
|
+
for (const detail of details) fire(this, COLUMN_REORDER, detail);
|
|
308
590
|
}
|
|
309
591
|
if (this.#appliedSort) {
|
|
310
592
|
const sorting = this.#appliedSort;
|
|
@@ -320,11 +602,73 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
320
602
|
end: range.end
|
|
321
603
|
});
|
|
322
604
|
}
|
|
605
|
+
if (range) {
|
|
606
|
+
const over = Math.max(0, this.overscan);
|
|
607
|
+
this.#data.request(Math.max(0, range.start - over), range.end + 1 + over);
|
|
608
|
+
}
|
|
609
|
+
for (const detail of this.#data.takeErrors()) fire(this, LOAD_ERROR, detail);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Measures the `width: 'auto'` columns once loaded body rows render, or right
|
|
613
|
+
* away when there are no rows (the header and pinned rows only). While a data
|
|
614
|
+
* source's first rows load, they keep the default width. A changed width
|
|
615
|
+
* renders once more.
|
|
616
|
+
*/
|
|
617
|
+
#measureAutoWidths(model) {
|
|
618
|
+
const loaded = this.#window.rows.some((r) => model.rowAt(r.index) !== void 0);
|
|
619
|
+
if (!loaded && model.rowCount > 0) return;
|
|
620
|
+
if (this.#autoWidths.measure(this.renderRoot, model.columns, loaded)) this.requestUpdate();
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Loading state for the body while a data source is set: the state of each
|
|
624
|
+
* row, and a Retry button on the first visible row of each failed page.
|
|
625
|
+
*/
|
|
626
|
+
#loading() {
|
|
627
|
+
const cache = this.#data.rows;
|
|
628
|
+
if (!cache) return void 0;
|
|
629
|
+
const top = this.#window.range?.start ?? 0;
|
|
630
|
+
const firstByPage = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const { index } of this.#window.rows) {
|
|
632
|
+
if (index < top || cache.stateAt(index) !== "error") continue;
|
|
633
|
+
const page = cache.pageOf(index);
|
|
634
|
+
const first = firstByPage.get(page);
|
|
635
|
+
if (first === void 0 || index < first) firstByPage.set(page, index);
|
|
636
|
+
}
|
|
637
|
+
const retry = /* @__PURE__ */ new Map();
|
|
638
|
+
for (const [page, index] of firstByPage) {
|
|
639
|
+
const start = page * cache.pageSize;
|
|
640
|
+
const end = Math.min(start + cache.pageSize, cache.rowCount);
|
|
641
|
+
retry.set(index, `Retry loading rows ${String(start + 1)} to ${String(end)}`);
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
stateAt: (index) => cache.stateAt(index),
|
|
645
|
+
retry,
|
|
646
|
+
onRetry: this.#onRetry
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
#onRetry;
|
|
650
|
+
/**
|
|
651
|
+
* A row's id. A data source row that hasn't loaded gets a stand-in: the
|
|
652
|
+
* active cell's row id if it sits there (so its DOM and focus stay put), else
|
|
653
|
+
* the index, which is also its real id when there is no `getRowId`.
|
|
654
|
+
*/
|
|
655
|
+
#rowId(model, index) {
|
|
656
|
+
const id = model.rowAt(index)?.id;
|
|
657
|
+
if (id !== void 0) return id;
|
|
658
|
+
const active = this.#active;
|
|
659
|
+
if (active?.index === index && active.rowId !== void 0) return active.rowId;
|
|
660
|
+
return this.#placeholderId(index);
|
|
661
|
+
}
|
|
662
|
+
/** The id of an unloaded row: its index, or a reserved string if `getRowId` makes the ids. */
|
|
663
|
+
#placeholderId(index) {
|
|
664
|
+
return this.getRowId ? `\u0000${String(index)}` : String(index);
|
|
323
665
|
}
|
|
324
666
|
#headerHandlers;
|
|
325
667
|
/**
|
|
326
|
-
* Keys on a focused cell or header
|
|
327
|
-
* a
|
|
668
|
+
* Keys on a focused cell or header: arrows by cell, Home/End along the row,
|
|
669
|
+
* PageUp/PageDown by a page of rows (the view scrolls with it), Ctrl+Home/End
|
|
670
|
+
* (or Cmd) to the first header and the last cell. Keys from content inside a
|
|
671
|
+
* cell (an input, a button) are left to that content.
|
|
328
672
|
*/
|
|
329
673
|
#onKeydown;
|
|
330
674
|
/** Focus that lands in a cell (a click, or content inside it) makes that cell active. */
|
|
@@ -333,7 +677,8 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
333
677
|
#onClick;
|
|
334
678
|
/**
|
|
335
679
|
* Fires `argus-cell-click` (when a cell is on the path), then `argus-row-click`,
|
|
336
|
-
* for the body row on an event path. Clicks and Enter on a
|
|
680
|
+
* 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 header fire nothing.
|
|
337
682
|
*/
|
|
338
683
|
#activate(path) {
|
|
339
684
|
let columnId;
|
|
@@ -343,27 +688,55 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
343
688
|
if (role === "gridcell") columnId = target.dataset.col;
|
|
344
689
|
if (role !== "row") continue;
|
|
345
690
|
const model = this.#table.model;
|
|
346
|
-
const
|
|
347
|
-
|
|
691
|
+
const at = gridRowOf(target, layoutOf(model));
|
|
692
|
+
if (!at || at.section === "header") return;
|
|
693
|
+
const { index } = at;
|
|
694
|
+
if (at.section === "body") {
|
|
695
|
+
const row = model.rowAt(index);
|
|
696
|
+
if (!row) return;
|
|
697
|
+
if (columnId !== void 0) fire(this, CELL_CLICK, {
|
|
698
|
+
row: row.original,
|
|
699
|
+
columnId,
|
|
700
|
+
value: model.valueAt(index, columnId)
|
|
701
|
+
});
|
|
702
|
+
fire(this, ROW_CLICK, {
|
|
703
|
+
row: row.original,
|
|
704
|
+
rowId: row.id,
|
|
705
|
+
index
|
|
706
|
+
});
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const pinned = at.section;
|
|
710
|
+
const row = model.pinnedRowAt(pinned, index);
|
|
348
711
|
if (!row) return;
|
|
349
712
|
if (columnId !== void 0) fire(this, CELL_CLICK, {
|
|
350
713
|
row: row.original,
|
|
351
714
|
columnId,
|
|
352
|
-
value: model.
|
|
715
|
+
value: model.pinnedValueAt(pinned, index, columnId),
|
|
716
|
+
pinned
|
|
353
717
|
});
|
|
354
718
|
fire(this, ROW_CLICK, {
|
|
355
719
|
row: row.original,
|
|
356
720
|
rowId: row.id,
|
|
357
|
-
index
|
|
721
|
+
index,
|
|
722
|
+
pinned
|
|
358
723
|
});
|
|
359
724
|
return;
|
|
360
725
|
}
|
|
361
726
|
}
|
|
727
|
+
/** The id of a grid row: a body or pinned row's id; none for the header. */
|
|
728
|
+
#gridRowId(model, row) {
|
|
729
|
+
switch (row.section) {
|
|
730
|
+
case "header": return;
|
|
731
|
+
case "body": return this.#rowId(model, row.index);
|
|
732
|
+
default: return model.pinnedRowAt(row.section, row.index)?.id;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
362
735
|
/** Makes a cell active and focuses it after the update. The keep-rendered rule renders its row. */
|
|
363
|
-
#moveTo(
|
|
736
|
+
#moveTo(row, col, model) {
|
|
364
737
|
this.#active = {
|
|
365
|
-
|
|
366
|
-
rowId:
|
|
738
|
+
...row,
|
|
739
|
+
rowId: this.#gridRowId(model, row),
|
|
367
740
|
col
|
|
368
741
|
};
|
|
369
742
|
this.#focusPending = true;
|
|
@@ -373,20 +746,30 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
373
746
|
const active = this.#active;
|
|
374
747
|
if (!active) return;
|
|
375
748
|
const col = CSS.escape(active.col);
|
|
376
|
-
const
|
|
377
|
-
const cell = this.renderRoot.querySelector(
|
|
749
|
+
const index = layoutOf(this.#table.model).ariaRowIndex(active);
|
|
750
|
+
const cell = this.renderRoot.querySelector(`[role='row'][aria-rowindex="${String(index)}"] > [data-col="${col}"]`);
|
|
378
751
|
if (!cell) return;
|
|
752
|
+
cell.focus({ preventScroll: true });
|
|
753
|
+
if (active.section === "body" && this.#virtual.scaled) {
|
|
754
|
+
const layout = layoutOf(this.#table.model);
|
|
755
|
+
const before = (1 + layout.top) * this.#rowPx;
|
|
756
|
+
if (this.#virtual.reveal(active.index, before, layout.bottom * this.#rowPx)) {
|
|
757
|
+
this.#focusPending = true;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
379
761
|
cell.scrollIntoView({
|
|
380
762
|
block: "nearest",
|
|
381
763
|
inline: "nearest"
|
|
382
764
|
});
|
|
383
|
-
cell.focus({ preventScroll: true });
|
|
384
765
|
}
|
|
385
766
|
/**
|
|
386
|
-
* Keeps the active cell valid after columns or rows change
|
|
387
|
-
* when it still exists (it may have moved), else at the same
|
|
388
|
-
*
|
|
389
|
-
*
|
|
767
|
+
* Keeps the active cell valid after columns or rows change. In the body: on the
|
|
768
|
+
* same row id when it still exists (it may have moved), else at the same
|
|
769
|
+
* index, clamped. A data source row that isn't loaded (yet, or any more) keeps
|
|
770
|
+
* its id. A pinned row stays at its index, clamped to its list. A row whose
|
|
771
|
+
* section has no rows left gives way to the header, the default. If its row
|
|
772
|
+
* went away while the grid had focus, focus follows to the replacement.
|
|
390
773
|
*/
|
|
391
774
|
#reconcileActive(model) {
|
|
392
775
|
const columns = model.columns;
|
|
@@ -396,21 +779,37 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
396
779
|
return;
|
|
397
780
|
}
|
|
398
781
|
const prev = this.#active ?? {
|
|
399
|
-
|
|
782
|
+
...HEADER,
|
|
400
783
|
rowId: void 0,
|
|
401
784
|
col: first.id
|
|
402
785
|
};
|
|
403
786
|
const col = columns.some((c) => c.id === prev.col) ? prev.col : first.id;
|
|
404
|
-
let { index, rowId } = prev;
|
|
405
|
-
|
|
787
|
+
let { section, index, rowId } = prev;
|
|
788
|
+
let moved = false;
|
|
789
|
+
if (section === "body" && rowId !== void 0) {
|
|
406
790
|
const found = model.indexOf(rowId);
|
|
407
|
-
|
|
791
|
+
const unloaded = index < model.rowCount && model.rowAt(index) === void 0;
|
|
792
|
+
if (found !== void 0) index = found;
|
|
793
|
+
else if (!unloaded) {
|
|
408
794
|
index = Math.min(index, model.rowCount - 1);
|
|
409
|
-
rowId = model.rowAt(index)?.id;
|
|
410
|
-
|
|
411
|
-
}
|
|
795
|
+
rowId = index >= 0 ? model.rowAt(index)?.id ?? this.#placeholderId(index) : void 0;
|
|
796
|
+
moved = true;
|
|
797
|
+
}
|
|
798
|
+
} else if (section === "top" || section === "bottom") {
|
|
799
|
+
const count = model.pinnedCount(section);
|
|
800
|
+
if (index >= count) {
|
|
801
|
+
index = count - 1;
|
|
802
|
+
moved = true;
|
|
803
|
+
}
|
|
804
|
+
rowId = index >= 0 ? model.pinnedRowAt(section, index)?.id : void 0;
|
|
412
805
|
}
|
|
413
|
-
if (index
|
|
806
|
+
if (index < 0) {
|
|
807
|
+
({section, index} = HEADER);
|
|
808
|
+
rowId = void 0;
|
|
809
|
+
}
|
|
810
|
+
if (moved && this.matches(":focus-within")) this.#focusPending = true;
|
|
811
|
+
if (section !== prev.section || index !== prev.index || rowId !== prev.rowId || col !== prev.col || !this.#active) this.#active = {
|
|
812
|
+
section,
|
|
414
813
|
index,
|
|
415
814
|
rowId,
|
|
416
815
|
col
|
|
@@ -432,20 +831,145 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
432
831
|
}
|
|
433
832
|
/** Sets one column's width (clamped by core), through the controlled/uncontrolled rules. */
|
|
434
833
|
#requestResize(columnId, width) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
834
|
+
this.#requestResizes(/* @__PURE__ */ new Map([[columnId, width]]));
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Sets column widths (clamped by core) in one change, through the
|
|
838
|
+
* controlled/uncontrolled rules. One `argus-column-resize` per column that
|
|
839
|
+
* changes, each with the whole new sizing.
|
|
840
|
+
*/
|
|
841
|
+
#requestResizes(widths) {
|
|
842
|
+
const model = this.#table.model;
|
|
438
843
|
const old = this.#sizing.value;
|
|
439
|
-
const
|
|
440
|
-
const
|
|
844
|
+
const targets = [];
|
|
845
|
+
for (const [columnId, width] of widths) {
|
|
846
|
+
const target = model.nextSizing(columnId, width)?.[columnId];
|
|
847
|
+
if (target !== void 0 && target !== old[columnId]) targets.push([columnId, target]);
|
|
848
|
+
}
|
|
849
|
+
if (targets.length === 0) return;
|
|
850
|
+
const { value, applied } = this.#sizing.request({
|
|
851
|
+
...old,
|
|
852
|
+
...Object.fromEntries(targets)
|
|
853
|
+
});
|
|
854
|
+
const details = targets.map(([columnId, width]) => ({
|
|
441
855
|
columnId,
|
|
442
|
-
width
|
|
856
|
+
width,
|
|
443
857
|
sizing: value
|
|
444
|
-
};
|
|
858
|
+
}));
|
|
445
859
|
if (applied) {
|
|
446
|
-
this.#
|
|
860
|
+
for (const detail of this.#appliedResizes.values()) detail.sizing = value;
|
|
861
|
+
for (const detail of details) this.#appliedResizes.set(detail.columnId, detail);
|
|
447
862
|
this.requestUpdate("columnSizing", old);
|
|
448
|
-
} else fire(this, COLUMN_RESIZE, detail);
|
|
863
|
+
} else for (const detail of details) fire(this, COLUMN_RESIZE, detail);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Moves a column to position `to` in the whole order (within its pin group),
|
|
867
|
+
* through the controlled/uncontrolled rules. Starts from the latest requested
|
|
868
|
+
* order, so moves can come faster than renders. `false` if it can't move there.
|
|
869
|
+
*/
|
|
870
|
+
#requestReorder(columnId, to) {
|
|
871
|
+
const old = this.#order.value;
|
|
872
|
+
const next = this.#table.model.nextOrder(columnId, to, old);
|
|
873
|
+
if (!next) return false;
|
|
874
|
+
const { value, applied } = this.#order.request(next);
|
|
875
|
+
if (applied) {
|
|
876
|
+
for (const detail of this.#appliedReorders.values()) detail.order = value;
|
|
877
|
+
this.#appliedReorders.set(columnId, {
|
|
878
|
+
columnId,
|
|
879
|
+
order: value
|
|
880
|
+
});
|
|
881
|
+
this.requestUpdate("columnOrder", old);
|
|
882
|
+
} else fire(this, COLUMN_REORDER, {
|
|
883
|
+
columnId,
|
|
884
|
+
order: value
|
|
885
|
+
});
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
/** Alt+Shift+Arrow on a header: one step toward the start (-1) or the end (1). */
|
|
889
|
+
#moveByKey(columnId, step) {
|
|
890
|
+
const model = this.#table.model;
|
|
891
|
+
const from = model.orderOf(this.#order.value).indexOf(columnId);
|
|
892
|
+
const view = model.columns.find((c) => c.id === columnId);
|
|
893
|
+
if (from < 0 || !view) return;
|
|
894
|
+
if (!this.#requestReorder(columnId, from + step)) return;
|
|
895
|
+
this.#focusPending = true;
|
|
896
|
+
this.#announceMove ??= {
|
|
897
|
+
id: columnId,
|
|
898
|
+
from: view.colIndex
|
|
899
|
+
};
|
|
900
|
+
this.requestUpdate();
|
|
901
|
+
}
|
|
902
|
+
/** Tells screen readers where a column moved from the keyboard went. */
|
|
903
|
+
#announceMoved(model, move) {
|
|
904
|
+
const view = model.columns.find((c) => c.id === move.id);
|
|
905
|
+
if (!view || view.colIndex === move.from) return;
|
|
906
|
+
const header = this.renderRoot.querySelector(`[role='columnheader'][data-col="${CSS.escape(move.id)}"] > slot`);
|
|
907
|
+
const name = header instanceof HTMLSlotElement ? header.assignedNodes({ flatten: true }).map((n) => n.textContent).join("").trim() : "";
|
|
908
|
+
announce(this.ownerDocument, `${name || move.id} moved to column ${String(view.colIndex)} of ${String(model.columns.length)}`);
|
|
909
|
+
}
|
|
910
|
+
/** A press on a reorderable header: becomes a drag once the pointer moves. */
|
|
911
|
+
#startReorder(down) {
|
|
912
|
+
const cell = down.composedPath().find((t) => t instanceof HTMLElement && t.getAttribute("role") === "columnheader");
|
|
913
|
+
const columnId = cell?.dataset.moveCol;
|
|
914
|
+
if (!cell || columnId === void 0 || !canStartReorder(down, cell)) return;
|
|
915
|
+
const scroller = this.renderRoot.querySelector("[part~=\"table\"]");
|
|
916
|
+
if (!scroller) return;
|
|
917
|
+
const pin = this.#table.model.columns.find((c) => c.id === columnId)?.pin;
|
|
918
|
+
startReorderDrag(cell, down, {
|
|
919
|
+
scroller,
|
|
920
|
+
targetAt: (x) => dropTarget(this.#dropSlots(pin), columnId, x, this.#direction()),
|
|
921
|
+
scrollArea: () => pin === void 0 ? this.#scrollArea(scroller) : void 0,
|
|
922
|
+
onStart: () => {
|
|
923
|
+
this.#drag = {
|
|
924
|
+
columnId,
|
|
925
|
+
target: void 0
|
|
926
|
+
};
|
|
927
|
+
this.requestUpdate();
|
|
928
|
+
},
|
|
929
|
+
onTarget: (target) => {
|
|
930
|
+
this.#drag = {
|
|
931
|
+
columnId,
|
|
932
|
+
target
|
|
933
|
+
};
|
|
934
|
+
this.requestUpdate();
|
|
935
|
+
},
|
|
936
|
+
onDrop: (target) => {
|
|
937
|
+
this.#drag = void 0;
|
|
938
|
+
if (target && this.#requestReorder(columnId, target.to)) {
|
|
939
|
+
if (this.matches(":focus-within")) this.#focusPending = true;
|
|
940
|
+
}
|
|
941
|
+
this.requestUpdate();
|
|
942
|
+
},
|
|
943
|
+
onCancel: () => {
|
|
944
|
+
this.#drag = void 0;
|
|
945
|
+
this.requestUpdate();
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
/** The rendered headers of one pin group, with their positions in the whole order. */
|
|
950
|
+
#dropSlots(pin) {
|
|
951
|
+
const slots = [];
|
|
952
|
+
this.#table.model.columns.forEach((c, index) => {
|
|
953
|
+
if (c.pin !== pin) return;
|
|
954
|
+
const cell = this.renderRoot.querySelector(`[role='columnheader'][data-col="${CSS.escape(c.id)}"]`);
|
|
955
|
+
if (cell) slots.push({
|
|
956
|
+
id: c.id,
|
|
957
|
+
index,
|
|
958
|
+
rect: cell.getBoundingClientRect()
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
return slots;
|
|
962
|
+
}
|
|
963
|
+
/** Where the scrolling columns show: the scroller's inside, less the pinned columns. */
|
|
964
|
+
#scrollArea(scroller) {
|
|
965
|
+
const columns = this.#table.model.columns;
|
|
966
|
+
const width = (side) => columns.reduce((sum, c) => c.pin === side ? sum + c.width : sum, 0);
|
|
967
|
+
const ltr = this.#direction() === 1;
|
|
968
|
+
const left = scroller.getBoundingClientRect().left + scroller.clientLeft;
|
|
969
|
+
return {
|
|
970
|
+
left: left + width(ltr ? "start" : "end"),
|
|
971
|
+
right: left + scroller.clientWidth - width(ltr ? "end" : "start")
|
|
972
|
+
};
|
|
449
973
|
}
|
|
450
974
|
/** asc → desc → unsorted, through the controlled/uncontrolled rules. */
|
|
451
975
|
#requestSort(columnId) {
|
|
@@ -475,17 +999,20 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
475
999
|
}
|
|
476
1000
|
}
|
|
477
1001
|
};
|
|
478
|
-
__decorate([property({ attribute: false })], ArgusTable.prototype, "columns",
|
|
479
|
-
__decorate([property({ attribute: false })], ArgusTable.prototype, "data",
|
|
1002
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columns", null);
|
|
1003
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "data", null);
|
|
1004
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "dataSource", void 0);
|
|
1005
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "pinnedRows", void 0);
|
|
480
1006
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "getRowId", void 0);
|
|
481
1007
|
__decorate([property({
|
|
482
1008
|
attribute: "row-height",
|
|
483
1009
|
type: Number
|
|
484
1010
|
})], ArgusTable.prototype, "rowHeight", void 0);
|
|
485
1011
|
__decorate([property({ reflect: true })], ArgusTable.prototype, "density", void 0);
|
|
486
|
-
__decorate([property({ type: Number })], ArgusTable.prototype, "overscan",
|
|
1012
|
+
__decorate([property({ type: Number })], ArgusTable.prototype, "overscan", null);
|
|
487
1013
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "sorting", null);
|
|
488
1014
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnSizing", null);
|
|
1015
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnOrder", null);
|
|
489
1016
|
ArgusTable = __decorate([customElement("argus-table")], ArgusTable);
|
|
490
1017
|
export { ArgusTable };
|
|
491
1018
|
|