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.
Files changed (59) hide show
  1. package/README.md +25 -2
  2. package/dist/announce.js +27 -0
  3. package/dist/announce.js.map +1 -0
  4. package/dist/argus-column.js +121 -0
  5. package/dist/argus-column.js.map +1 -0
  6. package/dist/argus-table.js +618 -91
  7. package/dist/argus-table.js.map +1 -1
  8. package/dist/cdn/argus-table.js +87 -4715
  9. package/dist/cdn/argus-table.js.map +1 -1
  10. package/dist/controllers/auto-width.js +89 -0
  11. package/dist/controllers/auto-width.js.map +1 -0
  12. package/dist/controllers/column-children.js +41 -0
  13. package/dist/controllers/column-children.js.map +1 -0
  14. package/dist/controllers/data-controller.js +177 -0
  15. package/dist/controllers/data-controller.js.map +1 -0
  16. package/dist/controllers/pin-scroll-controller.js +78 -0
  17. package/dist/controllers/pin-scroll-controller.js.map +1 -0
  18. package/dist/controllers/reorder-drag.js +160 -0
  19. package/dist/controllers/reorder-drag.js.map +1 -0
  20. package/dist/controllers/resize-drag.js +3 -0
  21. package/dist/controllers/resize-drag.js.map +1 -1
  22. package/dist/controllers/table-controller.js +2 -1
  23. package/dist/controllers/table-controller.js.map +1 -1
  24. package/dist/controllers/virtual-controller.js +146 -9
  25. package/dist/controllers/virtual-controller.js.map +1 -1
  26. package/dist/core/src/columns/column-order.js +50 -0
  27. package/dist/core/src/columns/column-order.js.map +1 -0
  28. package/dist/core/src/columns/resolve-columns.js +5 -1
  29. package/dist/core/src/columns/resolve-columns.js.map +1 -1
  30. package/dist/core/src/data/page-cache.js +143 -0
  31. package/dist/core/src/data/page-cache.js.map +1 -0
  32. package/dist/core/src/grid/row-layout.js +58 -0
  33. package/dist/core/src/grid/row-layout.js.map +1 -0
  34. package/dist/core/src/table/create-table.js +112 -14
  35. package/dist/core/src/table/create-table.js.map +1 -1
  36. package/dist/core/src/table/sorted-row-model.js +86 -0
  37. package/dist/core/src/table/sorted-row-model.js.map +1 -0
  38. package/dist/core/src/virtual/scroll-scale.js +74 -0
  39. package/dist/core/src/virtual/scroll-scale.js.map +1 -0
  40. package/dist/custom-elements.json +1510 -49
  41. package/dist/events.js +5 -1
  42. package/dist/events.js.map +1 -1
  43. package/dist/index.d.ts +252 -27
  44. package/dist/index.js +3 -2
  45. package/dist/max-height.js +68 -0
  46. package/dist/max-height.js.map +1 -0
  47. package/dist/render/body.js +53 -21
  48. package/dist/render/body.js.map +1 -1
  49. package/dist/render/cells.js +21 -4
  50. package/dist/render/cells.js.map +1 -1
  51. package/dist/render/content.js +12 -5
  52. package/dist/render/content.js.map +1 -1
  53. package/dist/render/header.js +21 -5
  54. package/dist/render/header.js.map +1 -1
  55. package/dist/render/pinned-rows.js +29 -0
  56. package/dist/render/pinned-rows.js.map +1 -0
  57. package/dist/styles/base.js +1 -225
  58. package/dist/styles/base.js.map +1 -1
  59. package/package.json +3 -1
