@rivinia/table 0.1.0 → 1.0.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +38 -19
- package/fesm2022/rivinia-table-ui.mjs +31 -17
- package/fesm2022/rivinia-table-ui.mjs.map +1 -1
- package/fesm2022/rivinia-table.mjs +40 -10
- package/fesm2022/rivinia-table.mjs.map +1 -1
- package/package.json +13 -1
- package/types/rivinia-table-ui.d.ts +2 -8
- package/types/rivinia-table.d.ts +12 -16
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rivinia contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rivinia/table)
|
|
4
4
|
[](https://www.npmjs.com/package/@rivinia/table)
|
|
5
5
|
|
|
6
|
+
<p align="center">
|
|
7
|
+
<img src="https://raw.githubusercontent.com/abdelfattahqandil21-oss/Revinia/table/v1.0.0/assets/rivinia-abstract-mark-light.svg" alt="Rivinia" width="340" />
|
|
8
|
+
</p>
|
|
9
|
+
|
|
6
10
|
A headless Angular table with typed columns, real row and column spans,
|
|
7
11
|
sorting, filtering, selection and paging — plus an optional ready-made grid.
|
|
8
12
|
The core owns the data and the geometry; it never renders a cell, never knows
|
|
9
13
|
what an "action" is, and never imports a stylesheet.
|
|
10
14
|
|
|
11
|
-
- Package version: **
|
|
15
|
+
- Package version: **1.0.0**
|
|
12
16
|
- Requires: **Angular `^22.0.0`** (`@angular/core` and `@angular/common`).
|
|
13
17
|
- Entry points: `@rivinia/table` (headless controller + pure helpers) and
|
|
14
18
|
`@rivinia/table/ui` (the `rv-table` grid, div-based, `role="grid"`).
|
|
@@ -82,7 +86,7 @@ property read at the use site.
|
|
|
82
86
|
```ts
|
|
83
87
|
import { createTable, tableColumn } from '@rivinia/table';
|
|
84
88
|
|
|
85
|
-
@Component({
|
|
89
|
+
@Component({/* … */})
|
|
86
90
|
export class InvoicesPage {
|
|
87
91
|
private readonly table = createTable<Invoice>(() => ({
|
|
88
92
|
// Columns are a FACTORY too: `options()` runs inside a computed, so a
|
|
@@ -117,19 +121,19 @@ A column is data, not a component. `tableColumn<T>()` has two builders:
|
|
|
117
121
|
`accessor(key, def?)` reads a field of the row, `display(id, def?)` has no field
|
|
118
122
|
and exists for a template or an actions column.
|
|
119
123
|
|
|
120
|
-
| Field
|
|
121
|
-
|
|
|
122
|
-
| `header`
|
|
123
|
-
| `width`
|
|
124
|
-
| `flex`
|
|
125
|
-
| `align`
|
|
126
|
-
| `sortable`
|
|
127
|
-
| `format`
|
|
128
|
-
| `comparator`
|
|
129
|
-
| `pinned`
|
|
130
|
-
| `textOverflow` | `'nowrap'` (default), `'wrap'`, `'clamp'`.
|
|
131
|
-
| `lines`
|
|
132
|
-
| `hidden`
|
|
124
|
+
| Field | Meaning |
|
|
125
|
+
| -------------- | ------------------------------------------------------------------- |
|
|
126
|
+
| `header` | Visible header text. Omit for an icon-only column. |
|
|
127
|
+
| `width` | A CSS length. Fixed columns take part in `table-layout: fixed`. |
|
|
128
|
+
| `flex` | `> 0` absorbs free space; `0` stays fixed. |
|
|
129
|
+
| `align` | `'start' \| 'center' \| 'end'` — logical, so RTL needs no override. |
|
|
130
|
+
| `sortable` | Adds the three-state sort affordance and `aria-sort`. |
|
|
131
|
+
| `format` | `(value, row, locale) => string` for display only. |
|
|
132
|
+
| `comparator` | Sort with something other than the default compare. |
|
|
133
|
+
| `pinned` | `'start' \| 'end'` — stays visible during horizontal scroll. |
|
|
134
|
+
| `textOverflow` | `'nowrap'` (default), `'wrap'`, `'clamp'`. |
|
|
135
|
+
| `lines` | Clamp height, default 2. |
|
|
136
|
+
| `hidden` | Removed from the visible set without losing its definition. |
|
|
133
137
|
|
|
134
138
|
## Row and column spans
|
|
135
139
|
|
|
@@ -139,15 +143,15 @@ in increasing power:
|
|
|
139
143
|
|
|
140
144
|
```ts
|
|
141
145
|
// 1. Row span by a shared key: consecutive rows with an equal value merge.
|
|
142
|
-
tableColumn<Invoice>().accessor('department', { header: 'Department', merge: 'equal' })
|
|
146
|
+
tableColumn<Invoice>().accessor('department', { header: 'Department', merge: 'equal' });
|
|
143
147
|
|
|
144
148
|
// 2. Column span: this cell covers N columns to its end.
|
|
145
|
-
tableColumn<Invoice>().accessor('note', { header: 'Notes', colSpan: 2 })
|
|
149
|
+
tableColumn<Invoice>().accessor('note', { header: 'Notes', colSpan: 2 });
|
|
146
150
|
|
|
147
151
|
// 3. Both axes, computed per cell.
|
|
148
152
|
tableColumn<Invoice>().display('summary', {
|
|
149
153
|
span: ({ rowIndex }) => (rowIndex === 0 ? { colSpan: 5 } : null),
|
|
150
|
-
})
|
|
154
|
+
});
|
|
151
155
|
```
|
|
152
156
|
|
|
153
157
|
Rules, enforced at `createTable()` rather than at render time:
|
|
@@ -181,10 +185,21 @@ createTable<Invoice>(() => ({
|
|
|
181
185
|
```ts
|
|
182
186
|
table.selection.toggle(row.id);
|
|
183
187
|
table.selection.isSelected(row.id);
|
|
184
|
-
table.selection.selectAllOnPage();
|
|
188
|
+
table.selection.selectAllOnPage(); // page scope, never "everything by accident"
|
|
185
189
|
table.selection.clear();
|
|
186
190
|
```
|
|
187
191
|
|
|
192
|
+
### Row identity contract
|
|
193
|
+
|
|
194
|
+
`getRowId` must return a unique, stable, non-empty string or finite number for
|
|
195
|
+
every row in the full active data set. Validation runs before filtering,
|
|
196
|
+
sorting, paging, or selection state is committed. In development, duplicate IDs
|
|
197
|
+
throw an error naming the ID and source row indices; invalid values name their
|
|
198
|
+
source index. In production, rows with ambiguous IDs remain visible, but
|
|
199
|
+
selection and other identity-dependent actions are disabled for those rows.
|
|
200
|
+
The table never substitutes an array index for an invalid ID. If rows have no
|
|
201
|
+
stable `id`, provide a `getRowId` backed by a persistent domain key.
|
|
202
|
+
|
|
188
203
|
## Paging and state
|
|
189
204
|
|
|
190
205
|
`page`, `pageSize`, `sorting`, `filters` and `globalSearch` are writable
|
|
@@ -271,3 +286,7 @@ Sizes are in `rem`, so the grid follows the browser's text size; hairlines stay
|
|
|
271
286
|
and spans are the 0.1.0 scope; server mode (`TableRequest`, `reload()`) and
|
|
272
287
|
editing (`@rivinia/table/editing`) are planned, additive, and not part of this
|
|
273
288
|
version.
|
|
289
|
+
|
|
290
|
+
## Further reading
|
|
291
|
+
|
|
292
|
+
[Package design and usage notes](https://github.com/abdelfattahqandil21-oss/Revinia/blob/table/v1.0.0/docs/specifications/table-design.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NgTemplateOutlet } from "@angular/common";
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
|
-
import { ChangeDetectionStrategy, Component, Directive, ElementRef, TemplateRef, afterRenderEffect, computed, contentChildren, inject, input, model, output, signal, viewChild } from "@angular/core";
|
|
3
|
+
import { ChangeDetectionStrategy, Component, Directive, ElementRef, TemplateRef, afterRenderEffect, computed, contentChildren, inject, input, isDevMode, model, output, signal, viewChild } from "@angular/core";
|
|
4
4
|
const TABLE_UI_LABELS_EN = {
|
|
5
5
|
grid: "Data grid",
|
|
6
6
|
loading: "Loading…",
|
|
@@ -26,7 +26,7 @@ var RvTableCell = class RvTableCell {
|
|
|
26
26
|
template = inject(TemplateRef);
|
|
27
27
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
28
28
|
minVersion: "12.0.0",
|
|
29
|
-
version: "22.2.
|
|
29
|
+
version: "22.2.3",
|
|
30
30
|
ngImport: i0,
|
|
31
31
|
type: RvTableCell,
|
|
32
32
|
deps: [],
|
|
@@ -34,7 +34,7 @@ var RvTableCell = class RvTableCell {
|
|
|
34
34
|
});
|
|
35
35
|
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
36
36
|
minVersion: "17.1.0",
|
|
37
|
-
version: "22.2.
|
|
37
|
+
version: "22.2.3",
|
|
38
38
|
type: RvTableCell,
|
|
39
39
|
isStandalone: true,
|
|
40
40
|
selector: "[rvTableCell]",
|
|
@@ -50,7 +50,7 @@ var RvTableCell = class RvTableCell {
|
|
|
50
50
|
};
|
|
51
51
|
i0.ɵɵngDeclareClassMetadata({
|
|
52
52
|
minVersion: "12.0.0",
|
|
53
|
-
version: "22.2.
|
|
53
|
+
version: "22.2.3",
|
|
54
54
|
ngImport: i0,
|
|
55
55
|
type: RvTableCell,
|
|
56
56
|
decorators: [{
|
|
@@ -135,17 +135,31 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
135
135
|
renderedRows = computed(() => {
|
|
136
136
|
const templates = this.cellTemplates();
|
|
137
137
|
const selected = this.selected();
|
|
138
|
-
|
|
138
|
+
const ids = /* @__PURE__ */ new Map();
|
|
139
|
+
const rendered = this.rows().map((row, rowIndex) => {
|
|
139
140
|
const id = this.rowId(row);
|
|
141
|
+
const validId = typeof id === "string" && id.trim().length > 0 || typeof id === "number" && Number.isFinite(id);
|
|
142
|
+
const positions = validId ? ids.get(id) ?? [] : [];
|
|
143
|
+
if (validId) {
|
|
144
|
+
positions.push(rowIndex);
|
|
145
|
+
ids.set(id, positions);
|
|
146
|
+
} else if (isDevMode()) throw new Error(`[rivinia/table/ui] Invalid row ID ${String(id)} at row index ${rowIndex}.`);
|
|
140
147
|
return {
|
|
141
148
|
id,
|
|
149
|
+
identityValid: row.identityValid !== false && validId,
|
|
142
150
|
row,
|
|
143
151
|
rowIndex,
|
|
144
152
|
ariaRowIndex: (Number.isFinite(row.index) ? row.index : rowIndex) + 2,
|
|
145
|
-
selected: selected.has(id),
|
|
153
|
+
selected: row.identityValid !== false && validId && selected.has(id),
|
|
146
154
|
cells: this.buildCells(row, rowIndex, templates)
|
|
147
155
|
};
|
|
148
156
|
});
|
|
157
|
+
for (const [id, positions] of ids) {
|
|
158
|
+
if (positions.length < 2) continue;
|
|
159
|
+
if (isDevMode()) throw new Error(`[rivinia/table/ui] Duplicate row ID ${JSON.stringify(id)} at row indices ${positions.join(", ")}.`);
|
|
160
|
+
for (const index of positions) rendered[index].identityValid = false;
|
|
161
|
+
}
|
|
162
|
+
return rendered;
|
|
149
163
|
}, ...ngDevMode ? [{ debugName: "renderedRows" }] : /* istanbul ignore next */ []);
|
|
150
164
|
skeletonList = computed(() => {
|
|
151
165
|
const requested = this.loading() ? this.skeletonRows() : 0;
|
|
@@ -255,7 +269,7 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
255
269
|
});
|
|
256
270
|
this.pendingFocus = false;
|
|
257
271
|
this.focusCell(item.rowIndex, cell.columnIndex);
|
|
258
|
-
if (this.selectable()) this.toggleRow(item
|
|
272
|
+
if (this.selectable()) this.toggleRow(item);
|
|
259
273
|
}
|
|
260
274
|
onCellFocus(row, column) {
|
|
261
275
|
const active = this.active();
|
|
@@ -316,7 +330,7 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
316
330
|
break;
|
|
317
331
|
case " ":
|
|
318
332
|
if (!this.selectable()) return;
|
|
319
|
-
this.toggleRow(rows[active.row]
|
|
333
|
+
this.toggleRow(rows[active.row]);
|
|
320
334
|
break;
|
|
321
335
|
default: return;
|
|
322
336
|
}
|
|
@@ -502,16 +516,16 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
502
516
|
const record = data;
|
|
503
517
|
return record === null || record === void 0 ? void 0 : record[accessor];
|
|
504
518
|
}
|
|
505
|
-
toggleRow(
|
|
506
|
-
if (!this.selectable()) return;
|
|
507
|
-
const id =
|
|
519
|
+
toggleRow(item) {
|
|
520
|
+
if (!this.selectable() || !item.identityValid) return;
|
|
521
|
+
const id = item.id;
|
|
508
522
|
const selected = new Set(this.selected());
|
|
509
523
|
if (selected.has(id)) selected.delete(id);
|
|
510
524
|
else selected.add(id);
|
|
511
525
|
this.selected.set(selected);
|
|
512
526
|
this.selectionChange.emit({
|
|
513
527
|
selected,
|
|
514
|
-
row
|
|
528
|
+
row: item.row
|
|
515
529
|
});
|
|
516
530
|
}
|
|
517
531
|
errorText(error) {
|
|
@@ -521,7 +535,7 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
521
535
|
}
|
|
522
536
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
523
537
|
minVersion: "12.0.0",
|
|
524
|
-
version: "22.2.
|
|
538
|
+
version: "22.2.3",
|
|
525
539
|
ngImport: i0,
|
|
526
540
|
type: RiviniaTableComponent,
|
|
527
541
|
deps: [],
|
|
@@ -529,7 +543,7 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
529
543
|
});
|
|
530
544
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
531
545
|
minVersion: "17.0.0",
|
|
532
|
-
version: "22.2.
|
|
546
|
+
version: "22.2.3",
|
|
533
547
|
type: RiviniaTableComponent,
|
|
534
548
|
isStandalone: true,
|
|
535
549
|
selector: "rv-table",
|
|
@@ -672,7 +686,7 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
672
686
|
isSignal: true
|
|
673
687
|
}],
|
|
674
688
|
ngImport: i0,
|
|
675
|
-
template: "<!--\n The screen-reader instructions live OUTSIDE the grid and are referenced by\n `aria-describedby`: a helper paragraph INSIDE a role=\"grid\" would be content\n the grid did not declare and would be announced as a row.\n-->\n<p class=\"rv-table-sr-only\" [id]=\"instructionsId\">{{ effectiveLabels().keyboardHelp }}</p>\n\n<div class=\"rv-table-scroller\" #scroll>\n <!--\n `min-inline-size` is the sum of the declared column widths and the column\n template is one custom property, so the header and the body cannot drift:\n they read the same value from this element.\n -->\n <div\n class=\"rv-table-grid\"\n role=\"grid\"\n [attr.aria-label]=\"ariaLabel() ?? hostLabel ?? effectiveLabels().grid\"\n [attr.aria-describedby]=\"instructionsId\"\n [attr.aria-rowcount]=\"rowCount()\"\n [attr.aria-colcount]=\"columnCount()\"\n [attr.aria-multiselectable]=\"selectable() ? 'true' : null\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n [style.--rv-table-columns]=\"columnTemplate()\"\n [style.min-inline-size]=\"minWidth()\"\n (keydown)=\"onKeydown($event)\"\n >\n <!--\n The header is its own rowgroup, so it can be sticky without the body\n participating in the same scroll offset. The row is the CSS grid: each\n header cell is a direct grid item at its column.\n -->\n <div class=\"rv-table-head\" role=\"rowgroup\">\n <div class=\"rv-table-row\" role=\"row\" aria-rowindex=\"1\">\n @for (column of visibleColumns(); track column.id; let index = $index) {\n <div\n class=\"rv-table-cell rv-table-head-cell\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"index + 1\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [attr.tabindex]=\"isActive(-1, index) ? 0 : -1\"\n [attr.data-rv-cell]=\"'-1:' + index\"\n [attr.data-sortable]=\"isSortable(column) ? 'true' : null\"\n [attr.data-sorted]=\"sortRank(column) > 0 ? 'true' : null\"\n [attr.data-sort-direction]=\"sortDirection(column)\"\n [attr.data-sort-rank]=\"sortRank(column) > 1 ? sortRank(column) : null\"\n [attr.data-align]=\"column.align ?? 'start'\"\n (click)=\"onHeaderClick(column, index, $event)\"\n (focus)=\"onCellFocus(-1, index)\"\n >\n <!--\n A real button, but NOT a tab stop: the cell owns the roving\n tabindex, so the grid keeps exactly one. Enter/Space on the cell\n sorts; the button keeps pointer and AT \"click\" working.\n -->\n <button type=\"button\" class=\"rv-table-head-button\" tabindex=\"-1\">\n <span class=\"rv-table-text rv-table-head-label\" dir=\"auto\">{{ column.header }}</span>\n @if (isSortable(column)) {\n <span\n class=\"rv-table-sort\"\n aria-hidden=\"true\"\n [attr.data-direction]=\"sortDirection(column) ?? 'none'\"\n >\n <svg viewBox=\"0 0 10 6\" width=\"10\" height=\"6\" focusable=\"false\">\n <path\n d=\"M1 5 5 1l4 4\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n @if (sortRank(column) > 1) {\n <span class=\"rv-table-sort-rank\" aria-hidden=\"true\">{{ sortRank(column) }}</span>\n }\n }\n </button>\n </div>\n }\n </div>\n </div>\n\n <!--\n One CSS grid for the whole body. A row wrapper is `display: contents`,\n so its cells are grid items of THIS grid and a real `grid-row: span N`\n can cross rows at any row height. Covered positions render no element.\n -->\n <div class=\"rv-table-body\" role=\"rowgroup\">\n @if (loading()) {\n @for (skeleton of skeletonList(); track $index; let rowIndex = $index) {\n <!-- Decorative: the polite status below announces the load once. -->\n <div class=\"rv-table-row rv-table-row--skeleton\" aria-hidden=\"true\">\n @for (column of visibleColumns(); track column.id; let columnIndex = $index) {\n <div\n class=\"rv-table-cell rv-table-skeleton\"\n [style.grid-row]=\"(rowIndex + 1) + ' / span 1'\"\n [style.grid-column]=\"(columnIndex + 1) + ' / span 1'\"\n [attr.data-col]=\"columnIndex\"\n >\n <span class=\"rv-table-skeleton-bar\"></span>\n </div>\n }\n </div>\n }\n } @else {\n @for (item of bodyRows(); track item.id) {\n <div\n class=\"rv-table-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"item.ariaRowIndex\"\n [attr.aria-selected]=\"selectable() ? (item.selected ? 'true' : 'false') : null\"\n [attr.data-selected]=\"item.selected ? 'true' : null\"\n [attr.data-row]=\"item.rowIndex\"\n (dblclick)=\"onRowDoubleClick(item, $event)\"\n >\n @for (cell of item.cells; track cell.columnIndex; let isFirst = $first) {\n <div\n class=\"rv-table-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"cell.columnIndex + 1\"\n [attr.aria-rowspan]=\"cell.rowSpan > 1 ? cell.rowSpan : null\"\n [attr.aria-colspan]=\"cell.colSpan > 1 ? cell.colSpan : null\"\n [attr.aria-describedby]=\"cell.describedBy\"\n [attr.tabindex]=\"isActive(item.rowIndex, cell.columnIndex) ? 0 : -1\"\n [attr.data-rv-cell]=\"item.rowIndex + ':' + cell.columnIndex\"\n [attr.data-row]=\"item.rowIndex\"\n [attr.data-col]=\"cell.columnIndex\"\n [attr.data-span]=\"cell.spanned ? 'true' : null\"\n [attr.data-align]=\"cell.column.align ?? 'start'\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.grid-row]=\"cell.gridRow\"\n [style.grid-column]=\"cell.gridColumn\"\n (click)=\"onCellClick(item, cell, $event)\"\n (focus)=\"onCellFocus(item.rowIndex, cell.columnIndex)\"\n >\n @if (selectable() && isFirst) {\n <!--\n Decorative, deliberately: `aria-selected` on the row is the\n semantics, and a second real checkbox would announce the\n same state twice. Clicking it bubbles to the cell, which is\n what toggles the row.\n -->\n <span\n class=\"rv-table-select\"\n aria-hidden=\"true\"\n [attr.data-checked]=\"item.selected ? 'true' : null\"\n ></span>\n }\n @if (cell.template; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"cell.context\"\n />\n } @else {\n <!--\n `dir=\"auto\"`: the grid's direction is the page's, but the\n VALUE's direction is the value's. Without it an English\n client name in an Arabic table renders with its full stop\n moved to the front (\".Nile Trading Co\"), which is correct\n bidi for an RTL paragraph and wrong for the data.\n -->\n <span\n class=\"rv-table-text\"\n dir=\"auto\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.--rv-table-clamp-lines]=\"cell.lines\"\n >{{ cell.text }}</span\n >\n }\n <!--\n The untruncated text, exposed through `aria-describedby` and\n hidden from the browse-order so it is not read twice. There is\n deliberately no `title`: the visual tooltip is the consumer's.\n -->\n @if (cell.describedBy) {\n <span class=\"rv-table-sr-only\" [id]=\"cell.describedBy\" aria-hidden=\"true\">{{\n cell.text\n }}</span>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n </div>\n\n <!--\n Inside the scroller (it belongs to the frame) but outside the grid (a grid\n may only own rows). `position: sticky` on the inline axis keeps it in view\n while the header scrolls horizontally.\n -->\n @if (state(); as current) {\n <div\n class=\"rv-table-state\"\n [attr.role]=\"current.kind === 'error' ? 'alert' : 'status'\"\n [attr.data-state]=\"current.kind\"\n >\n <span class=\"rv-table-state-art\" aria-hidden=\"true\">\n @if (current.kind === 'error') {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M12 7v6m0 4h.01M12 3l9 16H3l9-16Z\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M4 6h16M4 12h16M4 18h10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n />\n </svg>\n }\n </span>\n <p class=\"rv-table-state-text\">{{ current.message }}</p>\n </div>\n }\n</div>\n\n<!-- Always present, so the text change is announced by the live region. -->\n<p class=\"rv-table-sr-only\" role=\"status\">{{ loading() ? effectiveLabels().loading : '' }}</p>\n\n<!--\n The cell templates are never rendered here: they are looked up by column id\n and instantiated per row. Projecting them explicitly keeps the contract\n visible in the DOM (and avoids keeping unprojected content around).\n-->\n<ng-content select=\"[rvTableCell]\" />\n",
|
|
689
|
+
template: "<!--\n The screen-reader instructions live OUTSIDE the grid and are referenced by\n `aria-describedby`: a helper paragraph INSIDE a role=\"grid\" would be content\n the grid did not declare and would be announced as a row.\n-->\n<p class=\"rv-table-sr-only\" [id]=\"instructionsId\">{{ effectiveLabels().keyboardHelp }}</p>\n\n<div class=\"rv-table-scroller\" #scroll>\n <!--\n `min-inline-size` is the sum of the declared column widths and the column\n template is one custom property, so the header and the body cannot drift:\n they read the same value from this element.\n -->\n <div\n class=\"rv-table-grid\"\n role=\"grid\"\n [attr.aria-label]=\"ariaLabel() ?? hostLabel ?? effectiveLabels().grid\"\n [attr.aria-describedby]=\"instructionsId\"\n [attr.aria-rowcount]=\"rowCount()\"\n [attr.aria-colcount]=\"columnCount()\"\n [attr.aria-multiselectable]=\"selectable() ? 'true' : null\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n [style.--rv-table-columns]=\"columnTemplate()\"\n [style.min-inline-size]=\"minWidth()\"\n (keydown)=\"onKeydown($event)\"\n >\n <!--\n The header is its own rowgroup, so it can be sticky without the body\n participating in the same scroll offset. The row is the CSS grid: each\n header cell is a direct grid item at its column.\n -->\n <div class=\"rv-table-head\" role=\"rowgroup\">\n <div class=\"rv-table-row\" role=\"row\" aria-rowindex=\"1\">\n @for (column of visibleColumns(); track column.id; let index = $index) {\n <div\n class=\"rv-table-cell rv-table-head-cell\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"index + 1\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [attr.tabindex]=\"isActive(-1, index) ? 0 : -1\"\n [attr.data-rv-cell]=\"'-1:' + index\"\n [attr.data-sortable]=\"isSortable(column) ? 'true' : null\"\n [attr.data-sorted]=\"sortRank(column) > 0 ? 'true' : null\"\n [attr.data-sort-direction]=\"sortDirection(column)\"\n [attr.data-sort-rank]=\"sortRank(column) > 1 ? sortRank(column) : null\"\n [attr.data-align]=\"column.align ?? 'start'\"\n (click)=\"onHeaderClick(column, index, $event)\"\n (focus)=\"onCellFocus(-1, index)\"\n >\n <!--\n A real button, but NOT a tab stop: the cell owns the roving\n tabindex, so the grid keeps exactly one. Enter/Space on the cell\n sorts; the button keeps pointer and AT \"click\" working.\n -->\n <button type=\"button\" class=\"rv-table-head-button\" tabindex=\"-1\">\n <span class=\"rv-table-text rv-table-head-label\" dir=\"auto\">{{ column.header }}</span>\n @if (isSortable(column)) {\n <span\n class=\"rv-table-sort\"\n aria-hidden=\"true\"\n [attr.data-direction]=\"sortDirection(column) ?? 'none'\"\n >\n <svg viewBox=\"0 0 10 6\" width=\"10\" height=\"6\" focusable=\"false\">\n <path\n d=\"M1 5 5 1l4 4\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n @if (sortRank(column) > 1) {\n <span class=\"rv-table-sort-rank\" aria-hidden=\"true\">{{ sortRank(column) }}</span>\n }\n }\n </button>\n </div>\n }\n </div>\n </div>\n\n <!--\n One CSS grid for the whole body. A row wrapper is `display: contents`,\n so its cells are grid items of THIS grid and a real `grid-row: span N`\n can cross rows at any row height. Covered positions render no element.\n -->\n <div class=\"rv-table-body\" role=\"rowgroup\">\n @if (loading()) {\n @for (skeleton of skeletonList(); track $index; let rowIndex = $index) {\n <!-- Decorative: the polite status below announces the load once. -->\n <div class=\"rv-table-row rv-table-row--skeleton\" aria-hidden=\"true\">\n @for (column of visibleColumns(); track column.id; let columnIndex = $index) {\n <div\n class=\"rv-table-cell rv-table-skeleton\"\n [style.grid-row]=\"(rowIndex + 1) + ' / span 1'\"\n [style.grid-column]=\"(columnIndex + 1) + ' / span 1'\"\n [attr.data-col]=\"columnIndex\"\n >\n <span class=\"rv-table-skeleton-bar\"></span>\n </div>\n }\n </div>\n }\n } @else {\n @for (item of bodyRows(); track item.row) {\n <div\n class=\"rv-table-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"item.ariaRowIndex\"\n [attr.aria-selected]=\"selectable() && item.identityValid ? (item.selected ? 'true' : 'false') : null\"\n [attr.data-selected]=\"item.selected ? 'true' : null\"\n [attr.data-row]=\"item.rowIndex\"\n (dblclick)=\"onRowDoubleClick(item, $event)\"\n >\n @for (cell of item.cells; track cell.columnIndex; let isFirst = $first) {\n <div\n class=\"rv-table-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"cell.columnIndex + 1\"\n [attr.aria-rowspan]=\"cell.rowSpan > 1 ? cell.rowSpan : null\"\n [attr.aria-colspan]=\"cell.colSpan > 1 ? cell.colSpan : null\"\n [attr.aria-describedby]=\"cell.describedBy\"\n [attr.tabindex]=\"isActive(item.rowIndex, cell.columnIndex) ? 0 : -1\"\n [attr.data-rv-cell]=\"item.rowIndex + ':' + cell.columnIndex\"\n [attr.data-row]=\"item.rowIndex\"\n [attr.data-col]=\"cell.columnIndex\"\n [attr.data-span]=\"cell.spanned ? 'true' : null\"\n [attr.data-align]=\"cell.column.align ?? 'start'\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.grid-row]=\"cell.gridRow\"\n [style.grid-column]=\"cell.gridColumn\"\n (click)=\"onCellClick(item, cell, $event)\"\n (focus)=\"onCellFocus(item.rowIndex, cell.columnIndex)\"\n >\n @if (selectable() && isFirst) {\n <!--\n Decorative, deliberately: `aria-selected` on the row is the\n semantics, and a second real checkbox would announce the\n same state twice. Clicking it bubbles to the cell, which is\n what toggles the row.\n -->\n <span\n class=\"rv-table-select\"\n aria-hidden=\"true\"\n [attr.data-checked]=\"item.selected ? 'true' : null\"\n ></span>\n }\n @if (cell.template; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"cell.context\"\n />\n } @else {\n <!--\n `dir=\"auto\"`: the grid's direction is the page's, but the\n VALUE's direction is the value's. Without it an English\n client name in an Arabic table renders with its full stop\n moved to the front (\".Nile Trading Co\"), which is correct\n bidi for an RTL paragraph and wrong for the data.\n -->\n <span\n class=\"rv-table-text\"\n dir=\"auto\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.--rv-table-clamp-lines]=\"cell.lines\"\n >{{ cell.text }}</span\n >\n }\n <!--\n The untruncated text, exposed through `aria-describedby` and\n hidden from the browse-order so it is not read twice. There is\n deliberately no `title`: the visual tooltip is the consumer's.\n -->\n @if (cell.describedBy) {\n <span class=\"rv-table-sr-only\" [id]=\"cell.describedBy\" aria-hidden=\"true\">{{\n cell.text\n }}</span>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n </div>\n\n <!--\n Inside the scroller (it belongs to the frame) but outside the grid (a grid\n may only own rows). `position: sticky` on the inline axis keeps it in view\n while the header scrolls horizontally.\n -->\n @if (state(); as current) {\n <div\n class=\"rv-table-state\"\n [attr.role]=\"current.kind === 'error' ? 'alert' : 'status'\"\n [attr.data-state]=\"current.kind\"\n >\n <span class=\"rv-table-state-art\" aria-hidden=\"true\">\n @if (current.kind === 'error') {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M12 7v6m0 4h.01M12 3l9 16H3l9-16Z\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M4 6h16M4 12h16M4 18h10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n />\n </svg>\n }\n </span>\n <p class=\"rv-table-state-text\">{{ current.message }}</p>\n </div>\n }\n</div>\n\n<!-- Always present, so the text change is announced by the live region. -->\n<p class=\"rv-table-sr-only\" role=\"status\">{{ loading() ? effectiveLabels().loading : '' }}</p>\n\n<!--\n The cell templates are never rendered here: they are looked up by column id\n and instantiated per row. Projecting them explicitly keeps the contract\n visible in the DOM (and avoids keeping unprojected content around).\n-->\n<ng-content select=\"[rvTableCell]\" />\n",
|
|
676
690
|
styles: [":host{display:block;font-size:var(--rv-table-font-size, .875rem);color:var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5)));-webkit-text-size-adjust:100%}.rv-table-scroller,.rv-table-scroller *,.rv-table-scroller *:before,.rv-table-scroller *:after{box-sizing:border-box}.rv-table-scroller{position:relative;overflow:auto;max-block-size:var(--rv-table-max-height, none);overscroll-behavior:contain;border:1px solid var(--rv-table-border, var(--rv-border, light-dark(#e5e7eb, #2f2f36)));border-radius:var(--rv-table-radius, var(--rv-radius, .5rem));background:var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b)));scrollbar-width:thin;scrollbar-color:var(--rv-table-scrollbar, light-dark(#cbd5e1, #3f3f46)) transparent}.rv-table-grid{display:block}.rv-table-head{position:relative;z-index:2;background:var( --rv-table-header-bg, color-mix( in oklab, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) 92%, var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5))) ) );border-block-end:1px solid var(--rv-table-header-border, var(--rv-border, light-dark(#d1d5db, #3f3f46)))}:host([data-sticky=\"true\"]) .rv-table-head{position:sticky;inset-block-start:0}.rv-table-head .rv-table-row{display:grid;grid-template-columns:var(--rv-table-columns, 1fr)}.rv-table-head-cell{min-block-size:var(--rv-table-head-height, 2.75rem);border-block-end:0;color:var(--rv-table-header-fg, var(--rv-muted, light-dark(#52525b, #a1a1aa)));font-size:var(--rv-table-header-font-size, .75rem);font-weight:var(--rv-table-header-font-weight, 700);letter-spacing:.02em;-webkit-user-select:none;user-select:none}.rv-table-head-cell[data-sortable=true]{cursor:pointer}.rv-table-head-cell[data-sortable=true]:hover{background:var( --rv-table-header-hover, color-mix( in oklab, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) 86%, var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5))) ) );color:var(--rv-table-header-fg-active, var(--rv-fg, light-dark(#111827, #f4f4f5)))}.rv-table-head-cell[data-sorted=true]{color:var(--rv-table-header-fg-active, var(--rv-fg, light-dark(#111827, #f4f4f5)))}.rv-table-head-button{display:flex;align-items:center;gap:.375rem;inline-size:100%;min-inline-size:0;margin:0;padding:0;border:0;background:none;color:inherit;font:inherit;letter-spacing:inherit;text-align:inherit;cursor:inherit}.rv-table-head-button:focus{outline:none}.rv-table-head-label{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rv-table-sort{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;inline-size:1rem;block-size:1rem;color:var(--rv-table-sort-fg, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));opacity:0;transition:opacity .12s ease,transform .12s ease}.rv-table-head-cell[data-sortable=true]:hover .rv-table-sort{opacity:.45}.rv-table-head-cell[data-sorted=true] .rv-table-sort{opacity:1}.rv-table-sort[data-direction=desc]{transform:rotate(180deg)}.rv-table-sort-rank{display:inline-grid;flex:0 0 auto;place-items:center;min-inline-size:1rem;block-size:1rem;padding-inline:.2rem;border-radius:999px;background:color-mix(in oklab,var(--rv-table-sort-fg, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0))) 16%,transparent);color:var(--rv-table-sort-fg, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));font-size:.6875rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}.rv-table-body{display:grid;grid-template-columns:var(--rv-table-columns, 1fr);grid-auto-rows:minmax(var(--rv-table-row-height, 2.5rem),auto)}.rv-table-row{display:contents}.rv-table-cell{position:relative;display:flex;align-items:center;gap:.375rem;min-inline-size:0;padding:var(--rv-table-cell-padding, .5rem .75rem);border-block-end:1px solid var(--rv-table-border, var(--rv-border, light-dark(#e5e7eb, #2f2f36)));background:transparent;transition:background-color .12s ease}.rv-table-row:hover>.rv-table-cell{background:var( --rv-table-row-hover, color-mix( in oklab, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) 94%, var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5))) ) )}.rv-table-select{display:inline-grid;place-items:center;flex:none;inline-size:1rem;block-size:1rem;border:1px solid var(--rv-table-select-border, var(--rv-border, light-dark(#9ca3af, #52525b)));border-radius:var(--rv-table-select-radius, .25rem);background:var(--rv-table-select-bg, var(--rv-bg, light-dark(#ffffff, #17171b)));transition:background-color .12s ease,border-color .12s ease}.rv-table-select:after{content:\"\";inline-size:.5rem;block-size:.3rem;border-inline-start:2px solid var(--rv-table-select-mark, var(--rv-bg, light-dark(#ffffff, #17171b)));border-block-end:2px solid var(--rv-table-select-mark, var(--rv-bg, light-dark(#ffffff, #17171b)));transform:translateY(-.06rem) rotate(-45deg) scale(0);transition:transform .12s ease}.rv-table-select[data-checked=true]{border-color:var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));background:var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)))}.rv-table-select[data-checked=true]:after{transform:translateY(-.06rem) rotate(-45deg) scale(1)}@media(prefers-reduced-motion:reduce){.rv-table-select,.rv-table-select:after{transition:none}}.rv-table-row[aria-selected=true]>.rv-table-cell{background:var( --rv-table-row-selected, color-mix( in oklab, var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0))) 12%, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) ) )}.rv-table-row[aria-selected=true]>.rv-table-cell[data-col=\"0\"]:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;inline-size:var(--rv-table-selected-rail, 3px);background:var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)))}.rv-table-cell[data-align=center]{justify-content:center;text-align:center}.rv-table-cell[data-align=end]{justify-content:flex-end;text-align:end}.rv-table-cell[data-span]{align-items:flex-start}.rv-table-cell:focus{outline:none}.rv-table-cell:focus-visible{outline:2px solid var(--rv-table-focus, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));outline-offset:-2px;z-index:1}.rv-table-text{min-inline-size:0;max-inline-size:100%}.rv-table-text[data-overflow=nowrap]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.rv-table-text[data-overflow=wrap]{overflow-wrap:anywhere;white-space:normal}.rv-table-text[data-overflow=clamp]{display:-webkit-box;overflow:hidden;overflow-wrap:anywhere;-webkit-box-orient:vertical;-webkit-line-clamp:var(--rv-table-clamp-lines, 2);line-clamp:var(--rv-table-clamp-lines, 2)}.rv-table-skeleton{pointer-events:none}.rv-table-skeleton-bar{display:block;inline-size:64%;block-size:.625rem;border-radius:.3125rem;background:linear-gradient(90deg,var(--rv-table-skeleton, light-dark(#eceff3, #26262d)) 25%,var(--rv-table-skeleton-highlight, light-dark(#f6f8fa, #31313a)) 37%,var(--rv-table-skeleton, light-dark(#eceff3, #26262d)) 63%);background-size:400% 100%;animation:rv-table-shimmer 1.4s ease-in-out infinite}.rv-table-skeleton:nth-child(3n+2) .rv-table-skeleton-bar{inline-size:82%}.rv-table-skeleton:nth-child(3n) .rv-table-skeleton-bar{inline-size:46%}@keyframes rv-table-shimmer{0%{background-position:100% 0}to{background-position:0 0}}.rv-table-state{position:sticky;inset-inline-start:0;display:flex;flex-direction:column;align-items:center;gap:.5rem;inline-size:100%;padding:var(--rv-table-state-padding, 3rem 1.25rem);text-align:center;background:var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b)));color:var(--rv-table-state-fg, var(--rv-muted, light-dark(#52525b, #a1a1aa)))}.rv-table-state-art{display:grid;place-items:center;inline-size:3rem;block-size:3rem;border-radius:1rem;background:color-mix(in oklab,var(--rv-table-state-fg, var(--rv-muted, light-dark(#52525b, #a1a1aa))) 10%,transparent)}.rv-table-state[data-state=error] .rv-table-state-art{background:color-mix(in oklab,var(--rv-table-state-error, var(--rv-danger, light-dark(#dc2626, #f87171))) 14%,transparent);color:var(--rv-table-state-error, var(--rv-danger, light-dark(#dc2626, #f87171)))}.rv-table-state-text{margin:0;max-inline-size:34rem;color:var(--rv-table-state-text, var(--rv-fg, light-dark(#111827, #f4f4f5)));font-size:.9375rem;line-height:1.5}.rv-table-sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([data-density=\"compact\"]) .rv-table-body{grid-auto-rows:minmax(var(--rv-table-row-height, 2rem),auto)}:host([data-density=\"compact\"]) .rv-table-cell{padding:var(--rv-table-cell-padding, .25rem .5rem)}:host([data-density=\"compact\"]) .rv-table-head-cell{min-block-size:var(--rv-table-head-height, 2.25rem)}:host([data-density=\"compact\"]) .rv-table-skeleton-bar{block-size:.5rem}:host([data-density=\"comfortable\"]) .rv-table-body{grid-auto-rows:minmax(var(--rv-table-row-height, 3rem),auto)}:host([data-density=\"comfortable\"]) .rv-table-cell{padding:var(--rv-table-cell-padding, .75rem 1rem)}:host([data-density=\"comfortable\"]) .rv-table-head-cell{min-block-size:var(--rv-table-head-height, 3.25rem)}@media(prefers-reduced-motion:reduce){.rv-table-cell,.rv-table-sort{transition:none}.rv-table-skeleton-bar{animation:none}}@media(forced-colors:active){.rv-table-cell:focus-visible{outline-color:Highlight}.rv-table-skeleton-bar{background:GrayText}.rv-table-row[aria-selected=true]>.rv-table-cell[data-col=\"0\"]:before{background:Highlight}}\n"],
|
|
677
691
|
dependencies: [{
|
|
678
692
|
kind: "directive",
|
|
@@ -689,7 +703,7 @@ var RiviniaTableComponent = class RiviniaTableComponent {
|
|
|
689
703
|
};
|
|
690
704
|
i0.ɵɵngDeclareClassMetadata({
|
|
691
705
|
minVersion: "12.0.0",
|
|
692
|
-
version: "22.2.
|
|
706
|
+
version: "22.2.3",
|
|
693
707
|
ngImport: i0,
|
|
694
708
|
type: RiviniaTableComponent,
|
|
695
709
|
decorators: [{
|
|
@@ -704,7 +718,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
704
718
|
"[attr.data-sticky]": "stickyHeader() ? \"true\" : \"false\"",
|
|
705
719
|
"[attr.data-selectable]": "selectable() ? \"true\" : null"
|
|
706
720
|
},
|
|
707
|
-
template: "<!--\n The screen-reader instructions live OUTSIDE the grid and are referenced by\n `aria-describedby`: a helper paragraph INSIDE a role=\"grid\" would be content\n the grid did not declare and would be announced as a row.\n-->\n<p class=\"rv-table-sr-only\" [id]=\"instructionsId\">{{ effectiveLabels().keyboardHelp }}</p>\n\n<div class=\"rv-table-scroller\" #scroll>\n <!--\n `min-inline-size` is the sum of the declared column widths and the column\n template is one custom property, so the header and the body cannot drift:\n they read the same value from this element.\n -->\n <div\n class=\"rv-table-grid\"\n role=\"grid\"\n [attr.aria-label]=\"ariaLabel() ?? hostLabel ?? effectiveLabels().grid\"\n [attr.aria-describedby]=\"instructionsId\"\n [attr.aria-rowcount]=\"rowCount()\"\n [attr.aria-colcount]=\"columnCount()\"\n [attr.aria-multiselectable]=\"selectable() ? 'true' : null\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n [style.--rv-table-columns]=\"columnTemplate()\"\n [style.min-inline-size]=\"minWidth()\"\n (keydown)=\"onKeydown($event)\"\n >\n <!--\n The header is its own rowgroup, so it can be sticky without the body\n participating in the same scroll offset. The row is the CSS grid: each\n header cell is a direct grid item at its column.\n -->\n <div class=\"rv-table-head\" role=\"rowgroup\">\n <div class=\"rv-table-row\" role=\"row\" aria-rowindex=\"1\">\n @for (column of visibleColumns(); track column.id; let index = $index) {\n <div\n class=\"rv-table-cell rv-table-head-cell\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"index + 1\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [attr.tabindex]=\"isActive(-1, index) ? 0 : -1\"\n [attr.data-rv-cell]=\"'-1:' + index\"\n [attr.data-sortable]=\"isSortable(column) ? 'true' : null\"\n [attr.data-sorted]=\"sortRank(column) > 0 ? 'true' : null\"\n [attr.data-sort-direction]=\"sortDirection(column)\"\n [attr.data-sort-rank]=\"sortRank(column) > 1 ? sortRank(column) : null\"\n [attr.data-align]=\"column.align ?? 'start'\"\n (click)=\"onHeaderClick(column, index, $event)\"\n (focus)=\"onCellFocus(-1, index)\"\n >\n <!--\n A real button, but NOT a tab stop: the cell owns the roving\n tabindex, so the grid keeps exactly one. Enter/Space on the cell\n sorts; the button keeps pointer and AT \"click\" working.\n -->\n <button type=\"button\" class=\"rv-table-head-button\" tabindex=\"-1\">\n <span class=\"rv-table-text rv-table-head-label\" dir=\"auto\">{{ column.header }}</span>\n @if (isSortable(column)) {\n <span\n class=\"rv-table-sort\"\n aria-hidden=\"true\"\n [attr.data-direction]=\"sortDirection(column) ?? 'none'\"\n >\n <svg viewBox=\"0 0 10 6\" width=\"10\" height=\"6\" focusable=\"false\">\n <path\n d=\"M1 5 5 1l4 4\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n @if (sortRank(column) > 1) {\n <span class=\"rv-table-sort-rank\" aria-hidden=\"true\">{{ sortRank(column) }}</span>\n }\n }\n </button>\n </div>\n }\n </div>\n </div>\n\n <!--\n One CSS grid for the whole body. A row wrapper is `display: contents`,\n so its cells are grid items of THIS grid and a real `grid-row: span N`\n can cross rows at any row height. Covered positions render no element.\n -->\n <div class=\"rv-table-body\" role=\"rowgroup\">\n @if (loading()) {\n @for (skeleton of skeletonList(); track $index; let rowIndex = $index) {\n <!-- Decorative: the polite status below announces the load once. -->\n <div class=\"rv-table-row rv-table-row--skeleton\" aria-hidden=\"true\">\n @for (column of visibleColumns(); track column.id; let columnIndex = $index) {\n <div\n class=\"rv-table-cell rv-table-skeleton\"\n [style.grid-row]=\"(rowIndex + 1) + ' / span 1'\"\n [style.grid-column]=\"(columnIndex + 1) + ' / span 1'\"\n [attr.data-col]=\"columnIndex\"\n >\n <span class=\"rv-table-skeleton-bar\"></span>\n </div>\n }\n </div>\n }\n } @else {\n @for (item of bodyRows(); track item.id) {\n <div\n class=\"rv-table-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"item.ariaRowIndex\"\n [attr.aria-selected]=\"selectable() ? (item.selected ? 'true' : 'false') : null\"\n [attr.data-selected]=\"item.selected ? 'true' : null\"\n [attr.data-row]=\"item.rowIndex\"\n (dblclick)=\"onRowDoubleClick(item, $event)\"\n >\n @for (cell of item.cells; track cell.columnIndex; let isFirst = $first) {\n <div\n class=\"rv-table-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"cell.columnIndex + 1\"\n [attr.aria-rowspan]=\"cell.rowSpan > 1 ? cell.rowSpan : null\"\n [attr.aria-colspan]=\"cell.colSpan > 1 ? cell.colSpan : null\"\n [attr.aria-describedby]=\"cell.describedBy\"\n [attr.tabindex]=\"isActive(item.rowIndex, cell.columnIndex) ? 0 : -1\"\n [attr.data-rv-cell]=\"item.rowIndex + ':' + cell.columnIndex\"\n [attr.data-row]=\"item.rowIndex\"\n [attr.data-col]=\"cell.columnIndex\"\n [attr.data-span]=\"cell.spanned ? 'true' : null\"\n [attr.data-align]=\"cell.column.align ?? 'start'\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.grid-row]=\"cell.gridRow\"\n [style.grid-column]=\"cell.gridColumn\"\n (click)=\"onCellClick(item, cell, $event)\"\n (focus)=\"onCellFocus(item.rowIndex, cell.columnIndex)\"\n >\n @if (selectable() && isFirst) {\n <!--\n Decorative, deliberately: `aria-selected` on the row is the\n semantics, and a second real checkbox would announce the\n same state twice. Clicking it bubbles to the cell, which is\n what toggles the row.\n -->\n <span\n class=\"rv-table-select\"\n aria-hidden=\"true\"\n [attr.data-checked]=\"item.selected ? 'true' : null\"\n ></span>\n }\n @if (cell.template; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"cell.context\"\n />\n } @else {\n <!--\n `dir=\"auto\"`: the grid's direction is the page's, but the\n VALUE's direction is the value's. Without it an English\n client name in an Arabic table renders with its full stop\n moved to the front (\".Nile Trading Co\"), which is correct\n bidi for an RTL paragraph and wrong for the data.\n -->\n <span\n class=\"rv-table-text\"\n dir=\"auto\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.--rv-table-clamp-lines]=\"cell.lines\"\n >{{ cell.text }}</span\n >\n }\n <!--\n The untruncated text, exposed through `aria-describedby` and\n hidden from the browse-order so it is not read twice. There is\n deliberately no `title`: the visual tooltip is the consumer's.\n -->\n @if (cell.describedBy) {\n <span class=\"rv-table-sr-only\" [id]=\"cell.describedBy\" aria-hidden=\"true\">{{\n cell.text\n }}</span>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n </div>\n\n <!--\n Inside the scroller (it belongs to the frame) but outside the grid (a grid\n may only own rows). `position: sticky` on the inline axis keeps it in view\n while the header scrolls horizontally.\n -->\n @if (state(); as current) {\n <div\n class=\"rv-table-state\"\n [attr.role]=\"current.kind === 'error' ? 'alert' : 'status'\"\n [attr.data-state]=\"current.kind\"\n >\n <span class=\"rv-table-state-art\" aria-hidden=\"true\">\n @if (current.kind === 'error') {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M12 7v6m0 4h.01M12 3l9 16H3l9-16Z\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M4 6h16M4 12h16M4 18h10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n />\n </svg>\n }\n </span>\n <p class=\"rv-table-state-text\">{{ current.message }}</p>\n </div>\n }\n</div>\n\n<!-- Always present, so the text change is announced by the live region. -->\n<p class=\"rv-table-sr-only\" role=\"status\">{{ loading() ? effectiveLabels().loading : '' }}</p>\n\n<!--\n The cell templates are never rendered here: they are looked up by column id\n and instantiated per row. Projecting them explicitly keeps the contract\n visible in the DOM (and avoids keeping unprojected content around).\n-->\n<ng-content select=\"[rvTableCell]\" />\n",
|
|
721
|
+
template: "<!--\n The screen-reader instructions live OUTSIDE the grid and are referenced by\n `aria-describedby`: a helper paragraph INSIDE a role=\"grid\" would be content\n the grid did not declare and would be announced as a row.\n-->\n<p class=\"rv-table-sr-only\" [id]=\"instructionsId\">{{ effectiveLabels().keyboardHelp }}</p>\n\n<div class=\"rv-table-scroller\" #scroll>\n <!--\n `min-inline-size` is the sum of the declared column widths and the column\n template is one custom property, so the header and the body cannot drift:\n they read the same value from this element.\n -->\n <div\n class=\"rv-table-grid\"\n role=\"grid\"\n [attr.aria-label]=\"ariaLabel() ?? hostLabel ?? effectiveLabels().grid\"\n [attr.aria-describedby]=\"instructionsId\"\n [attr.aria-rowcount]=\"rowCount()\"\n [attr.aria-colcount]=\"columnCount()\"\n [attr.aria-multiselectable]=\"selectable() ? 'true' : null\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n [style.--rv-table-columns]=\"columnTemplate()\"\n [style.min-inline-size]=\"minWidth()\"\n (keydown)=\"onKeydown($event)\"\n >\n <!--\n The header is its own rowgroup, so it can be sticky without the body\n participating in the same scroll offset. The row is the CSS grid: each\n header cell is a direct grid item at its column.\n -->\n <div class=\"rv-table-head\" role=\"rowgroup\">\n <div class=\"rv-table-row\" role=\"row\" aria-rowindex=\"1\">\n @for (column of visibleColumns(); track column.id; let index = $index) {\n <div\n class=\"rv-table-cell rv-table-head-cell\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"index + 1\"\n [attr.aria-sort]=\"ariaSort(column)\"\n [attr.tabindex]=\"isActive(-1, index) ? 0 : -1\"\n [attr.data-rv-cell]=\"'-1:' + index\"\n [attr.data-sortable]=\"isSortable(column) ? 'true' : null\"\n [attr.data-sorted]=\"sortRank(column) > 0 ? 'true' : null\"\n [attr.data-sort-direction]=\"sortDirection(column)\"\n [attr.data-sort-rank]=\"sortRank(column) > 1 ? sortRank(column) : null\"\n [attr.data-align]=\"column.align ?? 'start'\"\n (click)=\"onHeaderClick(column, index, $event)\"\n (focus)=\"onCellFocus(-1, index)\"\n >\n <!--\n A real button, but NOT a tab stop: the cell owns the roving\n tabindex, so the grid keeps exactly one. Enter/Space on the cell\n sorts; the button keeps pointer and AT \"click\" working.\n -->\n <button type=\"button\" class=\"rv-table-head-button\" tabindex=\"-1\">\n <span class=\"rv-table-text rv-table-head-label\" dir=\"auto\">{{ column.header }}</span>\n @if (isSortable(column)) {\n <span\n class=\"rv-table-sort\"\n aria-hidden=\"true\"\n [attr.data-direction]=\"sortDirection(column) ?? 'none'\"\n >\n <svg viewBox=\"0 0 10 6\" width=\"10\" height=\"6\" focusable=\"false\">\n <path\n d=\"M1 5 5 1l4 4\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n @if (sortRank(column) > 1) {\n <span class=\"rv-table-sort-rank\" aria-hidden=\"true\">{{ sortRank(column) }}</span>\n }\n }\n </button>\n </div>\n }\n </div>\n </div>\n\n <!--\n One CSS grid for the whole body. A row wrapper is `display: contents`,\n so its cells are grid items of THIS grid and a real `grid-row: span N`\n can cross rows at any row height. Covered positions render no element.\n -->\n <div class=\"rv-table-body\" role=\"rowgroup\">\n @if (loading()) {\n @for (skeleton of skeletonList(); track $index; let rowIndex = $index) {\n <!-- Decorative: the polite status below announces the load once. -->\n <div class=\"rv-table-row rv-table-row--skeleton\" aria-hidden=\"true\">\n @for (column of visibleColumns(); track column.id; let columnIndex = $index) {\n <div\n class=\"rv-table-cell rv-table-skeleton\"\n [style.grid-row]=\"(rowIndex + 1) + ' / span 1'\"\n [style.grid-column]=\"(columnIndex + 1) + ' / span 1'\"\n [attr.data-col]=\"columnIndex\"\n >\n <span class=\"rv-table-skeleton-bar\"></span>\n </div>\n }\n </div>\n }\n } @else {\n @for (item of bodyRows(); track item.row) {\n <div\n class=\"rv-table-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"item.ariaRowIndex\"\n [attr.aria-selected]=\"selectable() && item.identityValid ? (item.selected ? 'true' : 'false') : null\"\n [attr.data-selected]=\"item.selected ? 'true' : null\"\n [attr.data-row]=\"item.rowIndex\"\n (dblclick)=\"onRowDoubleClick(item, $event)\"\n >\n @for (cell of item.cells; track cell.columnIndex; let isFirst = $first) {\n <div\n class=\"rv-table-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"cell.columnIndex + 1\"\n [attr.aria-rowspan]=\"cell.rowSpan > 1 ? cell.rowSpan : null\"\n [attr.aria-colspan]=\"cell.colSpan > 1 ? cell.colSpan : null\"\n [attr.aria-describedby]=\"cell.describedBy\"\n [attr.tabindex]=\"isActive(item.rowIndex, cell.columnIndex) ? 0 : -1\"\n [attr.data-rv-cell]=\"item.rowIndex + ':' + cell.columnIndex\"\n [attr.data-row]=\"item.rowIndex\"\n [attr.data-col]=\"cell.columnIndex\"\n [attr.data-span]=\"cell.spanned ? 'true' : null\"\n [attr.data-align]=\"cell.column.align ?? 'start'\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.grid-row]=\"cell.gridRow\"\n [style.grid-column]=\"cell.gridColumn\"\n (click)=\"onCellClick(item, cell, $event)\"\n (focus)=\"onCellFocus(item.rowIndex, cell.columnIndex)\"\n >\n @if (selectable() && isFirst) {\n <!--\n Decorative, deliberately: `aria-selected` on the row is the\n semantics, and a second real checkbox would announce the\n same state twice. Clicking it bubbles to the cell, which is\n what toggles the row.\n -->\n <span\n class=\"rv-table-select\"\n aria-hidden=\"true\"\n [attr.data-checked]=\"item.selected ? 'true' : null\"\n ></span>\n }\n @if (cell.template; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"cell.context\"\n />\n } @else {\n <!--\n `dir=\"auto\"`: the grid's direction is the page's, but the\n VALUE's direction is the value's. Without it an English\n client name in an Arabic table renders with its full stop\n moved to the front (\".Nile Trading Co\"), which is correct\n bidi for an RTL paragraph and wrong for the data.\n -->\n <span\n class=\"rv-table-text\"\n dir=\"auto\"\n [attr.data-overflow]=\"cell.overflow\"\n [style.--rv-table-clamp-lines]=\"cell.lines\"\n >{{ cell.text }}</span\n >\n }\n <!--\n The untruncated text, exposed through `aria-describedby` and\n hidden from the browse-order so it is not read twice. There is\n deliberately no `title`: the visual tooltip is the consumer's.\n -->\n @if (cell.describedBy) {\n <span class=\"rv-table-sr-only\" [id]=\"cell.describedBy\" aria-hidden=\"true\">{{\n cell.text\n }}</span>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n </div>\n\n <!--\n Inside the scroller (it belongs to the frame) but outside the grid (a grid\n may only own rows). `position: sticky` on the inline axis keeps it in view\n while the header scrolls horizontally.\n -->\n @if (state(); as current) {\n <div\n class=\"rv-table-state\"\n [attr.role]=\"current.kind === 'error' ? 'alert' : 'status'\"\n [attr.data-state]=\"current.kind\"\n >\n <span class=\"rv-table-state-art\" aria-hidden=\"true\">\n @if (current.kind === 'error') {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M12 7v6m0 4h.01M12 3l9 16H3l9-16Z\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" focusable=\"false\">\n <path\n d=\"M4 6h16M4 12h16M4 18h10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n />\n </svg>\n }\n </span>\n <p class=\"rv-table-state-text\">{{ current.message }}</p>\n </div>\n }\n</div>\n\n<!-- Always present, so the text change is announced by the live region. -->\n<p class=\"rv-table-sr-only\" role=\"status\">{{ loading() ? effectiveLabels().loading : '' }}</p>\n\n<!--\n The cell templates are never rendered here: they are looked up by column id\n and instantiated per row. Projecting them explicitly keeps the contract\n visible in the DOM (and avoids keeping unprojected content around).\n-->\n<ng-content select=\"[rvTableCell]\" />\n",
|
|
708
722
|
styles: [":host{display:block;font-size:var(--rv-table-font-size, .875rem);color:var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5)));-webkit-text-size-adjust:100%}.rv-table-scroller,.rv-table-scroller *,.rv-table-scroller *:before,.rv-table-scroller *:after{box-sizing:border-box}.rv-table-scroller{position:relative;overflow:auto;max-block-size:var(--rv-table-max-height, none);overscroll-behavior:contain;border:1px solid var(--rv-table-border, var(--rv-border, light-dark(#e5e7eb, #2f2f36)));border-radius:var(--rv-table-radius, var(--rv-radius, .5rem));background:var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b)));scrollbar-width:thin;scrollbar-color:var(--rv-table-scrollbar, light-dark(#cbd5e1, #3f3f46)) transparent}.rv-table-grid{display:block}.rv-table-head{position:relative;z-index:2;background:var( --rv-table-header-bg, color-mix( in oklab, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) 92%, var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5))) ) );border-block-end:1px solid var(--rv-table-header-border, var(--rv-border, light-dark(#d1d5db, #3f3f46)))}:host([data-sticky=\"true\"]) .rv-table-head{position:sticky;inset-block-start:0}.rv-table-head .rv-table-row{display:grid;grid-template-columns:var(--rv-table-columns, 1fr)}.rv-table-head-cell{min-block-size:var(--rv-table-head-height, 2.75rem);border-block-end:0;color:var(--rv-table-header-fg, var(--rv-muted, light-dark(#52525b, #a1a1aa)));font-size:var(--rv-table-header-font-size, .75rem);font-weight:var(--rv-table-header-font-weight, 700);letter-spacing:.02em;-webkit-user-select:none;user-select:none}.rv-table-head-cell[data-sortable=true]{cursor:pointer}.rv-table-head-cell[data-sortable=true]:hover{background:var( --rv-table-header-hover, color-mix( in oklab, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) 86%, var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5))) ) );color:var(--rv-table-header-fg-active, var(--rv-fg, light-dark(#111827, #f4f4f5)))}.rv-table-head-cell[data-sorted=true]{color:var(--rv-table-header-fg-active, var(--rv-fg, light-dark(#111827, #f4f4f5)))}.rv-table-head-button{display:flex;align-items:center;gap:.375rem;inline-size:100%;min-inline-size:0;margin:0;padding:0;border:0;background:none;color:inherit;font:inherit;letter-spacing:inherit;text-align:inherit;cursor:inherit}.rv-table-head-button:focus{outline:none}.rv-table-head-label{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rv-table-sort{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;inline-size:1rem;block-size:1rem;color:var(--rv-table-sort-fg, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));opacity:0;transition:opacity .12s ease,transform .12s ease}.rv-table-head-cell[data-sortable=true]:hover .rv-table-sort{opacity:.45}.rv-table-head-cell[data-sorted=true] .rv-table-sort{opacity:1}.rv-table-sort[data-direction=desc]{transform:rotate(180deg)}.rv-table-sort-rank{display:inline-grid;flex:0 0 auto;place-items:center;min-inline-size:1rem;block-size:1rem;padding-inline:.2rem;border-radius:999px;background:color-mix(in oklab,var(--rv-table-sort-fg, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0))) 16%,transparent);color:var(--rv-table-sort-fg, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));font-size:.6875rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}.rv-table-body{display:grid;grid-template-columns:var(--rv-table-columns, 1fr);grid-auto-rows:minmax(var(--rv-table-row-height, 2.5rem),auto)}.rv-table-row{display:contents}.rv-table-cell{position:relative;display:flex;align-items:center;gap:.375rem;min-inline-size:0;padding:var(--rv-table-cell-padding, .5rem .75rem);border-block-end:1px solid var(--rv-table-border, var(--rv-border, light-dark(#e5e7eb, #2f2f36)));background:transparent;transition:background-color .12s ease}.rv-table-row:hover>.rv-table-cell{background:var( --rv-table-row-hover, color-mix( in oklab, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) 94%, var(--rv-table-fg, var(--rv-fg, light-dark(#111827, #f4f4f5))) ) )}.rv-table-select{display:inline-grid;place-items:center;flex:none;inline-size:1rem;block-size:1rem;border:1px solid var(--rv-table-select-border, var(--rv-border, light-dark(#9ca3af, #52525b)));border-radius:var(--rv-table-select-radius, .25rem);background:var(--rv-table-select-bg, var(--rv-bg, light-dark(#ffffff, #17171b)));transition:background-color .12s ease,border-color .12s ease}.rv-table-select:after{content:\"\";inline-size:.5rem;block-size:.3rem;border-inline-start:2px solid var(--rv-table-select-mark, var(--rv-bg, light-dark(#ffffff, #17171b)));border-block-end:2px solid var(--rv-table-select-mark, var(--rv-bg, light-dark(#ffffff, #17171b)));transform:translateY(-.06rem) rotate(-45deg) scale(0);transition:transform .12s ease}.rv-table-select[data-checked=true]{border-color:var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));background:var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)))}.rv-table-select[data-checked=true]:after{transform:translateY(-.06rem) rotate(-45deg) scale(1)}@media(prefers-reduced-motion:reduce){.rv-table-select,.rv-table-select:after{transition:none}}.rv-table-row[aria-selected=true]>.rv-table-cell{background:var( --rv-table-row-selected, color-mix( in oklab, var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0))) 12%, var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b))) ) )}.rv-table-row[aria-selected=true]>.rv-table-cell[data-col=\"0\"]:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;inline-size:var(--rv-table-selected-rail, 3px);background:var(--rv-table-accent, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)))}.rv-table-cell[data-align=center]{justify-content:center;text-align:center}.rv-table-cell[data-align=end]{justify-content:flex-end;text-align:end}.rv-table-cell[data-span]{align-items:flex-start}.rv-table-cell:focus{outline:none}.rv-table-cell:focus-visible{outline:2px solid var(--rv-table-focus, var(--rv-accent, light-dark(#5b5bd6, #8b8bf0)));outline-offset:-2px;z-index:1}.rv-table-text{min-inline-size:0;max-inline-size:100%}.rv-table-text[data-overflow=nowrap]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.rv-table-text[data-overflow=wrap]{overflow-wrap:anywhere;white-space:normal}.rv-table-text[data-overflow=clamp]{display:-webkit-box;overflow:hidden;overflow-wrap:anywhere;-webkit-box-orient:vertical;-webkit-line-clamp:var(--rv-table-clamp-lines, 2);line-clamp:var(--rv-table-clamp-lines, 2)}.rv-table-skeleton{pointer-events:none}.rv-table-skeleton-bar{display:block;inline-size:64%;block-size:.625rem;border-radius:.3125rem;background:linear-gradient(90deg,var(--rv-table-skeleton, light-dark(#eceff3, #26262d)) 25%,var(--rv-table-skeleton-highlight, light-dark(#f6f8fa, #31313a)) 37%,var(--rv-table-skeleton, light-dark(#eceff3, #26262d)) 63%);background-size:400% 100%;animation:rv-table-shimmer 1.4s ease-in-out infinite}.rv-table-skeleton:nth-child(3n+2) .rv-table-skeleton-bar{inline-size:82%}.rv-table-skeleton:nth-child(3n) .rv-table-skeleton-bar{inline-size:46%}@keyframes rv-table-shimmer{0%{background-position:100% 0}to{background-position:0 0}}.rv-table-state{position:sticky;inset-inline-start:0;display:flex;flex-direction:column;align-items:center;gap:.5rem;inline-size:100%;padding:var(--rv-table-state-padding, 3rem 1.25rem);text-align:center;background:var(--rv-table-bg, var(--rv-bg, light-dark(#ffffff, #17171b)));color:var(--rv-table-state-fg, var(--rv-muted, light-dark(#52525b, #a1a1aa)))}.rv-table-state-art{display:grid;place-items:center;inline-size:3rem;block-size:3rem;border-radius:1rem;background:color-mix(in oklab,var(--rv-table-state-fg, var(--rv-muted, light-dark(#52525b, #a1a1aa))) 10%,transparent)}.rv-table-state[data-state=error] .rv-table-state-art{background:color-mix(in oklab,var(--rv-table-state-error, var(--rv-danger, light-dark(#dc2626, #f87171))) 14%,transparent);color:var(--rv-table-state-error, var(--rv-danger, light-dark(#dc2626, #f87171)))}.rv-table-state-text{margin:0;max-inline-size:34rem;color:var(--rv-table-state-text, var(--rv-fg, light-dark(#111827, #f4f4f5)));font-size:.9375rem;line-height:1.5}.rv-table-sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([data-density=\"compact\"]) .rv-table-body{grid-auto-rows:minmax(var(--rv-table-row-height, 2rem),auto)}:host([data-density=\"compact\"]) .rv-table-cell{padding:var(--rv-table-cell-padding, .25rem .5rem)}:host([data-density=\"compact\"]) .rv-table-head-cell{min-block-size:var(--rv-table-head-height, 2.25rem)}:host([data-density=\"compact\"]) .rv-table-skeleton-bar{block-size:.5rem}:host([data-density=\"comfortable\"]) .rv-table-body{grid-auto-rows:minmax(var(--rv-table-row-height, 3rem),auto)}:host([data-density=\"comfortable\"]) .rv-table-cell{padding:var(--rv-table-cell-padding, .75rem 1rem)}:host([data-density=\"comfortable\"]) .rv-table-head-cell{min-block-size:var(--rv-table-head-height, 3.25rem)}@media(prefers-reduced-motion:reduce){.rv-table-cell,.rv-table-sort{transition:none}.rv-table-skeleton-bar{animation:none}}@media(forced-colors:active){.rv-table-cell:focus-visible{outline-color:Highlight}.rv-table-skeleton-bar{background:GrayText}.rv-table-row[aria-selected=true]>.rv-table-cell[data-col=\"0\"]:before{background:Highlight}}\n"]
|
|
709
723
|
}]
|
|
710
724
|
}],
|