argus-table 0.1.0 → 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 +585 -83
- package/dist/argus-table.js.map +1 -1
- package/dist/cdn/argus-table.js +79 -4533
- 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/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 +1463 -62
- 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 -20
- 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 -4
- 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 -1
- package/dist/styles/base.js.map +1 -1
- package/package.json +2 -1
package/dist/argus-table.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
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";
|
|
2
7
|
import { PinScrollController } from "./controllers/pin-scroll-controller.js";
|
|
8
|
+
import { canStartReorder, dropTarget, startReorderDrag } from "./controllers/reorder-drag.js";
|
|
3
9
|
import { startResizeDrag } from "./controllers/resize-drag.js";
|
|
4
10
|
import { TableController } from "./controllers/table-controller.js";
|
|
5
11
|
import { VirtualController } from "./controllers/virtual-controller.js";
|
|
6
|
-
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";
|
|
7
14
|
import { renderBody } from "./render/body.js";
|
|
8
15
|
import { renderLightDom } from "./render/cells.js";
|
|
9
16
|
import { renderHeader } from "./render/header.js";
|
|
17
|
+
import { renderPinnedRows } from "./render/pinned-rows.js";
|
|
10
18
|
import { baseStyles } from "./styles/base.js";
|
|
11
19
|
import __decorate from "./_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.js";
|
|
12
20
|
import { LitElement, html, nothing } from "lit";
|
|
@@ -37,15 +45,24 @@ function sortColumnOf(event) {
|
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
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
|
+
}
|
|
40
56
|
/** The grid cell or column header an element is, if any. */
|
|
41
57
|
function gridCell(target) {
|
|
42
58
|
if (!(target instanceof HTMLElement) || target.dataset.col === void 0) return void 0;
|
|
43
59
|
const role = target.getAttribute("role");
|
|
44
60
|
return role === "gridcell" || role === "columnheader" ? target : void 0;
|
|
45
61
|
}
|
|
46
|
-
/**
|
|
47
|
-
function
|
|
48
|
-
|
|
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;
|
|
49
66
|
}
|
|
50
67
|
/**
|
|
51
68
|
* The pinned edge's shadow, drawn once per side over the scroller rather than
|
|
@@ -72,14 +89,27 @@ function track(column) {
|
|
|
72
89
|
var ArgusTable = class ArgusTable extends LitElement {
|
|
73
90
|
constructor(..._args) {
|
|
74
91
|
super(..._args);
|
|
75
|
-
this.columns = [];
|
|
76
|
-
this.data = [];
|
|
77
92
|
this.density = "comfortable";
|
|
78
|
-
this
|
|
93
|
+
this.#columnsProp = [];
|
|
94
|
+
this.#dataProp = [];
|
|
95
|
+
this.#overscan = 6;
|
|
79
96
|
this.#sorting = controllable([]);
|
|
80
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;
|
|
81
105
|
this.#internals = attachGridInternals(this);
|
|
82
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;
|
|
83
113
|
this.#virtual = new VirtualController(this, () => this.renderRoot.querySelector("[part~=\"table\"]"));
|
|
84
114
|
this.#pinScroll = new PinScrollController(this, () => this.renderRoot.querySelector("[part~=\"table\"]"));
|
|
85
115
|
this.#rowPx = 40;
|
|
@@ -90,6 +120,11 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
90
120
|
};
|
|
91
121
|
this.#focusPending = false;
|
|
92
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
|
+
};
|
|
93
128
|
this.#headerHandlers = {
|
|
94
129
|
click: (event) => {
|
|
95
130
|
const id = sortColumnOf(event);
|
|
@@ -101,18 +136,28 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
101
136
|
if (!(handle instanceof HTMLElement)) return;
|
|
102
137
|
const id = handle.dataset.resizeCol;
|
|
103
138
|
const cell = handle.parentElement;
|
|
104
|
-
if (id
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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]));
|
|
108
153
|
}
|
|
109
154
|
};
|
|
110
155
|
this.#onKeydown = (event) => {
|
|
111
156
|
const cell = gridCell(event.composedPath()[0] ?? event.target ?? this);
|
|
112
157
|
const id = cell?.dataset.col;
|
|
113
158
|
if (!cell || id === void 0) return;
|
|
159
|
+
const scroller = event.currentTarget;
|
|
114
160
|
if (event.key === "Tab" && event.shiftKey) {
|
|
115
|
-
const scroller = event.currentTarget;
|
|
116
161
|
scroller.tabIndex = -1;
|
|
117
162
|
cell.addEventListener("focusout", () => {
|
|
118
163
|
scroller.removeAttribute("tabindex");
|
|
@@ -121,7 +166,14 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
121
166
|
}
|
|
122
167
|
const inHeader = cell.getAttribute("role") === "columnheader";
|
|
123
168
|
const { key } = event;
|
|
124
|
-
|
|
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) {
|
|
125
177
|
event.preventDefault();
|
|
126
178
|
const towardEnd = key === "ArrowRight" === (this.#direction() === 1);
|
|
127
179
|
this.#requestResize(id, this.#currentWidth(id, cell) + (towardEnd ? RESIZE_STEP : -10));
|
|
@@ -132,22 +184,41 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
132
184
|
this.#requestSort(id);
|
|
133
185
|
return;
|
|
134
186
|
}
|
|
135
|
-
|
|
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;
|
|
136
193
|
if (!inHeader && key === "Enter") {
|
|
137
194
|
event.preventDefault();
|
|
138
|
-
this.#
|
|
195
|
+
if (from.section === "body" && this.#data.stateAt(from.index) === "error") this.#data.retry(from.index);
|
|
196
|
+
else this.#activate(event.composedPath());
|
|
139
197
|
return;
|
|
140
198
|
}
|
|
141
|
-
const model = this.#table.model;
|
|
142
199
|
const columns = model.columns;
|
|
143
|
-
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);
|
|
144
209
|
let col = columns.findIndex((c) => c.id === id);
|
|
145
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;
|
|
146
217
|
case "ArrowDown":
|
|
147
|
-
|
|
218
|
+
pos = Math.min(pos + 1, last);
|
|
148
219
|
break;
|
|
149
220
|
case "ArrowUp":
|
|
150
|
-
|
|
221
|
+
pos = Math.max(pos - 1, 0);
|
|
151
222
|
break;
|
|
152
223
|
case "ArrowRight":
|
|
153
224
|
col += this.#direction();
|
|
@@ -157,13 +228,21 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
157
228
|
break;
|
|
158
229
|
case "Home":
|
|
159
230
|
col = 0;
|
|
231
|
+
if (corner) {
|
|
232
|
+
pos = 0;
|
|
233
|
+
scroller.scrollTop = 0;
|
|
234
|
+
}
|
|
160
235
|
break;
|
|
161
236
|
case "End":
|
|
162
237
|
col = columns.length - 1;
|
|
238
|
+
if (corner) pos = last;
|
|
163
239
|
break;
|
|
164
240
|
default: return;
|
|
165
241
|
}
|
|
166
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;
|
|
167
246
|
const target = columns[Math.min(Math.max(col, 0), columns.length - 1)];
|
|
168
247
|
if (target) this.#moveTo(row, target.id, model);
|
|
169
248
|
};
|
|
@@ -180,12 +259,14 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
180
259
|
for (const target of event.composedPath()) {
|
|
181
260
|
const cell = gridCell(target);
|
|
182
261
|
if (cell?.dataset.col !== void 0) {
|
|
183
|
-
const
|
|
262
|
+
const model = this.#table.model;
|
|
263
|
+
const row = cell.getAttribute("role") === "columnheader" ? HEADER : gridRowOf(cell.parentElement, layoutOf(model));
|
|
184
264
|
const { col } = cell.dataset;
|
|
185
|
-
|
|
265
|
+
const prev = this.#active;
|
|
266
|
+
if (row && (row.section !== prev?.section || row.index !== prev.index || col !== prev.col)) {
|
|
186
267
|
this.#active = {
|
|
187
|
-
|
|
188
|
-
rowId: this.#
|
|
268
|
+
...row,
|
|
269
|
+
rowId: this.#gridRowId(model, row),
|
|
189
270
|
col
|
|
190
271
|
};
|
|
191
272
|
this.requestUpdate();
|
|
@@ -203,6 +284,34 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
203
284
|
this.styles = baseStyles;
|
|
204
285
|
}
|
|
205
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
|
+
/**
|
|
206
315
|
* The sort order, e.g. `[{ id: 'total', desc: true }]`; `[]` when unsorted. Unset,
|
|
207
316
|
* the table keeps it itself and header clicks change it. Once set, it is
|
|
208
317
|
* controlled: a click only fires `argus-sort-change` with the proposed order,
|
|
@@ -225,14 +334,54 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
225
334
|
set columnSizing(value) {
|
|
226
335
|
this.#sizing.set(value);
|
|
227
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;
|
|
228
355
|
#sorting;
|
|
229
356
|
#sizing;
|
|
230
|
-
|
|
231
|
-
|
|
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;
|
|
232
371
|
/** A sort the table applied itself; its event fires once the update is done. */
|
|
233
372
|
#appliedSort;
|
|
234
373
|
#internals;
|
|
235
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;
|
|
236
385
|
#virtual;
|
|
237
386
|
#pinScroll;
|
|
238
387
|
#rowPx;
|
|
@@ -242,14 +391,69 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
242
391
|
/** Move focus to the active cell after the next update (keyboard navigation). */
|
|
243
392
|
#focusPending;
|
|
244
393
|
#lastRange;
|
|
394
|
+
#unwatchMaxHeight;
|
|
245
395
|
connectedCallback() {
|
|
246
396
|
super.connectedCallback();
|
|
247
397
|
if (!this.hasAttribute("role")) this.setAttribute("role", "grid");
|
|
248
398
|
if (this.hasUpdated) this.#observeProbe();
|
|
399
|
+
this.#unwatchMaxHeight = watchMaxElementHeight(() => {
|
|
400
|
+
this.requestUpdate();
|
|
401
|
+
});
|
|
249
402
|
}
|
|
250
403
|
disconnectedCallback() {
|
|
251
404
|
super.disconnectedCallback();
|
|
252
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;
|
|
253
457
|
}
|
|
254
458
|
/** Scrolls so that the row at `index` (in the current row order) is at the top. */
|
|
255
459
|
async scrollToRow(index) {
|
|
@@ -258,24 +462,51 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
258
462
|
await this.updateComplete;
|
|
259
463
|
}
|
|
260
464
|
willUpdate(changed) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
267
494
|
});
|
|
268
495
|
const model = this.#table.model;
|
|
269
496
|
this.#reconcileActive(model);
|
|
270
497
|
const active = this.#active;
|
|
271
|
-
|
|
498
|
+
const unknownTotal = this.#data.rows?.totalKnown === false;
|
|
499
|
+
const layout = layoutOf(model);
|
|
500
|
+
this.#internals.ariaRowCount = unknownTotal ? "-1" : String(layout.total);
|
|
272
501
|
this.#internals.ariaColCount = String(model.columns.length);
|
|
502
|
+
const extra = (1 + layout.top + layout.bottom) * this.#rowPx;
|
|
273
503
|
this.#virtual.configure({
|
|
274
504
|
count: model.rowCount,
|
|
275
505
|
rowHeight: this.#rowPx,
|
|
506
|
+
cap: bodyCap(model.rowCount * this.#rowPx, extra),
|
|
276
507
|
overscan: Math.max(0, this.overscan),
|
|
277
|
-
rowIdAt: (index) => model
|
|
278
|
-
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 : {
|
|
279
510
|
id: active.rowId,
|
|
280
511
|
index: active.index
|
|
281
512
|
}
|
|
@@ -287,49 +518,75 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
287
518
|
this.#window = this.#virtual.computeWindow();
|
|
288
519
|
const pinnedWidth = (side) => columns.reduce((sum, c) => c.pin === side ? sum + c.width : sum, 0);
|
|
289
520
|
const pinned = (side) => `${String(pinnedWidth(side))}px`;
|
|
521
|
+
const layout = layoutOf(model);
|
|
290
522
|
const style = {
|
|
291
523
|
"--_cols": columns.map(track).join(" "),
|
|
292
524
|
"--_width": `${String(model.totalWidth)}px`,
|
|
293
525
|
"--_pin-start": pinned("start"),
|
|
294
|
-
"--_pin-end": pinned("end")
|
|
526
|
+
"--_pin-end": pinned("end"),
|
|
527
|
+
"--_pin-top-rows": String(layout.top),
|
|
528
|
+
"--_pin-bottom-rows": String(layout.bottom)
|
|
295
529
|
};
|
|
296
530
|
const active = this.#active;
|
|
297
|
-
const
|
|
531
|
+
const activeIn = (section) => active?.section === section ? {
|
|
298
532
|
index: active.index,
|
|
299
533
|
col: active.col
|
|
300
534
|
} : void 0;
|
|
535
|
+
const firstIndex = (section) => layout.ariaRowIndex({
|
|
536
|
+
section,
|
|
537
|
+
index: 0
|
|
538
|
+
});
|
|
301
539
|
if (this.rowHeight !== void 0 && this.rowHeight > 0) style["--_row-height"] = `${String(this.rowHeight)}px`;
|
|
302
540
|
return html`<div
|
|
303
541
|
part="table"
|
|
304
542
|
role="presentation"
|
|
305
543
|
style=${styleMap(style)}
|
|
544
|
+
?data-dragging=${this.#drag !== void 0}
|
|
306
545
|
@keydown=${this.#onKeydown}
|
|
307
546
|
@focusin=${this.#onFocusin}
|
|
308
547
|
@click=${this.#onClick}
|
|
309
548
|
>
|
|
310
|
-
${renderHeader(columns, this.#headerHandlers,
|
|
311
|
-
${
|
|
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"))}
|
|
312
553
|
<div class="probe" aria-hidden="true"></div>
|
|
313
554
|
</div>
|
|
314
|
-
${pinShadow("start", pinnedWidth("start"),
|
|
315
|
-
${pinShadow("end", pinnedWidth("end"),
|
|
555
|
+
${pinShadow("start", pinnedWidth("start"), layout.total - 1, this.#rowPx)}
|
|
556
|
+
${pinShadow("end", pinnedWidth("end"), layout.total - 1, this.#rowPx)}`;
|
|
316
557
|
}
|
|
317
558
|
firstUpdated() {
|
|
318
559
|
this.#observeProbe();
|
|
319
560
|
this.#measureRowHeight();
|
|
320
561
|
}
|
|
321
562
|
updated(changed) {
|
|
322
|
-
|
|
323
|
-
|
|
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();
|
|
324
572
|
if (this.#focusPending) {
|
|
325
573
|
this.#focusPending = false;
|
|
326
574
|
this.#focusActive();
|
|
327
575
|
}
|
|
328
576
|
if (changed.has("density") || changed.has("rowHeight")) this.#measureRowHeight();
|
|
329
|
-
if (this.#
|
|
330
|
-
const
|
|
331
|
-
this.#
|
|
332
|
-
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);
|
|
333
590
|
}
|
|
334
591
|
if (this.#appliedSort) {
|
|
335
592
|
const sorting = this.#appliedSort;
|
|
@@ -345,11 +602,73 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
345
602
|
end: range.end
|
|
346
603
|
});
|
|
347
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);
|
|
348
665
|
}
|
|
349
666
|
#headerHandlers;
|
|
350
667
|
/**
|
|
351
|
-
* Keys on a focused cell or header
|
|
352
|
-
* 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.
|
|
353
672
|
*/
|
|
354
673
|
#onKeydown;
|
|
355
674
|
/** Focus that lands in a cell (a click, or content inside it) makes that cell active. */
|
|
@@ -358,7 +677,8 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
358
677
|
#onClick;
|
|
359
678
|
/**
|
|
360
679
|
* Fires `argus-cell-click` (when a cell is on the path), then `argus-row-click`,
|
|
361
|
-
* 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.
|
|
362
682
|
*/
|
|
363
683
|
#activate(path) {
|
|
364
684
|
let columnId;
|
|
@@ -368,27 +688,55 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
368
688
|
if (role === "gridcell") columnId = target.dataset.col;
|
|
369
689
|
if (role !== "row") continue;
|
|
370
690
|
const model = this.#table.model;
|
|
371
|
-
const
|
|
372
|
-
|
|
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);
|
|
373
711
|
if (!row) return;
|
|
374
712
|
if (columnId !== void 0) fire(this, CELL_CLICK, {
|
|
375
713
|
row: row.original,
|
|
376
714
|
columnId,
|
|
377
|
-
value: model.
|
|
715
|
+
value: model.pinnedValueAt(pinned, index, columnId),
|
|
716
|
+
pinned
|
|
378
717
|
});
|
|
379
718
|
fire(this, ROW_CLICK, {
|
|
380
719
|
row: row.original,
|
|
381
720
|
rowId: row.id,
|
|
382
|
-
index
|
|
721
|
+
index,
|
|
722
|
+
pinned
|
|
383
723
|
});
|
|
384
724
|
return;
|
|
385
725
|
}
|
|
386
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
|
+
}
|
|
387
735
|
/** Makes a cell active and focuses it after the update. The keep-rendered rule renders its row. */
|
|
388
|
-
#moveTo(
|
|
736
|
+
#moveTo(row, col, model) {
|
|
389
737
|
this.#active = {
|
|
390
|
-
|
|
391
|
-
rowId:
|
|
738
|
+
...row,
|
|
739
|
+
rowId: this.#gridRowId(model, row),
|
|
392
740
|
col
|
|
393
741
|
};
|
|
394
742
|
this.#focusPending = true;
|
|
@@ -398,20 +746,30 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
398
746
|
const active = this.#active;
|
|
399
747
|
if (!active) return;
|
|
400
748
|
const col = CSS.escape(active.col);
|
|
401
|
-
const
|
|
402
|
-
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}"]`);
|
|
403
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
|
+
}
|
|
404
761
|
cell.scrollIntoView({
|
|
405
762
|
block: "nearest",
|
|
406
763
|
inline: "nearest"
|
|
407
764
|
});
|
|
408
|
-
cell.focus({ preventScroll: true });
|
|
409
765
|
}
|
|
410
766
|
/**
|
|
411
|
-
* Keeps the active cell valid after columns or rows change
|
|
412
|
-
* when it still exists (it may have moved), else at the same
|
|
413
|
-
*
|
|
414
|
-
*
|
|
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.
|
|
415
773
|
*/
|
|
416
774
|
#reconcileActive(model) {
|
|
417
775
|
const columns = model.columns;
|
|
@@ -421,21 +779,37 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
421
779
|
return;
|
|
422
780
|
}
|
|
423
781
|
const prev = this.#active ?? {
|
|
424
|
-
|
|
782
|
+
...HEADER,
|
|
425
783
|
rowId: void 0,
|
|
426
784
|
col: first.id
|
|
427
785
|
};
|
|
428
786
|
const col = columns.some((c) => c.id === prev.col) ? prev.col : first.id;
|
|
429
|
-
let { index, rowId } = prev;
|
|
430
|
-
|
|
787
|
+
let { section, index, rowId } = prev;
|
|
788
|
+
let moved = false;
|
|
789
|
+
if (section === "body" && rowId !== void 0) {
|
|
431
790
|
const found = model.indexOf(rowId);
|
|
432
|
-
|
|
791
|
+
const unloaded = index < model.rowCount && model.rowAt(index) === void 0;
|
|
792
|
+
if (found !== void 0) index = found;
|
|
793
|
+
else if (!unloaded) {
|
|
433
794
|
index = Math.min(index, model.rowCount - 1);
|
|
434
|
-
rowId = model.rowAt(index)?.id;
|
|
435
|
-
|
|
436
|
-
}
|
|
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;
|
|
805
|
+
}
|
|
806
|
+
if (index < 0) {
|
|
807
|
+
({section, index} = HEADER);
|
|
808
|
+
rowId = void 0;
|
|
437
809
|
}
|
|
438
|
-
if (
|
|
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,
|
|
439
813
|
index,
|
|
440
814
|
rowId,
|
|
441
815
|
col
|
|
@@ -457,20 +831,145 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
457
831
|
}
|
|
458
832
|
/** Sets one column's width (clamped by core), through the controlled/uncontrolled rules. */
|
|
459
833
|
#requestResize(columnId, width) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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;
|
|
463
843
|
const old = this.#sizing.value;
|
|
464
|
-
const
|
|
465
|
-
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]) => ({
|
|
466
855
|
columnId,
|
|
467
|
-
width
|
|
856
|
+
width,
|
|
468
857
|
sizing: value
|
|
469
|
-
};
|
|
858
|
+
}));
|
|
470
859
|
if (applied) {
|
|
471
|
-
this.#
|
|
860
|
+
for (const detail of this.#appliedResizes.values()) detail.sizing = value;
|
|
861
|
+
for (const detail of details) this.#appliedResizes.set(detail.columnId, detail);
|
|
472
862
|
this.requestUpdate("columnSizing", old);
|
|
473
|
-
} 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
|
+
};
|
|
474
973
|
}
|
|
475
974
|
/** asc → desc → unsorted, through the controlled/uncontrolled rules. */
|
|
476
975
|
#requestSort(columnId) {
|
|
@@ -500,17 +999,20 @@ var ArgusTable = class ArgusTable extends LitElement {
|
|
|
500
999
|
}
|
|
501
1000
|
}
|
|
502
1001
|
};
|
|
503
|
-
__decorate([property({ attribute: false })], ArgusTable.prototype, "columns",
|
|
504
|
-
__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);
|
|
505
1006
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "getRowId", void 0);
|
|
506
1007
|
__decorate([property({
|
|
507
1008
|
attribute: "row-height",
|
|
508
1009
|
type: Number
|
|
509
1010
|
})], ArgusTable.prototype, "rowHeight", void 0);
|
|
510
1011
|
__decorate([property({ reflect: true })], ArgusTable.prototype, "density", void 0);
|
|
511
|
-
__decorate([property({ type: Number })], ArgusTable.prototype, "overscan",
|
|
1012
|
+
__decorate([property({ type: Number })], ArgusTable.prototype, "overscan", null);
|
|
512
1013
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "sorting", null);
|
|
513
1014
|
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnSizing", null);
|
|
1015
|
+
__decorate([property({ attribute: false })], ArgusTable.prototype, "columnOrder", null);
|
|
514
1016
|
ArgusTable = __decorate([customElement("argus-table")], ArgusTable);
|
|
515
1017
|
export { ArgusTable };
|
|
516
1018
|
|