argus-table 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -2
- package/dist/announce.js +27 -0
- package/dist/announce.js.map +1 -0
- package/dist/argus-column.js +121 -0
- package/dist/argus-column.js.map +1 -0
- package/dist/argus-table.js +618 -91
- package/dist/argus-table.js.map +1 -1
- package/dist/cdn/argus-table.js +87 -4715
- package/dist/cdn/argus-table.js.map +1 -1
- package/dist/controllers/auto-width.js +89 -0
- package/dist/controllers/auto-width.js.map +1 -0
- package/dist/controllers/column-children.js +41 -0
- package/dist/controllers/column-children.js.map +1 -0
- package/dist/controllers/data-controller.js +177 -0
- package/dist/controllers/data-controller.js.map +1 -0
- package/dist/controllers/pin-scroll-controller.js +78 -0
- package/dist/controllers/pin-scroll-controller.js.map +1 -0
- package/dist/controllers/reorder-drag.js +160 -0
- package/dist/controllers/reorder-drag.js.map +1 -0
- package/dist/controllers/resize-drag.js +3 -0
- package/dist/controllers/resize-drag.js.map +1 -1
- package/dist/controllers/table-controller.js +2 -1
- package/dist/controllers/table-controller.js.map +1 -1
- package/dist/controllers/virtual-controller.js +146 -9
- package/dist/controllers/virtual-controller.js.map +1 -1
- package/dist/core/src/columns/column-order.js +50 -0
- package/dist/core/src/columns/column-order.js.map +1 -0
- package/dist/core/src/columns/resolve-columns.js +5 -1
- package/dist/core/src/columns/resolve-columns.js.map +1 -1
- package/dist/core/src/data/page-cache.js +143 -0
- package/dist/core/src/data/page-cache.js.map +1 -0
- package/dist/core/src/grid/row-layout.js +58 -0
- package/dist/core/src/grid/row-layout.js.map +1 -0
- package/dist/core/src/table/create-table.js +112 -14
- package/dist/core/src/table/create-table.js.map +1 -1
- package/dist/core/src/table/sorted-row-model.js +86 -0
- package/dist/core/src/table/sorted-row-model.js.map +1 -0
- package/dist/core/src/virtual/scroll-scale.js +74 -0
- package/dist/core/src/virtual/scroll-scale.js.map +1 -0
- package/dist/custom-elements.json +1510 -49
- package/dist/events.js +5 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +252 -27
- package/dist/index.js +3 -2
- package/dist/max-height.js +68 -0
- package/dist/max-height.js.map +1 -0
- package/dist/render/body.js +53 -21
- package/dist/render/body.js.map +1 -1
- package/dist/render/cells.js +21 -4
- package/dist/render/cells.js.map +1 -1
- package/dist/render/content.js +12 -5
- package/dist/render/content.js.map +1 -1
- package/dist/render/header.js +21 -5
- package/dist/render/header.js.map +1 -1
- package/dist/render/pinned-rows.js +29 -0
- package/dist/render/pinned-rows.js.map +1 -0
- package/dist/styles/base.js +1 -225
- package/dist/styles/base.js.map +1 -1
- package/package.json +3 -1
package/dist/events.js
CHANGED
|
@@ -5,10 +5,14 @@ var RANGE_CHANGE = "argus-range-change";
|
|
|
5
5
|
var SORT_CHANGE = "argus-sort-change";
|
|
6
6
|
/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */
|
|
7
7
|
var COLUMN_RESIZE = "argus-column-resize";
|
|
8
|
+
/** Fired when a column was moved (uncontrolled), or with the proposed order (controlled). */
|
|
9
|
+
var COLUMN_REORDER = "argus-column-reorder";
|
|
8
10
|
/** Fired when a body row was clicked, after `argus-cell-click`. */
|
|
9
11
|
var ROW_CLICK = "argus-row-click";
|
|
10
12
|
/** Fired when a body cell was clicked, before `argus-row-click`. */
|
|
11
13
|
var CELL_CLICK = "argus-cell-click";
|
|
14
|
+
/** Fired after the update when a `dataSource` page failed to load. */
|
|
15
|
+
var LOAD_ERROR = "argus-load-error";
|
|
12
16
|
/** Dispatches a bubbling, composed `CustomEvent`, the shape of every public event. */
|
|
13
17
|
function fire(host, name, detail) {
|
|
14
18
|
return host.dispatchEvent(new CustomEvent(name, {
|
|
@@ -17,6 +21,6 @@ function fire(host, name, detail) {
|
|
|
17
21
|
composed: true
|
|
18
22
|
}));
|
|
19
23
|
}
|
|
20
|
-
export { CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE, fire };
|
|
24
|
+
export { CELL_CLICK, COLUMN_REORDER, COLUMN_RESIZE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE, fire };
|
|
21
25
|
|
|
22
26
|
//# sourceMappingURL=events.js.map
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["import type {\n CellClickDetail,\n ColumnResizeDetail,\n RangeChangeDetail,\n RowClickDetail,\n SortChangeDetail,\n} from '@argus-table/core';\n\n/** Fired after an update when the visible row range changed. */\nexport const RANGE_CHANGE = 'argus-range-change';\n\n/** Fired after the sort order changed (uncontrolled), or with the proposed order (controlled). */\nexport const SORT_CHANGE = 'argus-sort-change';\n\n/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */\nexport const COLUMN_RESIZE = 'argus-column-resize';\n\n/** Fired when a body row was clicked, after `argus-cell-click`. */\nexport const ROW_CLICK = 'argus-row-click';\n\n/** Fired when a body cell was clicked, before `argus-row-click`. */\nexport const CELL_CLICK = 'argus-cell-click';\n\ndeclare global {\n interface HTMLElementEventMap {\n 'argus-range-change': CustomEvent<RangeChangeDetail>;\n 'argus-sort-change': CustomEvent<SortChangeDetail>;\n 'argus-column-resize': CustomEvent<ColumnResizeDetail>;\n 'argus-row-click': CustomEvent<RowClickDetail<unknown>>;\n 'argus-cell-click': CustomEvent<CellClickDetail<unknown>>;\n }\n}\n\n/** Dispatches a bubbling, composed `CustomEvent`, the shape of every public event. */\nexport function fire(host: HTMLElement, name: string, detail: unknown): boolean {\n return host.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["import type {\n CellClickDetail,\n ColumnReorderDetail,\n ColumnResizeDetail,\n LoadErrorDetail,\n RangeChangeDetail,\n RowClickDetail,\n SortChangeDetail,\n} from '@argus-table/core';\n\n/** Fired after an update when the visible row range changed. */\nexport const RANGE_CHANGE = 'argus-range-change';\n\n/** Fired after the sort order changed (uncontrolled), or with the proposed order (controlled). */\nexport const SORT_CHANGE = 'argus-sort-change';\n\n/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */\nexport const COLUMN_RESIZE = 'argus-column-resize';\n\n/** Fired when a column was moved (uncontrolled), or with the proposed order (controlled). */\nexport const COLUMN_REORDER = 'argus-column-reorder';\n\n/** Fired when a body row was clicked, after `argus-cell-click`. */\nexport const ROW_CLICK = 'argus-row-click';\n\n/** Fired when a body cell was clicked, before `argus-row-click`. */\nexport const CELL_CLICK = 'argus-cell-click';\n\n/** Fired after the update when a `dataSource` page failed to load. */\nexport const LOAD_ERROR = 'argus-load-error';\n\ndeclare global {\n interface HTMLElementEventMap {\n 'argus-range-change': CustomEvent<RangeChangeDetail>;\n 'argus-sort-change': CustomEvent<SortChangeDetail>;\n 'argus-column-resize': CustomEvent<ColumnResizeDetail>;\n 'argus-column-reorder': CustomEvent<ColumnReorderDetail>;\n 'argus-row-click': CustomEvent<RowClickDetail<unknown>>;\n 'argus-cell-click': CustomEvent<CellClickDetail<unknown>>;\n 'argus-load-error': CustomEvent<LoadErrorDetail>;\n }\n}\n\n/** Dispatches a bubbling, composed `CustomEvent`, the shape of every public event. */\nexport function fire(host: HTMLElement, name: string, detail: unknown): boolean {\n return host.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));\n}\n"],"mappings":";;AAWA,IAAa,eAAe;;AAG5B,IAAa,cAAc;;AAG3B,IAAa,gBAAgB;;AAG7B,IAAa,iBAAiB;;AAG9B,IAAa,YAAY;;AAGzB,IAAa,aAAa;;AAG1B,IAAa,aAAa;;AAe1B,SAAgB,KAAK,MAAmB,MAAc,QAA0B;CAC9E,OAAO,KAAK,cAAc,IAAI,YAAY,MAAM;EAAE;EAAQ,SAAS;EAAM,UAAU;CAAK,CAAC,CAAC;AAC5F"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TemplateResult, LitElement, CSSResultGroup, PropertyValues } from 'lit';
|
|
1
|
+
import { TemplateResult, ReactiveElement, LitElement, CSSResultGroup, PropertyValues } from 'lit';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Public types shared by `@argus-table/core` and `argus-table`. `argus-table`
|
|
@@ -8,27 +8,44 @@ import { TemplateResult, LitElement, CSSResultGroup, PropertyValues } from 'lit'
|
|
|
8
8
|
*/
|
|
9
9
|
/** Logical side of a pinned column: `'start'` is the left in LTR and the right in RTL. */
|
|
10
10
|
type ColumnPin = 'start' | 'end';
|
|
11
|
+
/** Side of a pinned row: `'top'` (below the header) or `'bottom'`. */
|
|
12
|
+
type RowPin = 'top' | 'bottom';
|
|
13
|
+
/**
|
|
14
|
+
* Rows fixed above or below the scrolling body, such as a totals row. They are
|
|
15
|
+
* separate objects, not rows of `data`: never sorted, and not counted as data rows.
|
|
16
|
+
*/
|
|
17
|
+
interface PinnedRows<TData> {
|
|
18
|
+
/** Rows pinned below the header, in order. */
|
|
19
|
+
top?: readonly TData[];
|
|
20
|
+
/** Rows pinned to the bottom, in order. */
|
|
21
|
+
bottom?: readonly TData[];
|
|
22
|
+
}
|
|
11
23
|
/** Text alignment inside a cell, in logical terms. */
|
|
12
24
|
type ColumnAlign = 'start' | 'center' | 'end';
|
|
13
25
|
/**
|
|
14
|
-
* Column width: a fixed pixel width,
|
|
15
|
-
*
|
|
26
|
+
* Column width: a fixed pixel width, `'auto'` to fit the content, or a share of
|
|
27
|
+
* the free space (`{ flex: 1 }`).
|
|
16
28
|
*/
|
|
17
|
-
type ColumnWidth = number | {
|
|
29
|
+
type ColumnWidth = number | 'auto' | {
|
|
18
30
|
flex: number;
|
|
19
31
|
};
|
|
20
32
|
/** What a `cell` function receives besides the row itself. */
|
|
21
33
|
interface CellContext<TData> {
|
|
22
|
-
/** The row object from `data
|
|
34
|
+
/** The row object from `data` (or from `pinnedRows`). */
|
|
23
35
|
row: TData;
|
|
24
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The id from `getRowId`, or the row's index as a string. A pinned row without
|
|
38
|
+
* `getRowId` gets `'top-0'`, `'bottom-0'` and so on.
|
|
39
|
+
*/
|
|
25
40
|
rowId: string;
|
|
26
|
-
/** Index of the row in the current (sorted) row model. */
|
|
41
|
+
/** Index of the row in the current (sorted) row model; for a pinned row, its index in its list. */
|
|
27
42
|
index: number;
|
|
28
43
|
/** The column's id. */
|
|
29
44
|
columnId: string;
|
|
30
45
|
/** The value read by the column's `accessor`, or `undefined` without one. */
|
|
31
46
|
value: unknown;
|
|
47
|
+
/** Set for a row from `pinnedRows`: the side it is pinned to. */
|
|
48
|
+
pinned?: RowPin;
|
|
32
49
|
}
|
|
33
50
|
/**
|
|
34
51
|
* One column of the table.
|
|
@@ -51,8 +68,12 @@ interface ColumnDef$1<TData, TContent = unknown> {
|
|
|
51
68
|
/** Custom cell content. Without it, the cell shows the accessor's value as text. */
|
|
52
69
|
cell?: (row: TData, context: CellContext<TData>) => TContent;
|
|
53
70
|
/**
|
|
54
|
-
* Fixed width in px, or `{ flex }` for a share of the
|
|
55
|
-
* Pinned columns ignore `flex`. Default: 150 px.
|
|
71
|
+
* Fixed width in px, `'auto'` to fit the content, or `{ flex }` for a share of the
|
|
72
|
+
* free space (at least `minWidth`). Pinned columns ignore `flex`. Default: 150 px.
|
|
73
|
+
*
|
|
74
|
+
* `'auto'` fits the header, the pinned rows and the rendered body rows, within
|
|
75
|
+
* `minWidth` and `maxWidth`. It measures once the first loaded rows render, then keeps
|
|
76
|
+
* that width; `autosizeColumns()` fits it again.
|
|
56
77
|
*/
|
|
57
78
|
width?: ColumnWidth;
|
|
58
79
|
/** Lower bound for resizing and for flex columns, in px. Default: 40. */
|
|
@@ -65,6 +86,12 @@ interface ColumnDef$1<TData, TContent = unknown> {
|
|
|
65
86
|
sortable?: boolean;
|
|
66
87
|
/** Whether the column can be resized. Default: `true`. */
|
|
67
88
|
resizable?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Whether the column can be moved by dragging its header or with Alt+Shift+Arrow.
|
|
91
|
+
* Default: `true`. Columns move within their own group (start-pinned, scrolling or
|
|
92
|
+
* end-pinned). Other columns can still move past one that sets `false`.
|
|
93
|
+
*/
|
|
94
|
+
reorderable?: boolean;
|
|
68
95
|
/** Text alignment. Default: `'start'`. */
|
|
69
96
|
align?: ColumnAlign;
|
|
70
97
|
}
|
|
@@ -79,6 +106,11 @@ interface SortItem {
|
|
|
79
106
|
type SortState = SortItem[];
|
|
80
107
|
/** Column widths in px set by resizing, keyed by column id. */
|
|
81
108
|
type ColumnSizingState = Record<string, number>;
|
|
109
|
+
/**
|
|
110
|
+
* The column order: column ids as the table shows them (start-pinned, scrolling,
|
|
111
|
+
* end-pinned). An empty array means the order of `columns`.
|
|
112
|
+
*/
|
|
113
|
+
type ColumnOrderState = string[];
|
|
82
114
|
/** `detail` of `argus-sort-change`. */
|
|
83
115
|
interface SortChangeDetail {
|
|
84
116
|
sorting: SortState;
|
|
@@ -89,23 +121,71 @@ interface ColumnResizeDetail {
|
|
|
89
121
|
width: number;
|
|
90
122
|
sizing: ColumnSizingState;
|
|
91
123
|
}
|
|
124
|
+
/** `detail` of `argus-column-reorder`. */
|
|
125
|
+
interface ColumnReorderDetail {
|
|
126
|
+
/** The column that moved. */
|
|
127
|
+
columnId: string;
|
|
128
|
+
/** The whole new order. */
|
|
129
|
+
order: ColumnOrderState;
|
|
130
|
+
}
|
|
92
131
|
/** `detail` of `argus-row-click`. */
|
|
93
132
|
interface RowClickDetail<TData> {
|
|
94
133
|
row: TData;
|
|
95
134
|
rowId: string;
|
|
135
|
+
/** Index in the current row order; for a pinned row, its index in its list. */
|
|
96
136
|
index: number;
|
|
137
|
+
/** Set when the row is from `pinnedRows`: the side it is pinned to. */
|
|
138
|
+
pinned?: RowPin;
|
|
97
139
|
}
|
|
98
140
|
/** `detail` of `argus-cell-click`. */
|
|
99
141
|
interface CellClickDetail<TData> {
|
|
100
142
|
row: TData;
|
|
101
143
|
columnId: string;
|
|
102
144
|
value: unknown;
|
|
145
|
+
/** Set when the row is from `pinnedRows`: the side it is pinned to. */
|
|
146
|
+
pinned?: RowPin;
|
|
103
147
|
}
|
|
104
148
|
/** `detail` of `argus-range-change`: the first and last rendered row index (inclusive). */
|
|
105
149
|
interface RangeChangeDetail {
|
|
106
150
|
start: number;
|
|
107
151
|
end: number;
|
|
108
152
|
}
|
|
153
|
+
/** What `getRows()` receives: a range of rows, the sort order and an abort signal. */
|
|
154
|
+
interface RowsRequest {
|
|
155
|
+
/** Index of the first row, inclusive. */
|
|
156
|
+
start: number;
|
|
157
|
+
/** Index after the last row: exclusive, like `Array.prototype.slice`. */
|
|
158
|
+
end: number;
|
|
159
|
+
/** The current sort order; sorting happens on the server. `[]` when unsorted. */
|
|
160
|
+
sorting: SortState;
|
|
161
|
+
/** Aborted when the rows are no longer needed: scrolled far away, or the source was reset. */
|
|
162
|
+
signal: AbortSignal;
|
|
163
|
+
}
|
|
164
|
+
/** What `getRows()` resolves to. */
|
|
165
|
+
interface RowsResponse<TData> {
|
|
166
|
+
/** The rows from `start`, at most `end - start` of them. */
|
|
167
|
+
rows: readonly TData[];
|
|
168
|
+
/**
|
|
169
|
+
* Total number of rows, if known. Without it, the table grows as pages arrive,
|
|
170
|
+
* and a page with fewer rows than asked for marks the end.
|
|
171
|
+
*/
|
|
172
|
+
total?: number;
|
|
173
|
+
}
|
|
174
|
+
/** A remote source of rows, loaded page by page as they scroll into view. */
|
|
175
|
+
interface DataSource<TData> {
|
|
176
|
+
/** Loads one page of rows. Reject (or throw) to show the page as failed, with a Retry button. */
|
|
177
|
+
getRows(request: RowsRequest): Promise<RowsResponse<TData>>;
|
|
178
|
+
/** Rows per request. Default: 100. */
|
|
179
|
+
pageSize?: number;
|
|
180
|
+
}
|
|
181
|
+
/** `detail` of `argus-load-error`: what `getRows()` rejected with, and the rows it was asked for. */
|
|
182
|
+
interface LoadErrorDetail {
|
|
183
|
+
error: unknown;
|
|
184
|
+
/** Index of the first row, inclusive. */
|
|
185
|
+
start: number;
|
|
186
|
+
/** Index after the last row, exclusive. */
|
|
187
|
+
end: number;
|
|
188
|
+
}
|
|
109
189
|
|
|
110
190
|
/**
|
|
111
191
|
* What a column's `header` or `cell` may produce: a Lit template, a DOM node, or
|
|
@@ -118,20 +198,90 @@ type ColumnDef<T = unknown> = ColumnDef$1<T, CellContent>;
|
|
|
118
198
|
/** Row density. Each one sets defaults for `--argus-row-height` and `--argus-cell-padding-inline`. */
|
|
119
199
|
type Density = 'compact' | 'comfortable' | 'spacious';
|
|
120
200
|
|
|
201
|
+
/**
|
|
202
|
+
* One column of the parent `<argus-table>`, written as markup instead of an entry in
|
|
203
|
+
* its `columns` property. It is data only and renders nothing.
|
|
204
|
+
*
|
|
205
|
+
* Only direct children of the table count, in document order. A non-empty `columns`
|
|
206
|
+
* property wins over the children. Adding, removing, moving or changing a column
|
|
207
|
+
* updates the table; sizes, order and sorting are kept by `column-id`.
|
|
208
|
+
*
|
|
209
|
+
* A `<template>` child is the content of every cell in the column (static: the same
|
|
210
|
+
* for each row). Set the `cell` property for content that depends on the row.
|
|
211
|
+
*
|
|
212
|
+
* @summary Declarative column of an `<argus-table>`.
|
|
213
|
+
*/
|
|
214
|
+
declare class ArgusColumn<T = unknown> extends ReactiveElement {
|
|
215
|
+
#private;
|
|
216
|
+
/** Stable key, used in state objects, events and persistence. Must be unique within the table. */
|
|
217
|
+
columnId: string;
|
|
218
|
+
/** Header content: text as an attribute; a Lit template, a node or a function as a property. */
|
|
219
|
+
header?: CellContent | (() => CellContent);
|
|
220
|
+
/**
|
|
221
|
+
* How to read the cell value: a property path such as `customer.name`, or (as a
|
|
222
|
+
* property) a function of the row and its index.
|
|
223
|
+
*/
|
|
224
|
+
accessor?: string | ((row: T, index: number) => unknown);
|
|
225
|
+
/** Cell content from the row: `(row, context) => content`. Property only; wins over a `<template>` child. */
|
|
226
|
+
cell?: (row: T, context: CellContext<T>) => CellContent;
|
|
227
|
+
/** Width: `90` (px), `auto` to fit the content, or `2fr` for a share of the free space. Default: 150 px. */
|
|
228
|
+
width?: ColumnWidth;
|
|
229
|
+
/** Lower bound for resizing and for flex columns, in px. Default: 40. */
|
|
230
|
+
minWidth?: number;
|
|
231
|
+
/** Upper bound for resizing, in px. Default: no limit. */
|
|
232
|
+
maxWidth?: number;
|
|
233
|
+
/** Pins the column to the `start` or `end` side. */
|
|
234
|
+
pin?: ColumnPin;
|
|
235
|
+
/** Text alignment: `start`, `center` or `end`. Default: `start`. */
|
|
236
|
+
align?: ColumnAlign;
|
|
237
|
+
/** Whether the header can be clicked to sort. Default: `false`. */
|
|
238
|
+
sortable?: boolean;
|
|
239
|
+
/** Whether the column can be resized. Default: `true`; `resizable="false"` turns it off. */
|
|
240
|
+
resizable?: boolean;
|
|
241
|
+
/** Whether the column can be moved. Default: `true`; `reorderable="false"` turns it off. */
|
|
242
|
+
reorderable?: boolean;
|
|
243
|
+
/** Data only: no shadow root, nothing rendered. */
|
|
244
|
+
protected createRenderRoot(): HTMLElement;
|
|
245
|
+
connectedCallback(): void;
|
|
246
|
+
disconnectedCallback(): void;
|
|
247
|
+
/**
|
|
248
|
+
* The column as the table reads it. Unset fields stay `undefined`, which the
|
|
249
|
+
* table reads as its defaults. An unknown `pin` is ignored and an unknown
|
|
250
|
+
* `align` falls back to `start`.
|
|
251
|
+
*/
|
|
252
|
+
toColumnDef(): ColumnDef<T>;
|
|
253
|
+
protected willUpdate(): void;
|
|
254
|
+
protected updated(): void;
|
|
255
|
+
}
|
|
256
|
+
declare global {
|
|
257
|
+
interface HTMLElementTagNameMap {
|
|
258
|
+
'argus-column': ArgusColumn;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
121
262
|
/**
|
|
122
263
|
* A virtualized data table.
|
|
123
264
|
*
|
|
124
|
-
* Set `columns
|
|
125
|
-
*
|
|
126
|
-
*
|
|
265
|
+
* Set `columns`, and `data` or `dataSource`, as properties. Columns can also be
|
|
266
|
+
* written as `<argus-column>` children; a non-empty `columns` wins over them.
|
|
267
|
+
* Header and cell content is rendered into the element's light DOM and slotted
|
|
268
|
+
* into the cells, so page CSS and the page's own components work inside cells.
|
|
269
|
+
*
|
|
270
|
+
* Any number of rows scrolls to the end. Browsers limit an element's height
|
|
271
|
+
* (about 800k rows of 40 px in Chromium and Safari, 400k in Firefox), so a taller
|
|
272
|
+
* body renders shorter and its scroll position is scaled onto the rows: the
|
|
273
|
+
* scrollbar still spans every row, and a wheel step covers a few more of them.
|
|
274
|
+
* Use `scrollToRow()` rather than setting the scroll container's `scrollTop`.
|
|
127
275
|
*
|
|
128
276
|
* @summary Data table with pinned rows and columns and infinite loading.
|
|
129
277
|
*
|
|
130
|
-
* @fires argus-sort-change - A sortable header was clicked, or activated with Enter or Space. `detail: { sorting }`: the new order, or the proposed one when `sorting` is controlled.
|
|
131
|
-
* @fires argus-column-resize - A column was resized by dragging its handle
|
|
132
|
-
* @fires argus-
|
|
133
|
-
* @fires argus-
|
|
134
|
-
* @fires argus-
|
|
278
|
+
* @fires {CustomEvent<SortChangeDetail>} argus-sort-change - A sortable header was clicked, or activated with Enter or Space. `detail: { sorting }`: the new order, or the proposed one when `sorting` is controlled.
|
|
279
|
+
* @fires {CustomEvent<ColumnResizeDetail>} argus-column-resize - A column was resized: by dragging its handle, with Alt+Arrow on its header, by a double-click on its handle (fit to content) or by `autosizeColumns()`. `detail: { columnId, width, sizing }`: the new widths, or the proposed ones when `columnSizing` is controlled. Fires on every frame of a drag, and once per column that `autosizeColumns()` changes.
|
|
280
|
+
* @fires {CustomEvent<ColumnReorderDetail>} argus-column-reorder - A column was moved: by dragging its header, or with Alt+Shift+Arrow on its focused header. Columns move within their own group (start-pinned, scrolling, end-pinned). `detail: { columnId, order }`: the new order, or the proposed one when `columnOrder` is controlled. Fires once per move, on drop.
|
|
281
|
+
* @fires {CustomEvent<CellClickDetail<T>>} argus-cell-click - A body or pinned-row cell was clicked, or Enter was pressed on it. `detail: { row, columnId, value, pinned? }`; `pinned` is `'top'` or `'bottom'` for a pinned row. Fires before `argus-row-click`.
|
|
282
|
+
* @fires {CustomEvent<RowClickDetail<T>>} argus-row-click - A body or pinned row was clicked, or Enter was pressed on one of its cells. `detail: { row, rowId, index, pinned? }`; for a pinned row, `index` is its index in its list.
|
|
283
|
+
* @fires {CustomEvent<RangeChangeDetail>} argus-range-change - The visible rows changed. `detail: { start, end }` (row indices, inclusive).
|
|
284
|
+
* @fires {CustomEvent<LoadErrorDetail>} argus-load-error - A `dataSource` page failed to load: `getRows()` rejected or threw. `detail: { error, start, end }` (`end` exclusive). The rows show a Retry button.
|
|
135
285
|
*
|
|
136
286
|
* @slot empty - Shown instead of the body when there are no rows. Default: "No rows".
|
|
137
287
|
*
|
|
@@ -140,15 +290,25 @@ type Density = 'compact' | 'comfortable' | 'spacious';
|
|
|
140
290
|
* @csspart header-row - The header row.
|
|
141
291
|
* @csspart header-cell - Every header cell.
|
|
142
292
|
* @csspart header-cell-sorted - The header of the sorted column. The direction is in `aria-sort`.
|
|
143
|
-
* @csspart
|
|
293
|
+
* @csspart header-cell-dragging - Added to `header-cell` on the header being dragged to a new position.
|
|
294
|
+
* @csspart drop-indicator - The line on the edge of the header where a dragged column would land.
|
|
295
|
+
* @csspart resize-handle - The drag handle on the inline-end edge of a resizable column's header. A double-click fits the column to its content.
|
|
144
296
|
* @csspart body - The row group holding the rendered body rows.
|
|
145
297
|
* @csspart row - Every body row.
|
|
146
298
|
* @csspart row-even - Body rows with an even index (0-based).
|
|
147
299
|
* @csspart row-odd - Body rows with an odd index.
|
|
300
|
+
* @csspart row-loading - Body rows whose `dataSource` page hasn't loaded yet, or failed.
|
|
301
|
+
* @csspart row-pinned - Every row from `pinnedRows`, on either side. Also has `row`, but no `row-even`/`row-odd`.
|
|
302
|
+
* @csspart row-pinned-top - The rows pinned below the header.
|
|
303
|
+
* @csspart row-pinned-bottom - The rows pinned to the bottom.
|
|
304
|
+
* @csspart cell-pinned-row - Every cell of a pinned row (next to `cell`).
|
|
148
305
|
* @csspart cell - Every body cell.
|
|
149
306
|
* @csspart cell-sorted - Body cells in the sorted column.
|
|
150
307
|
* @csspart cell-pinned - Body cells in a pinned column, on either side.
|
|
151
308
|
* @csspart cell-pinned-edge - The pinned body cell next to the scrolling columns (one per pinned side).
|
|
309
|
+
* @csspart skeleton - The placeholder bar in each cell of a row that is still loading.
|
|
310
|
+
* @csspart skeleton-error - Added to `skeleton` in a row whose page failed to load.
|
|
311
|
+
* @csspart retry - The Retry button in the first cell of a failed page's first visible row.
|
|
152
312
|
* @csspart empty - The empty-state container.
|
|
153
313
|
*
|
|
154
314
|
* @cssprop [--argus-bg=Canvas] - Background of the table and rows.
|
|
@@ -158,30 +318,62 @@ type Density = 'compact' | 'comfortable' | 'spacious';
|
|
|
158
318
|
* @cssprop [--argus-header-fg=inherit] - Header text colour.
|
|
159
319
|
* @cssprop [--argus-border-color] - Colour of the row borders.
|
|
160
320
|
* @cssprop [--argus-border-width=1px] - Width of the row borders.
|
|
321
|
+
* @cssprop [--argus-outline=none] - Border around the whole table, as a `border` shorthand (`1px solid …`). Inside the element's size.
|
|
322
|
+
* @cssprop [--argus-radius=0] - Corner radius of the table. Header, rows and pinned columns are clipped to it.
|
|
161
323
|
* @cssprop [--argus-row-height] - Height of a row. Default per density: 32px, 40px, 52px.
|
|
162
324
|
* @cssprop [--argus-cell-padding-inline] - Inline padding of cells. Default per density: 8px, 12px, 16px.
|
|
163
325
|
* @cssprop [--argus-row-stripe-bg] - Background of odd rows. Default: no stripes.
|
|
164
326
|
* @cssprop [--argus-pinned-shadow] - Shadow on the pinned edge, as a `box-shadow` falling toward the end side (`4px 0 6px -4px …`). Mirrored for the end edge and in RTL; `none` removes it.
|
|
165
327
|
* @cssprop [--argus-resize-handle-color] - Colour of the resize handle's line, shown on hover, focus and drag.
|
|
166
328
|
* @cssprop [--argus-focus-ring=2px solid Highlight] - Outline of the focused header or cell.
|
|
329
|
+
* @cssprop [--argus-drop-indicator-color=Highlight] - Colour of the line that marks where a dragged column would land.
|
|
167
330
|
* @cssprop [--argus-row-hover-bg] - Background of the hovered row. Default: no change.
|
|
331
|
+
* @cssprop [--argus-pinned-row-bg=var(--argus-bg)] - Background of the pinned rows, pinned columns included.
|
|
332
|
+
* @cssprop [--argus-skeleton-bg] - Colour of the skeleton bars in rows still loading. Default: the text colour at low opacity.
|
|
168
333
|
* @cssprop [--argus-row-selected-bg] - Reserved for row selection, which comes after v0.1. The table doesn't read it yet.
|
|
169
334
|
*/
|
|
170
335
|
declare class ArgusTable<T = unknown> extends LitElement {
|
|
171
336
|
#private;
|
|
172
337
|
static styles: CSSResultGroup;
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Column definitions. Property only: functions can't be attributes. While empty,
|
|
340
|
+
* the table reads its `<argus-column>` children instead. `null` or `undefined`
|
|
341
|
+
* means `[]`.
|
|
342
|
+
*/
|
|
343
|
+
get columns(): readonly ColumnDef<T>[];
|
|
344
|
+
set columns(value: readonly ColumnDef<T>[] | null | undefined);
|
|
345
|
+
/**
|
|
346
|
+
* The rows to show. Replace the array to update; changes inside it aren't
|
|
347
|
+
* detected. `null` or `undefined` means `[]`.
|
|
348
|
+
*/
|
|
349
|
+
get data(): readonly T[];
|
|
350
|
+
set data(value: readonly T[] | null | undefined);
|
|
351
|
+
/**
|
|
352
|
+
* Loads rows page by page as they scroll into view, instead of `data`:
|
|
353
|
+
* `{ getRows({ start, end, sorting, signal }), pageSize? }`, where `end` is
|
|
354
|
+
* exclusive and `getRows` resolves to `{ rows, total? }`. Without a `total`,
|
|
355
|
+
* the table grows as pages arrive and a short page marks the end. Sorting
|
|
356
|
+
* happens on the server: a new order reloads from the top. While set, it wins
|
|
357
|
+
* over `data`. Replace the object to change `getRows` or `pageSize`.
|
|
358
|
+
*/
|
|
359
|
+
dataSource?: DataSource<T>;
|
|
360
|
+
/**
|
|
361
|
+
* Rows fixed below the header (`top`) and at the bottom of the view (`bottom`),
|
|
362
|
+
* such as a totals row: `{ top?: T[], bottom?: T[] }`. They are separate
|
|
363
|
+
* objects, not rows of `data`: columns read them the same way, but they are
|
|
364
|
+
* never sorted and work the same with a `dataSource`. A `cell` function gets
|
|
365
|
+
* `pinned: 'top' | 'bottom'` in its context. Replace the object to update.
|
|
366
|
+
*/
|
|
367
|
+
pinnedRows?: PinnedRows<T>;
|
|
368
|
+
/** Returns a stable id for a row. Default: the row's index (in `data`, or among all `dataSource` rows). */
|
|
178
369
|
getRowId?: (row: T, index: number) => string;
|
|
179
370
|
/** Row height in px. Unset: the density (or the `--argus-row-height` token) decides. */
|
|
180
371
|
rowHeight?: number;
|
|
181
372
|
/** Row density: `compact`, `comfortable` or `spacious`. */
|
|
182
373
|
density: Density;
|
|
183
|
-
/** Rows rendered above and below the visible ones. */
|
|
184
|
-
overscan: number;
|
|
374
|
+
/** Rows rendered above and below the visible ones. `null` or `undefined` means the default, 6. */
|
|
375
|
+
get overscan(): number;
|
|
376
|
+
set overscan(value: number | null | undefined);
|
|
185
377
|
/**
|
|
186
378
|
* The sort order, e.g. `[{ id: 'total', desc: true }]`; `[]` when unsorted. Unset,
|
|
187
379
|
* the table keeps it itself and header clicks change it. Once set, it is
|
|
@@ -197,8 +389,35 @@ declare class ArgusTable<T = unknown> extends LitElement {
|
|
|
197
389
|
*/
|
|
198
390
|
get columnSizing(): ColumnSizingState;
|
|
199
391
|
set columnSizing(value: ColumnSizingState | undefined);
|
|
392
|
+
/**
|
|
393
|
+
* The column order: column ids as the table shows them, e.g.
|
|
394
|
+
* `['id', 'total', 'customer']`. `[]` (the default) keeps the order of `columns`.
|
|
395
|
+
* Each pin group (start-pinned, scrolling, end-pinned) takes its columns in this
|
|
396
|
+
* order; unknown ids are ignored and columns it doesn't list follow the listed
|
|
397
|
+
* ones of their group. After a move it holds every column id. Uncontrolled
|
|
398
|
+
* until set, like `sorting`: once set, a move only fires `argus-column-reorder`
|
|
399
|
+
* with the proposed order. `undefined` hands it back.
|
|
400
|
+
*/
|
|
401
|
+
get columnOrder(): ColumnOrderState;
|
|
402
|
+
set columnOrder(value: ColumnOrderState | undefined);
|
|
200
403
|
connectedCallback(): void;
|
|
201
404
|
disconnectedCallback(): void;
|
|
405
|
+
/**
|
|
406
|
+
* With a `dataSource`, drops every loaded page and loads the rows in view
|
|
407
|
+
* again, keeping the scroll position. With `data`, re-renders the cells (after
|
|
408
|
+
* changing row objects in place). Resolves once the table has re-rendered;
|
|
409
|
+
* reloaded rows arrive as their pages load.
|
|
410
|
+
*/
|
|
411
|
+
refresh(): Promise<void>;
|
|
412
|
+
/**
|
|
413
|
+
* Fits columns to their content: the header, the pinned rows and the body rows
|
|
414
|
+
* rendered now (rows out of view aren't measured). It resizes them like a drag
|
|
415
|
+
* would, so `argus-column-resize` fires for each column that changes, and with a
|
|
416
|
+
* controlled `columnSizing` nothing changes until you set it. Without `ids`, fits
|
|
417
|
+
* every resizable column; columns with `resizable: false` keep their width.
|
|
418
|
+
* Resolves once the table has re-rendered.
|
|
419
|
+
*/
|
|
420
|
+
autosizeColumns(ids?: readonly string[]): Promise<void>;
|
|
202
421
|
/** Scrolls so that the row at `index` (in the current row order) is at the top. */
|
|
203
422
|
scrollToRow(index: number): Promise<void>;
|
|
204
423
|
protected willUpdate(changed: PropertyValues<this>): void;
|
|
@@ -218,19 +437,25 @@ declare const RANGE_CHANGE = "argus-range-change";
|
|
|
218
437
|
declare const SORT_CHANGE = "argus-sort-change";
|
|
219
438
|
/** Fired when a column was resized (uncontrolled), or with the proposed widths (controlled). */
|
|
220
439
|
declare const COLUMN_RESIZE = "argus-column-resize";
|
|
440
|
+
/** Fired when a column was moved (uncontrolled), or with the proposed order (controlled). */
|
|
441
|
+
declare const COLUMN_REORDER = "argus-column-reorder";
|
|
221
442
|
/** Fired when a body row was clicked, after `argus-cell-click`. */
|
|
222
443
|
declare const ROW_CLICK = "argus-row-click";
|
|
223
444
|
/** Fired when a body cell was clicked, before `argus-row-click`. */
|
|
224
445
|
declare const CELL_CLICK = "argus-cell-click";
|
|
446
|
+
/** Fired after the update when a `dataSource` page failed to load. */
|
|
447
|
+
declare const LOAD_ERROR = "argus-load-error";
|
|
225
448
|
declare global {
|
|
226
449
|
interface HTMLElementEventMap {
|
|
227
450
|
'argus-range-change': CustomEvent<RangeChangeDetail>;
|
|
228
451
|
'argus-sort-change': CustomEvent<SortChangeDetail>;
|
|
229
452
|
'argus-column-resize': CustomEvent<ColumnResizeDetail>;
|
|
453
|
+
'argus-column-reorder': CustomEvent<ColumnReorderDetail>;
|
|
230
454
|
'argus-row-click': CustomEvent<RowClickDetail<unknown>>;
|
|
231
455
|
'argus-cell-click': CustomEvent<CellClickDetail<unknown>>;
|
|
456
|
+
'argus-load-error': CustomEvent<LoadErrorDetail>;
|
|
232
457
|
}
|
|
233
458
|
}
|
|
234
459
|
|
|
235
|
-
export { ArgusTable, CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE };
|
|
236
|
-
export type { CellClickDetail, CellContent, CellContext, ColumnAlign, ColumnDef, ColumnPin, ColumnResizeDetail, ColumnSizingState, ColumnWidth, Density, RangeChangeDetail, RowClickDetail, SortChangeDetail, SortItem, SortState };
|
|
460
|
+
export { ArgusColumn, ArgusTable, CELL_CLICK, COLUMN_REORDER, COLUMN_RESIZE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE };
|
|
461
|
+
export type { CellClickDetail, CellContent, CellContext, ColumnAlign, ColumnDef, ColumnOrderState, ColumnPin, ColumnReorderDetail, ColumnResizeDetail, ColumnSizingState, ColumnWidth, DataSource, Density, LoadErrorDetail, PinnedRows, RangeChangeDetail, RowClickDetail, RowPin, RowsRequest, RowsResponse, SortChangeDetail, SortItem, SortState };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ArgusColumn } from "./argus-column.js";
|
|
1
2
|
import { ArgusTable } from "./argus-table.js";
|
|
2
|
-
import { CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE } from "./events.js";
|
|
3
|
-
export { ArgusTable, CELL_CLICK, COLUMN_RESIZE, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE };
|
|
3
|
+
import { CELL_CLICK, COLUMN_REORDER, COLUMN_RESIZE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE } from "./events.js";
|
|
4
|
+
export { ArgusColumn, ArgusTable, CELL_CLICK, COLUMN_REORDER, COLUMN_RESIZE, LOAD_ERROR, RANGE_CHANGE, ROW_CLICK, SORT_CHANGE };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/max-height.ts
|
|
2
|
+
/**
|
|
3
|
+
* The tallest element the engine renders, measured once per page.
|
|
4
|
+
*
|
|
5
|
+
* Chromium and WebKit clamp a taller element at about 33.5M px (fewer CSS px
|
|
6
|
+
* when the page is zoomed in), and Firefox drops anything over about 17.9M px
|
|
7
|
+
* to 0. A hidden probe tries a few heights and keeps the first that renders at
|
|
8
|
+
* full size. A zoom change (a new `devicePixelRatio` on resize) measures again
|
|
9
|
+
* and tells the tables, so a scaled body stays within the new limit.
|
|
10
|
+
*/
|
|
11
|
+
var HEIGHTS = [
|
|
12
|
+
32e6,
|
|
13
|
+
16e6,
|
|
14
|
+
8e6,
|
|
15
|
+
4e6,
|
|
16
|
+
2e6
|
|
17
|
+
];
|
|
18
|
+
/** Content below this never needs the probe: every engine renders it. */
|
|
19
|
+
var SAFE_HEIGHT = 2e6;
|
|
20
|
+
/** Room left for the scrollbar and rounding. */
|
|
21
|
+
var MARGIN = 1e3;
|
|
22
|
+
var measured;
|
|
23
|
+
var dpr = 0;
|
|
24
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
25
|
+
function onResize() {
|
|
26
|
+
if (window.devicePixelRatio === dpr) return;
|
|
27
|
+
dpr = window.devicePixelRatio;
|
|
28
|
+
measured = void 0;
|
|
29
|
+
for (const listener of listeners) listener();
|
|
30
|
+
}
|
|
31
|
+
/** The tallest element height in px the engine renders. */
|
|
32
|
+
function maxElementHeight() {
|
|
33
|
+
if (measured !== void 0) return measured;
|
|
34
|
+
const probe = document.createElement("div");
|
|
35
|
+
probe.setAttribute("aria-hidden", "true");
|
|
36
|
+
probe.style.cssText = "position:fixed;inset-block-start:0;inset-inline-start:0;inline-size:1px;visibility:hidden;pointer-events:none";
|
|
37
|
+
document.body.append(probe);
|
|
38
|
+
measured = HEIGHTS.find((height) => {
|
|
39
|
+
probe.style.blockSize = `${String(height)}px`;
|
|
40
|
+
return probe.offsetHeight >= height - 1;
|
|
41
|
+
}) ?? SAFE_HEIGHT;
|
|
42
|
+
probe.remove();
|
|
43
|
+
return measured;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The tallest the body may render: the engine's maximum less what scrolls with
|
|
47
|
+
* it (`extra`: the header and pinned rows). `Infinity` for a body of `logical`
|
|
48
|
+
* px that is far from any limit, without measuring.
|
|
49
|
+
*/
|
|
50
|
+
function bodyCap(logical, extra) {
|
|
51
|
+
if (logical + extra < SAFE_HEIGHT) return Infinity;
|
|
52
|
+
return Math.max(maxElementHeight() - extra - MARGIN, 0);
|
|
53
|
+
}
|
|
54
|
+
/** Calls `listener` when the maximum may have changed (the page was zoomed). Returns the unsubscribe. */
|
|
55
|
+
function watchMaxElementHeight(listener) {
|
|
56
|
+
if (listeners.size === 0) {
|
|
57
|
+
dpr = window.devicePixelRatio;
|
|
58
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
59
|
+
}
|
|
60
|
+
listeners.add(listener);
|
|
61
|
+
return () => {
|
|
62
|
+
listeners.delete(listener);
|
|
63
|
+
if (listeners.size === 0) window.removeEventListener("resize", onResize);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export { bodyCap, watchMaxElementHeight };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=max-height.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"max-height.js","names":[],"sources":["../src/max-height.ts"],"sourcesContent":["/**\n * The tallest element the engine renders, measured once per page.\n *\n * Chromium and WebKit clamp a taller element at about 33.5M px (fewer CSS px\n * when the page is zoomed in), and Firefox drops anything over about 17.9M px\n * to 0. A hidden probe tries a few heights and keeps the first that renders at\n * full size. A zoom change (a new `devicePixelRatio` on resize) measures again\n * and tells the tables, so a scaled body stays within the new limit.\n */\nconst HEIGHTS = [32_000_000, 16_000_000, 8_000_000, 4_000_000, 2_000_000];\n\n/** Content below this never needs the probe: every engine renders it. */\nconst SAFE_HEIGHT = 2_000_000;\n/** Room left for the scrollbar and rounding. */\nconst MARGIN = 1000;\n\nlet measured: number | undefined;\nlet forced: number | undefined;\nlet dpr = 0;\nconst listeners = new Set<() => void>();\n\nfunction onResize() {\n if (window.devicePixelRatio === dpr) return;\n dpr = window.devicePixelRatio;\n measured = undefined;\n for (const listener of listeners) listener();\n}\n\n/** The tallest element height in px the engine renders. */\nfunction maxElementHeight(): number {\n if (forced !== undefined) return forced;\n if (measured !== undefined) return measured;\n // Fixed, so the probe never makes the page scroll.\n const probe = document.createElement('div');\n probe.setAttribute('aria-hidden', 'true');\n probe.style.cssText =\n 'position:fixed;inset-block-start:0;inset-inline-start:0;inline-size:1px;visibility:hidden;pointer-events:none';\n document.body.append(probe);\n measured =\n HEIGHTS.find((height) => {\n probe.style.blockSize = `${String(height)}px`;\n return probe.offsetHeight >= height - 1;\n }) ?? SAFE_HEIGHT;\n probe.remove();\n return measured;\n}\n\n/**\n * The tallest the body may render: the engine's maximum less what scrolls with\n * it (`extra`: the header and pinned rows). `Infinity` for a body of `logical`\n * px that is far from any limit, without measuring.\n */\nexport function bodyCap(logical: number, extra: number): number {\n if (forced === undefined && logical + extra < SAFE_HEIGHT) return Infinity;\n return Math.max(maxElementHeight() - extra - MARGIN, 0);\n}\n\n/** Calls `listener` when the maximum may have changed (the page was zoomed). Returns the unsubscribe. */\nexport function watchMaxElementHeight(listener: () => void): () => void {\n if (listeners.size === 0) {\n dpr = window.devicePixelRatio;\n window.addEventListener('resize', onResize, { passive: true });\n }\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) window.removeEventListener('resize', onResize);\n };\n}\n\n/** Tests: pretend the engine renders at most `height` px (`undefined` measures again). */\nexport function setMaxElementHeightForTests(height: number | undefined): void {\n forced = height;\n}\n"],"mappings":";;;;;;;;;;AASA,IAAM,UAAU;CAAC;CAAY;CAAY;CAAW;CAAW;AAAS;;AAGxE,IAAM,cAAc;;AAEpB,IAAM,SAAS;AAEf,IAAI;AAEJ,IAAI,MAAM;AACV,IAAM,4BAAY,IAAI,IAAgB;AAEtC,SAAS,WAAW;CAClB,IAAI,OAAO,qBAAqB,KAAK;CACrC,MAAM,OAAO;CACb,WAAW,KAAA;CACX,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;;AAGA,SAAS,mBAA2B;CAElC,IAAI,aAAa,KAAA,GAAW,OAAO;CAEnC,MAAM,QAAQ,SAAS,cAAc,KAAK;CAC1C,MAAM,aAAa,eAAe,MAAM;CACxC,MAAM,MAAM,UACV;CACF,SAAS,KAAK,OAAO,KAAK;CAC1B,WACE,QAAQ,MAAM,WAAW;EACvB,MAAM,MAAM,YAAY,GAAG,OAAO,MAAM,EAAE;EAC1C,OAAO,MAAM,gBAAgB,SAAS;CACxC,CAAC,KAAK;CACR,MAAM,OAAO;CACb,OAAO;AACT;;;;;;AAOA,SAAgB,QAAQ,SAAiB,OAAuB;CAC9D,IAA4B,UAAU,QAAQ,aAAa,OAAO;CAClE,OAAO,KAAK,IAAI,iBAAiB,IAAI,QAAQ,QAAQ,CAAC;AACxD;;AAGA,SAAgB,sBAAsB,UAAkC;CACtE,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,OAAO;EACb,OAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;CAC/D;CACA,UAAU,IAAI,QAAQ;CACtB,aAAa;EACX,UAAU,OAAO,QAAQ;EACzB,IAAI,UAAU,SAAS,GAAG,OAAO,oBAAoB,UAAU,QAAQ;CACzE;AACF"}
|
package/dist/render/body.js
CHANGED
|
@@ -6,44 +6,76 @@ import { repeat } from "lit/directives/repeat.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* The virtualized body. Rows are keyed by pool slot, so a row entering the window
|
|
8
8
|
* reuses the DOM of one that left (ADR 0001), and they stay in pool order: screen
|
|
9
|
-
* readers get the position from `aria-rowindex`
|
|
9
|
+
* readers get the position from `aria-rowindex`. `firstRowIndex` is the
|
|
10
|
+
* `aria-rowindex` of row 0: after the header and the rows pinned to the top.
|
|
10
11
|
*/
|
|
11
|
-
function renderBody(rows, columns, totalSize, active) {
|
|
12
|
+
function renderBody(rows, columns, totalSize, active, firstRowIndex, loading) {
|
|
12
13
|
return html`<div
|
|
13
14
|
part="body"
|
|
14
15
|
role="rowgroup"
|
|
15
16
|
style=${styleMap({ blockSize: `${String(totalSize)}px` })}
|
|
16
17
|
>
|
|
17
|
-
${repeat(rows, (r) => r.slot, (r) => renderRow(r, columns, r.index === active?.index ? active.col : void 0))}
|
|
18
|
+
${repeat(rows, (r) => r.slot, (r) => renderRow(r, columns, r.index === active?.index ? active.col : void 0, firstRowIndex, loading))}
|
|
18
19
|
</div>`;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* One body row. Every cell is focusable (`tabindex=-1`) so clicks focus it; the
|
|
22
|
-
* active one is the grid's single tab stop (roving tabindex).
|
|
23
|
+
* active one is the grid's single tab stop (roving tabindex). A row whose data
|
|
24
|
+
* isn't loaded shows skeleton bars, and a Retry button if its page failed.
|
|
23
25
|
*/
|
|
24
|
-
function renderRow(row, columns, activeCol) {
|
|
26
|
+
function renderRow(row, columns, activeCol, firstRowIndex, loading) {
|
|
27
|
+
const state = loading?.stateAt(row.index) ?? "loaded";
|
|
28
|
+
let parts = row.index % 2 === 0 ? "row row-even" : "row row-odd";
|
|
29
|
+
if (state !== "loaded") parts += " row-loading";
|
|
30
|
+
const retry = state === "error" ? loading?.retry.get(row.index) : void 0;
|
|
25
31
|
return html`<div
|
|
26
|
-
part=${
|
|
32
|
+
part=${parts}
|
|
27
33
|
class="row"
|
|
28
34
|
role="row"
|
|
29
|
-
aria-rowindex=${row.index +
|
|
35
|
+
aria-rowindex=${row.index + firstRowIndex}
|
|
36
|
+
aria-busy=${state === "loading" ? "true" : nothing}
|
|
30
37
|
style=${styleMap({ transform: `translateY(${String(row.start)}px)` })}
|
|
31
38
|
>
|
|
32
|
-
${repeat(columns, (c) => c.id, (c) =>
|
|
33
|
-
part=${cellParts(c)}
|
|
34
|
-
role="gridcell"
|
|
35
|
-
aria-colindex=${c.colIndex}
|
|
36
|
-
tabindex=${c.id === activeCol ? 0 : -1}
|
|
37
|
-
data-col=${c.id}
|
|
38
|
-
data-align=${c.align}
|
|
39
|
-
data-pin=${c.pin ?? nothing}
|
|
40
|
-
?data-pin-edge=${c.pinEdge}
|
|
41
|
-
style=${pinStyle(c) ?? nothing}
|
|
42
|
-
>
|
|
43
|
-
<slot name=${cellSlot(row.slot, c.colIndex)}></slot>
|
|
44
|
-
</div>`)}
|
|
39
|
+
${repeat(columns, (c) => c.id, (c) => renderCell(c, c.id === activeCol, cellSlot(row.slot, c.colIndex), false, placeholder(state, c.colIndex === 1 ? retry : void 0, row.index, loading)))}
|
|
45
40
|
</div>`;
|
|
46
41
|
}
|
|
47
|
-
|
|
42
|
+
/**
|
|
43
|
+
* A body or pinned-row cell. Every cell is focusable (`tabindex=-1`) so clicks
|
|
44
|
+
* focus it; `tabStop` marks the grid's single tab stop. `before` goes ahead of
|
|
45
|
+
* the slot (a loading row's skeleton bar or Retry button).
|
|
46
|
+
*/
|
|
47
|
+
function renderCell(c, tabStop, slot, pinnedRow, before = nothing) {
|
|
48
|
+
return html`<div
|
|
49
|
+
part=${cellParts(c, pinnedRow)}
|
|
50
|
+
role="gridcell"
|
|
51
|
+
aria-colindex=${c.colIndex}
|
|
52
|
+
tabindex=${tabStop ? 0 : -1}
|
|
53
|
+
data-col=${c.id}
|
|
54
|
+
data-align=${c.align}
|
|
55
|
+
data-pin=${c.pin ?? nothing}
|
|
56
|
+
style=${pinStyle(c) ?? nothing}
|
|
57
|
+
>
|
|
58
|
+
${before}<slot name=${slot}></slot>
|
|
59
|
+
</div>`;
|
|
60
|
+
}
|
|
61
|
+
/** What an unloaded cell shows: a skeleton bar, or the Retry button in a failed page's first cell. */
|
|
62
|
+
function placeholder(state, retryLabel, index, loading) {
|
|
63
|
+
if (state === "loaded") return nothing;
|
|
64
|
+
if (retryLabel !== void 0 && loading) return html`<button
|
|
65
|
+
part="retry"
|
|
66
|
+
type="button"
|
|
67
|
+
tabindex="-1"
|
|
68
|
+
data-row=${index}
|
|
69
|
+
aria-label=${retryLabel}
|
|
70
|
+
@click=${loading.onRetry}
|
|
71
|
+
>
|
|
72
|
+
Retry
|
|
73
|
+
</button>`;
|
|
74
|
+
return html`<div
|
|
75
|
+
part=${state === "error" ? "skeleton skeleton-error" : "skeleton"}
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
></div>`;
|
|
78
|
+
}
|
|
79
|
+
export { renderBody, renderCell };
|
|
48
80
|
|
|
49
81
|
//# sourceMappingURL=body.js.map
|