@@ -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
- /** Row index of a rendered row element (`aria-rowindex` minus the header); `-1` for the header. */
46
- function rowIndexOf(row) {
47
- return Number(row?.getAttribute("aria-rowindex") ?? 1) - 2;
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.overscan = 6;
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 === void 0 || !cell) return;
85
- startResizeDrag(handle, event, cell.getBoundingClientRect().width, this.#direction(), (w) => {
86
- this.#requestResize(id, w);
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
- if (inHeader && event.altKey && (key === "ArrowLeft" || key === "ArrowRight")) {
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
- if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
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.#activate(event.composedPath());
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 row = inHeader ? -1 : rowIndexOf(cell.parentElement);
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
- row = Math.min(row + 1, model.rowCount - 1);
218
+ pos = Math.min(pos + 1, last);
128
219
  break;
129
220
  case "ArrowUp":
130
- row = Math.max(row - 1, -1);
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 index = cell.getAttribute("role") === "columnheader" ? -1 : rowIndexOf(cell.parentElement);
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
- if (index !== this.#active?.index || col !== this.#active.col) {
265
+ const prev = this.#active;
266
+ if (row && (row.section !== prev?.section || row.index !== prev.index || col !== prev.col)) {
166
267
  this.#active = {
167
- index,
168
- rowId: this.#table.model.rowAt(index)?.id,
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
- /** A resize the table applied itself; its event fires once the update is done. */
211
- #appliedResize;
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
- if (changed.has("columns") || changed.has("data") || changed.has("getRowId") || changed.has("sorting") || changed.has("columnSizing")) this.#table.update({
241
- columns: this.columns,
242
- data: this.data,
243
- getRowId: this.getRowId,
244
- sorting: this.#sorting.value,
245
- columnSizing: this.#sizing.value
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
- this.#internals.ariaRowCount = String(model.rowCount + 1);
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.rowAt(index)?.id ?? String(index),
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 pinned = (side) => `${String(columns.reduce((sum, c) => c.pin === side ? sum + c.width : sum, 0))}px`;
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 bodyActive = active && active.index >= 0 ? {
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
- part="table"
282
- role="presentation"
283
- style=${styleMap(style)}
284
- @keydown=${this.#onKeydown}
285
- @focusin=${this.#onFocusin}
286
- @click=${this.#onClick}
287
- >
288
- ${renderHeader(columns, this.#headerHandlers, active?.index === -1 ? active.col : void 0)}
289
- ${model.rowCount === 0 ? html`<div part="empty"><slot name="empty">No rows</slot></div>` : renderBody(this.#window.rows, columns, this.#window.totalSize, bodyActive)}
290
- <div class="probe" aria-hidden="true"></div>
291
- </div>`;
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
- renderLightDom(this, this.#table.model, this.#window.rows);
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.#appliedResize) {
305
- const detail = this.#appliedResize;
306
- this.#appliedResize = void 0;
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. Keys from content inside a cell (an input,
327
- * a button) are left to that content.
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 body cell use it.
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 index = rowIndexOf(target);
347
- const row = index >= 0 ? model.rowAt(index) : void 0;
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.valueAt(index, columnId)
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(index, col, model) {
736
+ #moveTo(row, col, model) {
364
737
  this.#active = {
365
- index,
366
- rowId: index >= 0 ? model.rowAt(index)?.id : void 0,
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 selector = active.index === -1 ? `[part~='header-row'] > [data-col="${col}"]` : `[part~='body'] > [aria-rowindex="${String(active.index + 2)}"] > [data-col="${col}"]`;
377
- const cell = this.renderRoot.querySelector(selector);
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: on the same row id
387
- * when it still exists (it may have moved), else at the same index, clamped.
388
- * Defaults to the first header. If its row went away while the grid had focus,
389
- * focus follows to the replacement.
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
- index: -1,
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
- if (rowId !== void 0) {
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
- if (found === void 0) {
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
- if (this.matches(":focus-within")) this.#focusPending = true;
411
- } else index = found;
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 !== prev.index || rowId !== prev.rowId || col !== prev.col || !this.#active) this.#active = {
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
- const next = this.#table.model.nextSizing(columnId, width);
436
- const target = next?.[columnId];
437
- if (!next || target === void 0 || target === this.#sizing.value[columnId]) return;
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 { value, applied } = this.#sizing.request(next);
440
- const detail = {
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: target,
856
+ width,
443
857
  sizing: value
444
- };
858
+ }));
445
859
  if (applied) {
446
- this.#appliedResize = detail;
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", void 0);
479
- __decorate([property({ attribute: false })], ArgusTable.prototype, "data", void 0);
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", void 0);
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