@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rivinia-table-ui.mjs","names":[],"sources":["../../../projects/table/ui/src/lib/models/ui.types.ts","../../../projects/table/ui/src/lib/components/rv-table/rv-table-cell.ts","../../../projects/table/ui/src/lib/components/rv-table/rv-table.ts","../../../projects/table/ui/src/lib/components/rv-table/rv-table.html"],"sourcesContent":["/**\n * The UI-only vocabulary of `@rivinia/table/ui`.\n *\n * The column, row, span and sort shapes are NOT redeclared here — they come\n * from `@rivinia/table` (single source of truth). This file only holds what a\n * renderer needs and the headless core could not know: density presets, the\n * text a grid shows when it has nothing to show, and the context handed to a\n * consumer's projected cell template.\n */\n\nimport type {\n CellSpan,\n TableRow,\n TableRowId,\n TextOverflow,\n} from '@rivinia/table';\n\n/**\n * The column shape the grid READS — a renderer-facing contract, not a copy of\n * the core type.\n *\n * Why not `TableColumn<any>`: that looks like the obvious \"some column\" type,\n * but it is not a supertype of a real one. Its accessor is\n * `(keyof any & string) | ((row: any) => V)` = `string | …`, and a concrete\n * `keyof Person & string` is narrower, so `TableColumn<Person>` would NOT be\n * assignable to `TableColumn<any>` — a consumer could not pass their own\n * typed columns at all. The same inversion repeats through `SpanContext`.\n *\n * So the callbacks here take `any` as their PARAMETER type (always assignable\n * in both directions) instead of `any` as the type argument. The result is a\n * genuine supertype: every `TableColumn<T>` is assignable to it.\n *\n * The renderer reads `id`, `header`, `accessor`, `format`, `sortable`,\n * `width`, `flex`, `align`, `hidden`, `textOverflow` and `lines`. The rest is\n * declared so a column literal typed as `TableUiColumn[]` can carry the full\n * core vocabulary without an excess-property error: spans are rendered from\n * `[spans]` (not from the column), `comparator` belongs to the core's sorting,\n * and pinning is not implemented by this entry point.\n */\nexport interface TableUiColumn {\n readonly id: string;\n readonly header?: string;\n readonly accessor?: string | ((row: any) => unknown);\n readonly sortable?: boolean;\n readonly width?: string;\n /** 0 = fixed; > 0 absorbs free space. */\n readonly flex?: number;\n readonly pinned?: 'start' | 'end';\n readonly align?: 'start' | 'center' | 'end';\n readonly format?: (value: any, row: any, locale: string) => string;\n readonly comparator?: (a: any, b: any) => number;\n readonly hidden?: boolean;\n readonly textOverflow?: TextOverflow;\n readonly lines?: number;\n readonly merge?: 'equal' | ((row: any, anchor: any) => boolean);\n readonly colSpan?: number | ((context: any) => number);\n readonly span?: (context: any) => CellSpan | null | undefined;\n}\n\n/** A row as the grid reads it. `TableRow<T>` has no variance, so this is safe. */\nexport type TableUiRow = TableRow<any>;\n\n/** Row height + padding preset. Token values per preset are in the CSS. */\nexport type TableUiDensity = 'compact' | 'default' | 'comfortable';\n\n/**\n * The text a grid shows on its own. Everything here is also a `[labels]`\n * override, so an Arabic page gets Arabic text without a second code path.\n */\nexport interface TableUiLabels {\n /** The grid's accessible name when `[ariaLabel]` is unset. */\n readonly grid: string;\n /** Announced while `[loading]` is true (the skeletons are decorative). */\n readonly loading: string;\n /** Fallback when `[error]` holds something that is not an `Error`/string. */\n readonly error: string;\n /** Used when `[emptyMessage]` is unset. */\n readonly empty: string;\n /** The grid's `aria-describedby`: what the keyboard does. */\n readonly keyboardHelp: string;\n}\n\nexport const TABLE_UI_LABELS_EN: TableUiLabels = {\n grid: 'Data grid',\n loading: 'Loading…',\n error: 'The data could not be loaded.',\n empty: 'No rows to show.',\n keyboardHelp:\n 'Use the arrow keys to move between cells, Home and End for the first and last cell of a row, Page Up and Page Down to move a screen at a time, Enter to open the focused row, and Space to select it.',\n};\n\nexport const TABLE_UI_LABELS_AR: TableUiLabels = {\n grid: 'شبكة بيانات',\n loading: 'جارٍ التحميل…',\n error: 'تعذّر تحميل البيانات.',\n empty: 'لا توجد صفوف لعرضها.',\n keyboardHelp:\n 'استخدم مفاتيح الأسهم للتنقل بين الخلايا، و Home و End لأول خلية وآخر خلية في الصف، و Page Up و Page Down للتنقل شاشةً كاملة، و Enter لفتح الصف الذي عليه التركيز، ومسافة لتحديده.',\n};\n\n/**\n * The label set for a locale, defaulting to English.\n *\n * It is a plain function (not a DI token) because it is pure and the UI entry\n * must stay usable without a provider — the same rule the core follows.\n */\nexport function tableUiLabels(locale: string | undefined): TableUiLabels {\n return locale?.toLowerCase().startsWith('ar')\n ? TABLE_UI_LABELS_AR\n : TABLE_UI_LABELS_EN;\n}\n\n/**\n * What a projected `[rvTableCell]` template receives.\n *\n * `$implicit` is the {@link TableRow} wrapper — the same shape the `rows`\n * input holds — so `let-row` is what the grid itself sees. `data` is the\n * consumer's own object, `value` is the accessor's resolved value BEFORE\n * `format`, and `column` is the column definition. The value is untrusted\n * (`unknown`) because the grid does not know the column's value type.\n *\n * ```html\n * <ng-template rvTableCell=\"actions\" let-row let-data=\"data\">\n * <button (click)=\"open(row.id)\">Open {{ data.number }}</button>\n * </ng-template>\n * ```\n */\nexport interface TableUiCellContext<T = unknown> {\n readonly $implicit: TableRow<T>;\n /** The same row under a named key, for templates that already destructure. */\n readonly row: TableRow<T>;\n /** The consumer's own object (`row.data`). */\n readonly data: T;\n /** The accessor's value before `column.format`. */\n readonly value: unknown;\n /** The renderer-facing column definition the grid rendered. */\n readonly column: TableUiColumn;\n /** Position inside the rendered page, not the index in the full data set. */\n readonly rowIndex: number;\n}\n\n/** Payload of `(selectionChange)`: the new set AND which row moved it. */\nexport interface TableUiSelectionChange<T = unknown> {\n readonly selected: ReadonlySet<TableRowId>;\n /** `null` when no single row caused the change (programmatic or clear-all). */\n readonly row: TableRow<T> | null;\n}\n","import { Directive, TemplateRef, inject, input } from '@angular/core';\n\nimport type { TableUiCellContext } from '../../models/ui.types';\n\n/**\n * `[rvTableCell]=\"columnId\"` — the consumer's renderer for one column.\n *\n * Put it on an `<ng-template>`; the grid never renders the template directly,\n * it asks for it BY COLUMN ID and instantiates it once per row. That is what\n * makes an actions column possible without the table knowing anything about\n * actions: the column list only declares geometry and a header, and the\n * markup decides what a cell contains.\n *\n * ```html\n * <rv-table [columns]=\"columns()\" [rows]=\"rows()\">\n * <ng-template rvTableCell=\"actions\" let-row let-data=\"data\">\n * <button type=\"button\" (click)=\"edit(data)\">Edit</button>\n * </ng-template>\n * </rv-table>\n * ```\n *\n * A column whose id has no template and no `accessor` renders an empty cell,\n * so declare the column for its width/header and supply the template here.\n * The template wins over `accessor` when both exist: markup is an override.\n *\n * The context is {@link TableUiCellContext} — `$implicit` (and `row`) is the\n * `TableRow`, `data` is your object, `value` is the accessor's value.\n */\n@Directive({ selector: '[rvTableCell]' })\nexport class RvTableCell {\n /**\n * The id of the column this template renders. It must match a\n * `TableColumn.id`; an id that matches nothing is simply never used (the\n * grid cannot throw at render time for a template it was given).\n */\n readonly columnId = input.required<string>({ alias: 'rvTableCell' });\n\n /** The template to instantiate. Injected from the `<ng-template>` host. */\n readonly template = inject(TemplateRef) as TemplateRef<TableUiCellContext>;\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n afterRenderEffect,\n computed,\n contentChildren,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport type { TemplateRef } from '@angular/core';\nimport type {\n SortDirection,\n SortState,\n SpanModel,\n TableColumn,\n TableRowId,\n TextOverflow,\n} from '@rivinia/table';\n\nimport type {\n TableUiCellContext,\n TableUiColumn,\n TableUiDensity,\n TableUiLabels,\n TableUiRow,\n TableUiSelectionChange,\n} from '../../models/ui.types';\nimport { tableUiLabels } from '../../models/ui.types';\nimport { RvTableCell } from './rv-table-cell';\n\n/** Per-instance counter: unique ids for the `aria-describedby` targets. */\nlet nextTableId = 0;\n\n/**\n * 160px at a 16px root, the design's default column width. A column with no\n * `width` is never zero-wide, which would make its header unreachable.\n */\nconst DEFAULT_COLUMN_WIDTH = '10rem';\n\n/** One cell as the template sees it — everything resolved, nothing computed there. */\ninterface RenderCell {\n readonly column: TableUiColumn;\n readonly columnIndex: number;\n readonly rowSpan: number;\n readonly colSpan: number;\n readonly spanned: boolean;\n readonly overflow: TextOverflow;\n /** `null` unless the column is a clamp column; drives `--rv-table-clamp-lines`. */\n readonly lines: number | null;\n readonly text: string;\n /** Id of the visually-hidden full-text node, when the cell can be cut off. */\n readonly describedBy: string | null;\n readonly template: TemplateRef<TableUiCellContext> | null;\n readonly context: TableUiCellContext;\n /** Explicit placement, so covered positions leave no auto-flow hole. */\n readonly gridRow: string;\n readonly gridColumn: string;\n}\n\ninterface RenderRow {\n /** The effective row id (`[rowKey]` applied): what selection and tracking use. */\n readonly id: TableRowId;\n readonly row: TableUiRow;\n /** Position inside the rendered page. */\n readonly rowIndex: number;\n /** 1-based position in the FULL data set; the header is row 1. */\n readonly ariaRowIndex: number;\n readonly selected: boolean;\n readonly cells: readonly RenderCell[];\n}\n\ninterface TableUiState {\n readonly kind: 'error' | 'empty';\n readonly message: string;\n}\n\n/**\n * `<rv-table>` — a `role=\"grid\"` data grid, built from divs on purpose.\n *\n * ## Why not `<table>`\n * An interactive grid is `role=\"grid\"`, not `role=\"table\"`, so the roles have\n * to be written either way. Once they are, a native table adds layout\n * negotiation (`table-layout`), inconsistent sticky headers and no way to span\n * a cell across ROWS without owning the row structure. Divs + CSS grid keep\n * the geometry explicit and the ARIA exact.\n *\n * ## The consumer decides what a cell contains\n * The column list carries geometry and a header; the CELL is either the\n * column's `accessor` (+ optional `format`) or — for an actions column, a\n * badge, an avatar — a projected template keyed by column id:\n *\n * ```html\n * <rv-table [columns]=\"columns()\" [rows]=\"rows()\" [spans]=\"spans()\" selectable>\n * <ng-template rvTableCell=\"actions\" let-row let-data=\"data\">\n * <button type=\"button\" (click)=\"edit(row.id)\">Edit {{ data.number }}</button>\n * </ng-template>\n * </rv-table>\n * ```\n *\n * The table never knows that \"actions\" means actions: a column with an id and\n * no `accessor` renders only the projected template. A template also WINS over\n * an accessor when both exist — markup is an override, not a second source of\n * truth for the column list (`[columns]` stays the only one).\n *\n * ## Spans\n * `[spans]` is the core's resolved `SpanModel`: only anchors are rendered,\n * covered positions have no element at all (nothing is hidden with a flag), and\n * each anchor is placed on the shared CSS grid with an explicit\n * `grid-row: <start> / span <n>` and `grid-column: <start> / span <n>`. Because\n * the spans are real grid spans, a merged cell survives wrapping rows and\n * needs no height arithmetic. `aria-rowspan` / `aria-colspan` are written only\n * when greater than 1, and every cell carries `aria-colindex` (WAI-ARIA\n * requires it once the grid is not a simple matrix).\n *\n * ## Keyboard\n * One tab stop (roving `tabindex`), moved with the arrows through\n * `spans.move()` — so moving INTO a merged cell lands on its anchor and moving\n * inside one leaves it. Home/End pick the first/last anchor of the row,\n * PageUp/PageDown move a screen, Enter activates the row, Space toggles\n * selection. Horizontal arrows mirror in RTL; vertical ones never do.\n *\n * ## Theming\n * Every colour and shape is read AT THE USE SITE as\n * `var(--rv-table-<part>, var(--rv-<semantic>, <default>))`, so one\n * `:root { --rv-bg: … }` reaches the grid. Nothing is declared on the host, so\n * a token set anywhere on an ancestor wins (the D42/D46 rule).\n * `[density]` only changes the FALLBACK of the size tokens, so an explicit\n * token still wins over the preset.\n *\n * The override surface is documented and stable:\n * classes `.rv-table-scroller`, `.rv-table-grid`, `.rv-table-head`,\n * `.rv-table-head-cell`, `.rv-table-head-button`, `.rv-table-head-label`,\n * `.rv-table-body`, `.rv-table-row`, `.rv-table-cell`, `.rv-table-text`,\n * `.rv-table-sort`, `.rv-table-sort-rank`, `.rv-table-state`,\n * `.rv-table-skeleton`, `.rv-table-sr-only`;\n * hooks `[data-density]`, `[data-loading]`, `[data-sticky]`, `[data-selectable]`\n * on the host, `[data-sortable]`, `[data-sorted]`, `[data-sort-rank]`,\n * `[data-sort-direction]` on a header cell, and `[data-selected]`,\n * `[data-span]`, `[data-align]`, `[data-overflow]`, `[data-row]`, `[data-col]`\n * on a body cell. Component styles are injected at runtime and carry the\n * component's scope attribute, so a plain `.rv-table-cell { … }` in an app\n * stylesheet can lose at equal specificity: scope it as\n * `rv-table .rv-table-cell { … }`, or use the tokens.\n */\n@Component({\n selector: 'rv-table',\n imports: [NgTemplateOutlet],\n templateUrl: './rv-table.html',\n styleUrl: './rv-table.css',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[attr.data-density]': 'density()',\n '[attr.data-loading]': 'loading() ? \"true\" : null',\n '[attr.data-sticky]': 'stickyHeader() ? \"true\" : \"false\"',\n '[attr.data-selectable]': 'selectable() ? \"true\" : null',\n },\n})\nexport class RiviniaTableComponent {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly scroller = viewChild<ElementRef<HTMLElement>>('scroll');\n private readonly projectedCells = contentChildren(RvTableCell);\n\n /** The columns, in render order. Hidden ones are filtered out, not counted. */\n readonly columns = input.required<readonly TableUiColumn[]>();\n /** The page's rows (already filtered/sorted/paged by the consumer or core). */\n readonly rows = input<readonly TableUiRow[]>([]);\n /** The core's resolved span matrix for `rows` + visible columns. */\n readonly spans = input<SpanModel | null>(null);\n /** While true the grid is `aria-busy`, renders skeletons and no data rows. */\n readonly loading = input(false);\n /**\n * How many skeleton rows to draw. `'auto'` (default) mirrors the rows that\n * are currently rendered, at least one — so the layout does not jump.\n */\n readonly skeletonRows = input<number | 'auto'>('auto');\n /** A failed load. `null`/`undefined` means \"no error\". */\n readonly error = input<unknown>(null);\n /** Text of the empty state. Unset falls back to the locale's label. */\n readonly emptyMessage = input<string | undefined>(undefined);\n /**\n * Derives the id used by selection, tracking and `(rowActivate)` from the\n * row's data. Unset uses `TableRow.id`, which is already stable.\n */\n readonly rowKey = input<((row: any) => TableRowId) | undefined>(undefined);\n /** Renders row selection: `aria-selected`, Space, and click-to-select. */\n readonly selectable = input(false);\n /**\n * The selected row ids. Two-way: `[(selected)]`. A `Set` (not an array)\n * because every row asks \"am I selected?\" on every render.\n */\n readonly selected = model<ReadonlySet<TableRowId>>(new Set<TableRowId>());\n /**\n * The active sort, highest priority first. Two-way: `[(sort)]`, and the\n * model's own `(sortChange)` fires whenever this component changes it.\n */\n readonly sort = model<readonly SortState[]>([]);\n readonly density = input<TableUiDensity>('default');\n /** Sticks the header to the top of the scroller. */\n readonly stickyHeader = input(true);\n /**\n * The number of rows in the FULL result set, for `aria-rowcount` when the\n * consumer pages on the server. Unset derives it from the rows' `index`,\n * which is exact when the grid renders the whole set.\n */\n readonly totalRows = input<number | undefined>(undefined);\n /** Accessible name of the grid (`aria-label`). */\n readonly ariaLabel = input<string | undefined>(undefined);\n /** Overrides for the built-in English/Arabic text. */\n readonly labels = input<Partial<TableUiLabels> | undefined>(undefined);\n\n /** Enter on a focused cell, or a double-click on a row. */\n readonly rowActivate = output<TableUiRow>();\n /**\n * A row was selected/deselected by the user. The model already carries the\n * new set (`selectedChange`); this output also names the row that moved.\n */\n readonly selectionChange = output<TableUiSelectionChange>();\n // `(sortChange)` is generated by the `sort` model — no duplicate output here.\n\n protected readonly instructionsId = `rv-table-help-${++nextTableId}`;\n private readonly uid = nextTableId;\n\n protected readonly visibleColumns = computed(() =>\n this.columns().filter((column) => column.hidden !== true),\n );\n\n protected readonly columnCount = computed(() => this.visibleColumns().length);\n\n /**\n * The ONE column geometry both the header and the body read.\n *\n * A flex column keeps its declared width as the track's FLOOR\n * (`minmax(<width>, Nfr)`, design §5.2): with `minmax(0, Nfr)` a narrow\n * container would collapse the column to zero and its content would vanish\n * instead of scrolling. Fixed columns are their width, untouched.\n */\n protected readonly columnTemplate = computed(() => {\n const tracks = this.visibleColumns().map((column) => {\n const width = column.width ?? DEFAULT_COLUMN_WIDTH;\n const flex = column.flex ?? 0;\n return flex > 0 ? `minmax(${width}, ${flex}fr)` : width;\n });\n return tracks.length > 0 ? tracks.join(' ') : '1fr';\n });\n\n /**\n * The grid's intrinsic width, so the scroller actually scrolls instead of\n * squeezing every column. It is the sum of the declared widths — which is\n * why a column width should be a length (`px`, `rem`, `ch`), not `max-content`.\n */\n protected readonly minWidth = computed(() => {\n const widths = this.visibleColumns().map(\n (column) => `(${column.width ?? DEFAULT_COLUMN_WIDTH})`,\n );\n return `calc(${widths.length > 0 ? widths.join(' + ') : '0px'})`;\n });\n\n /**\n * `aria-label` written on the HOST — `<rv-table aria-label=\"Invoices\">` — is\n * the attribute a consumer reaches for first, but the `role=\"grid\"` element\n * lives INSIDE the component, so the attribute would otherwise stop at the\n * host and the grid would keep its fallback name (\"Data grid\"). Read on every\n * change detection: it is a `getAttribute` call, and a dynamic label belongs\n * on the `[ariaLabel]` input, which is reactive.\n */\n protected get hostLabel(): string | undefined {\n const label = this.host.nativeElement.getAttribute('aria-label')?.trim();\n return label ? label : undefined;\n }\n\n protected readonly effectiveLabels = computed<TableUiLabels>(() => ({\n ...tableUiLabels(this.locale()),\n ...this.labels(),\n }));\n\n protected readonly locale = computed(() => {\n const pageLanguage =\n typeof document !== 'undefined' ? document.documentElement.lang : '';\n if (pageLanguage) return pageLanguage;\n const browserLanguage =\n typeof navigator !== 'undefined' ? navigator.language : '';\n return browserLanguage || 'en-US';\n });\n\n /** Column id → the consumer's template. Rebuilt only when content changes. */\n private readonly cellTemplates = computed(() => {\n const templates = new Map<string, TemplateRef<TableUiCellContext>>();\n for (const directive of this.projectedCells()) {\n templates.set(directive.columnId(), directive.template);\n }\n return templates;\n });\n\n /**\n * Sort lookup by column id: direction plus 1-based priority. One map instead\n * of a linear search per rendered header cell.\n */\n private readonly sortIndex = computed(() => {\n const index = new Map<string, { direction: SortDirection; rank: number }>();\n this.sort().forEach((entry, position) =>\n index.set(entry.column, { direction: entry.direction, rank: position + 1 }),\n );\n return index;\n });\n\n /** The fully resolved view model. Nothing in the template computes. */\n protected readonly renderedRows = computed<readonly RenderRow[]>(() => {\n const templates = this.cellTemplates();\n const selected = this.selected();\n return this.rows().map((row, rowIndex) => {\n const id = this.rowId(row);\n return {\n id,\n row,\n rowIndex,\n ariaRowIndex:\n (Number.isFinite(row.index) ? row.index : rowIndex) + 2,\n selected: selected.has(id),\n cells: this.buildCells(row, rowIndex, templates),\n };\n });\n });\n\n protected readonly skeletonList = computed(() => {\n const requested = this.loading() ? this.skeletonRows() : 0;\n const count =\n requested === 'auto' ? Math.max(1, this.rows().length) : requested;\n return Array.from({ length: Math.max(0, Math.floor(count)) });\n });\n\n protected readonly state = computed<TableUiState | null>(() => {\n if (this.loading()) return null;\n const error = this.error();\n if (error !== null && error !== undefined) {\n return { kind: 'error', message: this.errorText(error) };\n }\n if (this.rows().length === 0) {\n return {\n kind: 'empty',\n message: this.emptyMessage() ?? this.effectiveLabels().empty,\n };\n }\n return null;\n });\n\n /**\n * The rows that actually have DOM cells. While loading the skeletons replace\n * them, and an error/empty state replaces them too, so the keyboard must not\n * \"move\" into a row that has no element to focus.\n */\n protected readonly bodyRows = computed<readonly RenderRow[]>(() =>\n this.loading() || this.state() !== null ? [] : this.renderedRows(),\n );\n\n /**\n * The header row is row 1 (WAI-ARIA counts the header), so the grid's row\n * count is the data count + 1. `-1` is legal (\"unknown\"), but a wrong count\n * is worse than an honest one, so `[totalRows]` is the precise answer when\n * the consumer pages on the server.\n */\n protected readonly rowCount = computed(() => {\n const total = this.totalRows();\n if (total !== undefined) return Math.max(0, Math.floor(total)) + 1;\n const rows = this.rows();\n let highest = rows.length;\n for (const row of rows) {\n if (Number.isFinite(row.index) && row.index + 1 > highest) {\n highest = row.index + 1;\n }\n }\n return highest + 1;\n });\n\n // ── Active cell (roving tabindex) ─────────────────────────────────────────\n\n /** Row index in the rendered page; `-1` is the header row. */\n private readonly active = signal<{ readonly row: number; readonly column: number }>({\n row: -1,\n column: 0,\n });\n private pendingFocus = false;\n\n /**\n * The active cell, clamped to what is actually rendered. A stale row index\n * (the data shrank) would otherwise leave the grid with no tab stop at all.\n * While loading there are no data cells, so the header is the tab stop.\n */\n protected readonly effectiveActive = computed(() => {\n const active = this.active();\n const columns = this.columnCount();\n if (columns === 0) return { row: -1, column: 0 };\n const column = Math.min(Math.max(active.column, 0), columns - 1);\n const rowCount = this.bodyRows().length;\n if (active.row < 0 || rowCount === 0) return { row: -1, column };\n return { row: Math.min(active.row, rowCount - 1), column };\n });\n\n constructor() {\n // Focus is applied AFTER the DOM has the new tabindex; focusing during the\n // keydown would land on an element that is about to be re-rendered.\n afterRenderEffect(() => {\n const active = this.effectiveActive();\n this.columnTemplate();\n if (!this.pendingFocus) return;\n this.pendingFocus = false;\n this.focusCell(active.row, active.column);\n });\n }\n\n protected isActive(row: number, column: number): boolean {\n const active = this.effectiveActive();\n return active.row === row && active.column === column;\n }\n\n // ── Header + sorting ──────────────────────────────────────────────────────\n\n protected isSortable(column: TableUiColumn): boolean {\n // A display/actions column has no value to sort: opt in explicitly to\n // avoid \"sorting\" a column whose order would look arbitrary.\n return column.sortable ?? column.accessor !== undefined;\n }\n\n protected sortRank(column: TableUiColumn): number {\n return this.sortIndex().get(column.id)?.rank ?? 0;\n }\n\n protected sortDirection(column: TableUiColumn): SortDirection | null {\n return this.sortIndex().get(column.id)?.direction ?? null;\n }\n\n /**\n * `aria-sort` lives on exactly ONE header (the highest-priority sort), as\n * WAI-ARIA requires; secondary sorted columns are shown with a rank badge\n * and announce `none`, because the attribute has no \"secondary\" value.\n */\n protected ariaSort(\n column: TableUiColumn,\n ): 'ascending' | 'descending' | 'none' | null {\n if (!this.isSortable(column)) return null;\n const entry = this.sortIndex().get(column.id);\n if (!entry || entry.rank !== 1) return 'none';\n return entry.direction === 'asc' ? 'ascending' : 'descending';\n }\n\n protected onHeaderClick(\n column: TableUiColumn,\n columnIndex: number,\n event: MouseEvent,\n ): void {\n if (!this.isSortable(column)) return;\n this.active.set({ row: -1, column: columnIndex });\n this.pendingFocus = false;\n // The click landed on the inner button, which is `tabindex=\"-1\"`; park\n // focus on the CELL so the grid keeps exactly one tab stop.\n this.focusCell(-1, columnIndex);\n this.cycleSort(column, event.shiftKey);\n }\n\n // ── Rows + selection ──────────────────────────────────────────────────────\n\n protected rowId(row: TableUiRow): TableRowId {\n const key = this.rowKey();\n return key ? key(row.data) : row.id;\n }\n\n protected onRowActivate(item: RenderRow): void {\n this.rowActivate.emit(item.row);\n }\n\n /**\n * A double-click is a row activation. It is ignored when it lands on an\n * interactive element inside a projected template: the action the user asked\n * for (Edit, Open, a link) must not also fire the row's own activation.\n */\n protected onRowDoubleClick(item: RenderRow, event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n if (\n target?.closest(\n 'button, a, input, select, textarea, label, [contenteditable=\"true\"]',\n )\n ) {\n return;\n }\n this.onRowActivate(item);\n }\n\n protected onCellClick(\n item: RenderRow,\n cell: RenderCell,\n event: MouseEvent,\n ): void {\n const target = event.target as HTMLElement | null;\n // A button, link or input in a projected template owns its click: toggling\n // the row would fight the action the user actually asked for.\n if (\n target?.closest(\n 'button, a, input, select, textarea, label, [contenteditable=\"true\"]',\n )\n ) {\n return;\n }\n // A double-click is an activation; its second click must not undo the\n // selection the first one made.\n if (event.detail > 1) return;\n\n this.active.set({ row: item.rowIndex, column: cell.columnIndex });\n this.pendingFocus = false;\n this.focusCell(item.rowIndex, cell.columnIndex);\n if (this.selectable()) this.toggleRow(item.row);\n }\n\n protected onCellFocus(row: number, column: number): void {\n const active = this.active();\n if (active.row === row && active.column === column) return;\n // The user's own focus (click/tab) is already final: do not re-focus after\n // the render, which would fight `:focus-visible` and re-scroll the page.\n this.pendingFocus = false;\n this.active.set({ row, column });\n }\n\n // ── Keyboard ──────────────────────────────────────────────────────────────\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented) return;\n const target = event.target as HTMLElement | null;\n // Only the CELL owns the grid keys. Anything focusable inside a projected\n // template (a button, an input) keeps its native keyboard behaviour.\n if (!target || !target.hasAttribute('data-rv-cell')) return;\n\n const rows = this.bodyRows();\n const active = this.effectiveActive();\n const rtl = this.isRtl(target);\n\n if ((event.ctrlKey || event.metaKey) && (event.key === 'Home' || event.key === 'End')) {\n event.preventDefault();\n if (rows.length === 0) return;\n const row = event.key === 'Home' ? 0 : rows.length - 1;\n this.moveTo(row, event.key === 'Home' ? this.firstColumn(row) : this.lastColumn(row));\n return;\n }\n // Alt+Arrow is the browser's back/forward on some platforms; not ours yet.\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (active.row < 0) {\n this.onHeaderKey(event, active.column, rows.length);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n this.moveBy(1, 0);\n break;\n case 'ArrowUp':\n // The header is part of the grid, not decoration: up from the first\n // data row reaches the same column's header (and its sort action).\n if (active.row === 0) this.moveTo(-1, active.column);\n else this.moveBy(-1, 0);\n break;\n case 'ArrowLeft':\n this.moveBy(0, rtl ? 1 : -1);\n break;\n case 'ArrowRight':\n this.moveBy(0, rtl ? -1 : 1);\n break;\n case 'Home':\n this.moveTo(active.row, this.firstColumn(active.row));\n break;\n case 'End':\n this.moveTo(active.row, this.lastColumn(active.row));\n break;\n case 'PageDown':\n this.page(1);\n break;\n case 'PageUp':\n this.page(-1);\n break;\n case 'Enter':\n // Enter opens a data row; on a header it sorts (see onHeaderKey).\n this.onRowActivate(rows[active.row]);\n break;\n case ' ':\n if (!this.selectable()) return;\n this.toggleRow(rows[active.row].row);\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n\n private onHeaderKey(\n event: KeyboardEvent,\n column: number,\n rowCount: number,\n ): void {\n const rtl = this.isRtl(event.target as HTMLElement);\n switch (event.key) {\n case 'ArrowDown':\n if (rowCount > 0) this.moveTo(0, column);\n break;\n case 'ArrowLeft':\n this.moveTo(-1, column + (rtl ? 1 : -1));\n break;\n case 'ArrowRight':\n this.moveTo(-1, column + (rtl ? -1 : 1));\n break;\n case 'Home':\n this.moveTo(-1, 0);\n break;\n case 'End':\n this.moveTo(-1, this.columnCount() - 1);\n break;\n case 'Enter':\n case ' ': {\n const definition = this.visibleColumns()[column];\n if (definition) this.cycleSort(definition, event.shiftKey);\n break;\n }\n default:\n return;\n }\n event.preventDefault();\n }\n\n private cycleSort(column: TableUiColumn, additive: boolean): void {\n const current = this.sort();\n const position = current.findIndex((entry) => entry.column === column.id);\n const direction = position >= 0 ? current[position].direction : null;\n\n if (additive) {\n if (position < 0) {\n this.sort.set([...current, { column: column.id, direction: 'asc' }]);\n } else if (direction === 'asc') {\n this.sort.set(\n current.map((entry, index) =>\n index === position\n ? { column: entry.column, direction: 'desc' }\n : entry,\n ),\n );\n } else {\n this.sort.set(current.filter((_, index) => index !== position));\n }\n return;\n }\n\n // Clicking the only sorted column cycles asc -> desc -> unsorted.\n if (current.length === 1 && position === 0) {\n this.sort.set(\n direction === 'asc'\n ? [{ column: column.id, direction: 'desc' }]\n : [],\n );\n return;\n }\n // Any other click makes this column the ONLY sort.\n this.sort.set([\n { column: column.id, direction: direction === 'asc' ? 'desc' : 'asc' },\n ]);\n }\n\n /**\n * A page step stays inside the BODY. The header is reachable with ArrowUp,\n * but PageUp from the first data row must not surprise the user by landing on\n * the sort controls; page keys are a data-reading affordance.\n */\n private page(direction: 1 | -1): void {\n const rowCount = this.bodyRows().length;\n if (rowCount === 0) return;\n const active = this.effectiveActive();\n const room = direction > 0 ? rowCount - 1 - active.row : active.row;\n const step = direction * Math.min(this.pageRows(), Math.max(0, room));\n if (step === 0) return;\n this.moveBy(step, 0);\n }\n\n private moveBy(deltaRow: number, deltaColumn: number): void {\n const active = this.effectiveActive();\n const spans = this.spans();\n if (spans) {\n const target = spans.move(\n active.row,\n active.column,\n deltaRow,\n deltaColumn,\n );\n this.moveTo(target.row, target.column);\n return;\n }\n this.moveTo(active.row + deltaRow, active.column + deltaColumn);\n }\n\n /** Clamp to the page, then snap onto the anchor of whatever cell is there. */\n private moveTo(row: number, column: number): void {\n const rows = this.bodyRows().length;\n const columns = this.columnCount();\n column = Math.min(Math.max(column, 0), Math.max(columns - 1, 0));\n row = rows === 0 ? -1 : Math.min(Math.max(row, -1), rows - 1);\n\n const spans = this.spans();\n if (spans && row >= 0) {\n const anchor = spans.at(row, column) ?? spans.owner(row, column);\n if (anchor) {\n row = anchor.row;\n column = anchor.column;\n }\n }\n\n const active = this.effectiveActive();\n if (active.row === row && active.column === column) return;\n this.pendingFocus = true;\n this.active.set({ row, column });\n }\n\n private firstColumn(row: number): number {\n const spans = this.spans();\n if (!spans) return 0;\n for (let column = 0; column < this.columnCount(); column++) {\n if (spans.at(row, column) ?? spans.owner(row, column)) return column;\n }\n return 0;\n }\n\n private lastColumn(row: number): number {\n const spans = this.spans();\n const last = Math.max(this.columnCount() - 1, 0);\n if (!spans) return last;\n for (let column = last; column >= 0; column--) {\n if (spans.at(row, column) ?? spans.owner(row, column)) return column;\n }\n return last;\n }\n\n /** One screen minus one row; 10 when there is no layout to measure (SSR, jsdom). */\n private pageRows(): number {\n const viewport = this.scroller()?.nativeElement.clientHeight ?? 0;\n const cell = this.host.nativeElement.querySelector<HTMLElement>(\n '.rv-table-body .rv-table-cell',\n );\n const rowHeight = cell?.getBoundingClientRect().height ?? 0;\n if (!(viewport > 0) || !(rowHeight > 0)) return 10;\n return Math.max(1, Math.floor(viewport / rowHeight) - 1);\n }\n\n private focusCell(row: number, column: number): void {\n const element = this.host.nativeElement.querySelector<HTMLElement>(\n `[data-rv-cell=\"${row}:${column}\"]`,\n );\n element?.focus({ preventScroll: true });\n element?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });\n }\n\n /**\n * Direction is read from the nearest `[dir]` FIRST, then from computed\n * style: jsdom does not inherit computed direction, and `dir=\"auto\"` needs\n * the computed answer anyway.\n */\n private isRtl(element: HTMLElement): boolean {\n const declared = element.closest('[dir]')?.getAttribute('dir')?.toLowerCase();\n if (declared === 'rtl') return true;\n if (declared === 'ltr') return false;\n if (typeof getComputedStyle !== 'function') return false;\n return getComputedStyle(element).direction === 'rtl';\n }\n\n // ── Internals ─────────────────────────────────────────────────────────────\n\n private buildCells(\n row: TableUiRow,\n rowIndex: number,\n templates: ReadonlyMap<string, TemplateRef<TableUiCellContext>>,\n ): readonly RenderCell[] {\n const spans = this.spans();\n const cells: RenderCell[] = [];\n\n this.visibleColumns().forEach((column, columnIndex) => {\n let rowSpan = 1;\n let colSpan = 1;\n if (spans) {\n const anchor = spans.at(rowIndex, columnIndex);\n if (anchor) {\n rowSpan = Math.max(1, anchor.rowSpan);\n colSpan = Math.max(1, anchor.colSpan);\n } else if (spans.owner(rowIndex, columnIndex)) {\n // Covered by an earlier anchor: no element, no ARIA cell, no tab stop.\n return;\n }\n }\n\n const value = this.readValue(column, row.data);\n const template = templates.get(column.id) ?? null;\n const text = template\n ? ''\n : column.format\n ? column.format(value, row.data, this.locale())\n : value === null || value === undefined\n ? ''\n : String(value);\n\n const overflow: TextOverflow = column.textOverflow ?? 'nowrap';\n // `wrap` never cuts the text, so it needs no full-text description; the\n // other two modes may, and a hidden node is the only way to expose the\n // untruncated value without a `title` (the consumer owns the tooltip).\n const describedBy =\n text !== '' && overflow !== 'wrap'\n ? `rv-table-${this.uid}-full-${rowIndex}-${columnIndex}`\n : null;\n\n cells.push({\n column,\n columnIndex,\n rowSpan,\n colSpan,\n spanned: rowSpan > 1 || colSpan > 1,\n overflow,\n lines: overflow === 'clamp' ? (column.lines ?? 2) : null,\n text,\n describedBy,\n template,\n // CSS grid line numbers are 1-based, and every anchor sits exactly at\n // its matrix position, so placement is deterministic even when a span\n // above or beside it removed cells from the flow.\n gridRow: `${rowIndex + 1} / span ${rowSpan}`,\n gridColumn: `${columnIndex + 1} / span ${colSpan}`,\n context: {\n $implicit: row,\n row,\n data: row.data,\n value,\n column,\n rowIndex,\n },\n });\n });\n\n return cells;\n }\n\n private readValue(column: TableUiColumn, data: unknown): unknown {\n const accessor = column.accessor;\n if (accessor === undefined) return undefined;\n if (typeof accessor === 'function') return accessor(data);\n const record = data as Record<string, unknown> | null | undefined;\n return record === null || record === undefined ? undefined : record[accessor];\n }\n\n private toggleRow(row: TableUiRow): void {\n if (!this.selectable()) return;\n const id = this.rowId(row);\n const selected = new Set(this.selected());\n if (selected.has(id)) selected.delete(id);\n else selected.add(id);\n this.selected.set(selected);\n this.selectionChange.emit({ selected, row });\n }\n\n private errorText(error: unknown): string {\n if (error instanceof Error && error.message) return error.message;\n if (typeof error === 'string' && error) return error;\n return this.effectiveLabels().error;\n }\n}\n","<!--\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rivinia-table-ui.mjs","names":[],"sources":["../../../projects/table/ui/src/lib/models/ui.types.ts","../../../projects/table/ui/src/lib/components/rv-table/rv-table-cell.ts","../../../projects/table/ui/src/lib/components/rv-table/rv-table.ts","../../../projects/table/ui/src/lib/components/rv-table/rv-table.html"],"sourcesContent":["/**\n * The UI-only vocabulary of `@rivinia/table/ui`.\n *\n * The column, row, span and sort shapes are NOT redeclared here — they come\n * from `@rivinia/table` (single source of truth). This file only holds what a\n * renderer needs and the headless core could not know: density presets, the\n * text a grid shows when it has nothing to show, and the context handed to a\n * consumer's projected cell template.\n */\n\nimport type {\n CellSpan,\n TableRow,\n TableRowId,\n TextOverflow,\n} from '@rivinia/table';\n\n/**\n * The column shape the grid READS — a renderer-facing contract, not a copy of\n * the core type.\n *\n * Why not `TableColumn<any>`: that looks like the obvious \"some column\" type,\n * but it is not a supertype of a real one. Its accessor is\n * `(keyof any & string) | ((row: any) => V)` = `string | …`, and a concrete\n * `keyof Person & string` is narrower, so `TableColumn<Person>` would NOT be\n * assignable to `TableColumn<any>` — a consumer could not pass their own\n * typed columns at all. The same inversion repeats through `SpanContext`.\n *\n * So the callbacks here take `any` as their PARAMETER type (always assignable\n * in both directions) instead of `any` as the type argument. The result is a\n * genuine supertype: every `TableColumn<T>` is assignable to it.\n *\n * The renderer reads `id`, `header`, `accessor`, `format`, `sortable`,\n * `width`, `flex`, `align`, `hidden`, `textOverflow` and `lines`. The rest is\n * declared so a column literal typed as `TableUiColumn[]` can carry the full\n * core vocabulary without an excess-property error: spans are rendered from\n * `[spans]` (not from the column), `comparator` belongs to the core's sorting,\n * and pinning is not implemented by this entry point.\n */\nexport interface TableUiColumn {\n readonly id: string;\n readonly header?: string;\n readonly accessor?: string | ((row: any) => unknown);\n readonly sortable?: boolean;\n readonly width?: string;\n /** 0 = fixed; > 0 absorbs free space. */\n readonly flex?: number;\n readonly pinned?: 'start' | 'end';\n readonly align?: 'start' | 'center' | 'end';\n readonly format?: (value: any, row: any, locale: string) => string;\n readonly comparator?: (a: any, b: any) => number;\n readonly hidden?: boolean;\n readonly textOverflow?: TextOverflow;\n readonly lines?: number;\n readonly merge?: 'equal' | ((row: any, anchor: any) => boolean);\n readonly colSpan?: number | ((context: any) => number);\n readonly span?: (context: any) => CellSpan | null | undefined;\n}\n\n/** A row as the grid reads it. `TableRow<T>` has no variance, so this is safe. */\nexport type TableUiRow = TableRow<any>;\n\n/** Row height + padding preset. Token values per preset are in the CSS. */\nexport type TableUiDensity = 'compact' | 'default' | 'comfortable';\n\n/**\n * The text a grid shows on its own. Everything here is also a `[labels]`\n * override, so an Arabic page gets Arabic text without a second code path.\n */\nexport interface TableUiLabels {\n /** The grid's accessible name when `[ariaLabel]` is unset. */\n readonly grid: string;\n /** Announced while `[loading]` is true (the skeletons are decorative). */\n readonly loading: string;\n /** Fallback when `[error]` holds something that is not an `Error`/string. */\n readonly error: string;\n /** Used when `[emptyMessage]` is unset. */\n readonly empty: string;\n /** The grid's `aria-describedby`: what the keyboard does. */\n readonly keyboardHelp: string;\n}\n\nexport const TABLE_UI_LABELS_EN: TableUiLabels = {\n grid: 'Data grid',\n loading: 'Loading…',\n error: 'The data could not be loaded.',\n empty: 'No rows to show.',\n keyboardHelp:\n 'Use the arrow keys to move between cells, Home and End for the first and last cell of a row, Page Up and Page Down to move a screen at a time, Enter to open the focused row, and Space to select it.',\n};\n\nexport const TABLE_UI_LABELS_AR: TableUiLabels = {\n grid: 'شبكة بيانات',\n loading: 'جارٍ التحميل…',\n error: 'تعذّر تحميل البيانات.',\n empty: 'لا توجد صفوف لعرضها.',\n keyboardHelp:\n 'استخدم مفاتيح الأسهم للتنقل بين الخلايا، و Home و End لأول خلية وآخر خلية في الصف، و Page Up و Page Down للتنقل شاشةً كاملة، و Enter لفتح الصف الذي عليه التركيز، ومسافة لتحديده.',\n};\n\n/**\n * The label set for a locale, defaulting to English.\n *\n * It is a plain function (not a DI token) because it is pure and the UI entry\n * must stay usable without a provider — the same rule the core follows.\n */\nexport function tableUiLabels(locale: string | undefined): TableUiLabels {\n return locale?.toLowerCase().startsWith('ar')\n ? TABLE_UI_LABELS_AR\n : TABLE_UI_LABELS_EN;\n}\n\n/**\n * What a projected `[rvTableCell]` template receives.\n *\n * `$implicit` is the {@link TableRow} wrapper — the same shape the `rows`\n * input holds — so `let-row` is what the grid itself sees. `data` is the\n * consumer's own object, `value` is the accessor's resolved value BEFORE\n * `format`, and `column` is the column definition. The value is untrusted\n * (`unknown`) because the grid does not know the column's value type.\n *\n * ```html\n * <ng-template rvTableCell=\"actions\" let-row let-data=\"data\">\n * <button (click)=\"open(row.id)\">Open {{ data.number }}</button>\n * </ng-template>\n * ```\n */\nexport interface TableUiCellContext<T = unknown> {\n readonly $implicit: TableRow<T>;\n /** The same row under a named key, for templates that already destructure. */\n readonly row: TableRow<T>;\n /** The consumer's own object (`row.data`). */\n readonly data: T;\n /** The accessor's value before `column.format`. */\n readonly value: unknown;\n /** The renderer-facing column definition the grid rendered. */\n readonly column: TableUiColumn;\n /** Position inside the rendered page, not the index in the full data set. */\n readonly rowIndex: number;\n}\n\n/** Payload of `(selectionChange)`: the new set AND which row moved it. */\nexport interface TableUiSelectionChange<T = unknown> {\n readonly selected: ReadonlySet<TableRowId>;\n /** `null` when no single row caused the change (programmatic or clear-all). */\n readonly row: TableRow<T> | null;\n}\n","import { Directive, TemplateRef, inject, input } from '@angular/core';\n\nimport type { TableUiCellContext } from '../../models/ui.types';\n\n/**\n * `[rvTableCell]=\"columnId\"` — the consumer's renderer for one column.\n *\n * Put it on an `<ng-template>`; the grid never renders the template directly,\n * it asks for it BY COLUMN ID and instantiates it once per row. That is what\n * makes an actions column possible without the table knowing anything about\n * actions: the column list only declares geometry and a header, and the\n * markup decides what a cell contains.\n *\n * ```html\n * <rv-table [columns]=\"columns()\" [rows]=\"rows()\">\n * <ng-template rvTableCell=\"actions\" let-row let-data=\"data\">\n * <button type=\"button\" (click)=\"edit(data)\">Edit</button>\n * </ng-template>\n * </rv-table>\n * ```\n *\n * A column whose id has no template and no `accessor` renders an empty cell,\n * so declare the column for its width/header and supply the template here.\n * The template wins over `accessor` when both exist: markup is an override.\n *\n * The context is {@link TableUiCellContext} — `$implicit` (and `row`) is the\n * `TableRow`, `data` is your object, `value` is the accessor's value.\n */\n@Directive({ selector: '[rvTableCell]' })\nexport class RvTableCell {\n /**\n * The id of the column this template renders. It must match a\n * `TableColumn.id`; an id that matches nothing is simply never used (the\n * grid cannot throw at render time for a template it was given).\n */\n readonly columnId = input.required<string>({ alias: 'rvTableCell' });\n\n /** The template to instantiate. Injected from the `<ng-template>` host. */\n readonly template = inject(TemplateRef) as TemplateRef<TableUiCellContext>;\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n isDevMode,\n afterRenderEffect,\n computed,\n contentChildren,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport type { TemplateRef } from '@angular/core';\nimport type {\n SortDirection,\n SortState,\n SpanModel,\n TableColumn,\n TableRowId,\n TextOverflow,\n} from '@rivinia/table';\n\nimport type {\n TableUiCellContext,\n TableUiColumn,\n TableUiDensity,\n TableUiLabels,\n TableUiRow,\n TableUiSelectionChange,\n} from '../../models/ui.types';\nimport { tableUiLabels } from '../../models/ui.types';\nimport { RvTableCell } from './rv-table-cell';\n\n/** Per-instance counter: unique ids for the `aria-describedby` targets. */\nlet nextTableId = 0;\n\n/**\n * 160px at a 16px root, the design's default column width. A column with no\n * `width` is never zero-wide, which would make its header unreachable.\n */\nconst DEFAULT_COLUMN_WIDTH = '10rem';\n\n/** One cell as the template sees it — everything resolved, nothing computed there. */\ninterface RenderCell {\n readonly column: TableUiColumn;\n readonly columnIndex: number;\n readonly rowSpan: number;\n readonly colSpan: number;\n readonly spanned: boolean;\n readonly overflow: TextOverflow;\n /** `null` unless the column is a clamp column; drives `--rv-table-clamp-lines`. */\n readonly lines: number | null;\n readonly text: string;\n /** Id of the visually-hidden full-text node, when the cell can be cut off. */\n readonly describedBy: string | null;\n readonly template: TemplateRef<TableUiCellContext> | null;\n readonly context: TableUiCellContext;\n /** Explicit placement, so covered positions leave no auto-flow hole. */\n readonly gridRow: string;\n readonly gridColumn: string;\n}\n\ninterface RenderRow {\n /** The effective row id (`[rowKey]` applied): what selection refers to. */\n readonly id: TableRowId;\n identityValid: boolean;\n readonly row: TableUiRow;\n /** Position inside the rendered page. */\n readonly rowIndex: number;\n /** 1-based position in the FULL data set; the header is row 1. */\n readonly ariaRowIndex: number;\n readonly selected: boolean;\n readonly cells: readonly RenderCell[];\n}\n\ninterface TableUiState {\n readonly kind: 'error' | 'empty';\n readonly message: string;\n}\n\n/**\n * `<rv-table>` — a `role=\"grid\"` data grid, built from divs on purpose.\n *\n * ## Why not `<table>`\n * An interactive grid is `role=\"grid\"`, not `role=\"table\"`, so the roles have\n * to be written either way. Once they are, a native table adds layout\n * negotiation (`table-layout`), inconsistent sticky headers and no way to span\n * a cell across ROWS without owning the row structure. Divs + CSS grid keep\n * the geometry explicit and the ARIA exact.\n *\n * ## The consumer decides what a cell contains\n * The column list carries geometry and a header; the CELL is either the\n * column's `accessor` (+ optional `format`) or — for an actions column, a\n * badge, an avatar — a projected template keyed by column id:\n *\n * ```html\n * <rv-table [columns]=\"columns()\" [rows]=\"rows()\" [spans]=\"spans()\" selectable>\n * <ng-template rvTableCell=\"actions\" let-row let-data=\"data\">\n * <button type=\"button\" (click)=\"edit(row.id)\">Edit {{ data.number }}</button>\n * </ng-template>\n * </rv-table>\n * ```\n *\n * The table never knows that \"actions\" means actions: a column with an id and\n * no `accessor` renders only the projected template. A template also WINS over\n * an accessor when both exist — markup is an override, not a second source of\n * truth for the column list (`[columns]` stays the only one).\n *\n * ## Spans\n * `[spans]` is the core's resolved `SpanModel`: only anchors are rendered,\n * covered positions have no element at all (nothing is hidden with a flag), and\n * each anchor is placed on the shared CSS grid with an explicit\n * `grid-row: <start> / span <n>` and `grid-column: <start> / span <n>`. Because\n * the spans are real grid spans, a merged cell survives wrapping rows and\n * needs no height arithmetic. `aria-rowspan` / `aria-colspan` are written only\n * when greater than 1, and every cell carries `aria-colindex` (WAI-ARIA\n * requires it once the grid is not a simple matrix).\n *\n * ## Keyboard\n * One tab stop (roving `tabindex`), moved with the arrows through\n * `spans.move()` — so moving INTO a merged cell lands on its anchor and moving\n * inside one leaves it. Home/End pick the first/last anchor of the row,\n * PageUp/PageDown move a screen, Enter activates the row, Space toggles\n * selection. Horizontal arrows mirror in RTL; vertical ones never do.\n *\n * ## Theming\n * Every colour and shape is read AT THE USE SITE as\n * `var(--rv-table-<part>, var(--rv-<semantic>, <default>))`, so one\n * `:root { --rv-bg: … }` reaches the grid. Nothing is declared on the host, so\n * a token set anywhere on an ancestor wins (the D42/D46 rule).\n * `[density]` only changes the FALLBACK of the size tokens, so an explicit\n * token still wins over the preset.\n *\n * The override surface is documented and stable:\n * classes `.rv-table-scroller`, `.rv-table-grid`, `.rv-table-head`,\n * `.rv-table-head-cell`, `.rv-table-head-button`, `.rv-table-head-label`,\n * `.rv-table-body`, `.rv-table-row`, `.rv-table-cell`, `.rv-table-text`,\n * `.rv-table-sort`, `.rv-table-sort-rank`, `.rv-table-state`,\n * `.rv-table-skeleton`, `.rv-table-sr-only`;\n * hooks `[data-density]`, `[data-loading]`, `[data-sticky]`, `[data-selectable]`\n * on the host, `[data-sortable]`, `[data-sorted]`, `[data-sort-rank]`,\n * `[data-sort-direction]` on a header cell, and `[data-selected]`,\n * `[data-span]`, `[data-align]`, `[data-overflow]`, `[data-row]`, `[data-col]`\n * on a body cell. Component styles are injected at runtime and carry the\n * component's scope attribute, so a plain `.rv-table-cell { … }` in an app\n * stylesheet can lose at equal specificity: scope it as\n * `rv-table .rv-table-cell { … }`, or use the tokens.\n */\n@Component({\n selector: 'rv-table',\n imports: [NgTemplateOutlet],\n templateUrl: './rv-table.html',\n styleUrl: './rv-table.css',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[attr.data-density]': 'density()',\n '[attr.data-loading]': 'loading() ? \"true\" : null',\n '[attr.data-sticky]': 'stickyHeader() ? \"true\" : \"false\"',\n '[attr.data-selectable]': 'selectable() ? \"true\" : null',\n },\n})\nexport class RiviniaTableComponent {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly scroller = viewChild<ElementRef<HTMLElement>>('scroll');\n private readonly projectedCells = contentChildren(RvTableCell);\n\n /** The columns, in render order. Hidden ones are filtered out, not counted. */\n readonly columns = input.required<readonly TableUiColumn[]>();\n /** The page's rows (already filtered/sorted/paged by the consumer or core). */\n readonly rows = input<readonly TableUiRow[]>([]);\n /** The core's resolved span matrix for `rows` + visible columns. */\n readonly spans = input<SpanModel | null>(null);\n /** While true the grid is `aria-busy`, renders skeletons and no data rows. */\n readonly loading = input(false);\n /**\n * How many skeleton rows to draw. `'auto'` (default) mirrors the rows that\n * are currently rendered, at least one — so the layout does not jump.\n */\n readonly skeletonRows = input<number | 'auto'>('auto');\n /** A failed load. `null`/`undefined` means \"no error\". */\n readonly error = input<unknown>(null);\n /** Text of the empty state. Unset falls back to the locale's label. */\n readonly emptyMessage = input<string | undefined>(undefined);\n /**\n * Derives the id used by selection, tracking and `(rowActivate)` from the\n * row's data. Unset uses `TableRow.id`, which is already stable.\n */\n readonly rowKey = input<((row: any) => TableRowId) | undefined>(undefined);\n /** Renders row selection: `aria-selected`, Space, and click-to-select. */\n readonly selectable = input(false);\n /**\n * The selected row ids. Two-way: `[(selected)]`. A `Set` (not an array)\n * because every row asks \"am I selected?\" on every render.\n */\n readonly selected = model<ReadonlySet<TableRowId>>(new Set<TableRowId>());\n /**\n * The active sort, highest priority first. Two-way: `[(sort)]`, and the\n * model's own `(sortChange)` fires whenever this component changes it.\n */\n readonly sort = model<readonly SortState[]>([]);\n readonly density = input<TableUiDensity>('default');\n /** Sticks the header to the top of the scroller. */\n readonly stickyHeader = input(true);\n /**\n * The number of rows in the FULL result set, for `aria-rowcount` when the\n * consumer pages on the server. Unset derives it from the rows' `index`,\n * which is exact when the grid renders the whole set.\n */\n readonly totalRows = input<number | undefined>(undefined);\n /** Accessible name of the grid (`aria-label`). */\n readonly ariaLabel = input<string | undefined>(undefined);\n /** Overrides for the built-in English/Arabic text. */\n readonly labels = input<Partial<TableUiLabels> | undefined>(undefined);\n\n /** Enter on a focused cell, or a double-click on a row. */\n readonly rowActivate = output<TableUiRow>();\n /**\n * A row was selected/deselected by the user. The model already carries the\n * new set (`selectedChange`); this output also names the row that moved.\n */\n readonly selectionChange = output<TableUiSelectionChange>();\n // `(sortChange)` is generated by the `sort` model — no duplicate output here.\n\n protected readonly instructionsId = `rv-table-help-${++nextTableId}`;\n private readonly uid = nextTableId;\n\n protected readonly visibleColumns = computed(() =>\n this.columns().filter((column) => column.hidden !== true),\n );\n\n protected readonly columnCount = computed(() => this.visibleColumns().length);\n\n /**\n * The ONE column geometry both the header and the body read.\n *\n * A flex column keeps its declared width as the track's FLOOR\n * (`minmax(<width>, Nfr)`, design §5.2): with `minmax(0, Nfr)` a narrow\n * container would collapse the column to zero and its content would vanish\n * instead of scrolling. Fixed columns are their width, untouched.\n */\n protected readonly columnTemplate = computed(() => {\n const tracks = this.visibleColumns().map((column) => {\n const width = column.width ?? DEFAULT_COLUMN_WIDTH;\n const flex = column.flex ?? 0;\n return flex > 0 ? `minmax(${width}, ${flex}fr)` : width;\n });\n return tracks.length > 0 ? tracks.join(' ') : '1fr';\n });\n\n /**\n * The grid's intrinsic width, so the scroller actually scrolls instead of\n * squeezing every column. It is the sum of the declared widths — which is\n * why a column width should be a length (`px`, `rem`, `ch`), not `max-content`.\n */\n protected readonly minWidth = computed(() => {\n const widths = this.visibleColumns().map(\n (column) => `(${column.width ?? DEFAULT_COLUMN_WIDTH})`,\n );\n return `calc(${widths.length > 0 ? widths.join(' + ') : '0px'})`;\n });\n\n /**\n * `aria-label` written on the HOST — `<rv-table aria-label=\"Invoices\">` — is\n * the attribute a consumer reaches for first, but the `role=\"grid\"` element\n * lives INSIDE the component, so the attribute would otherwise stop at the\n * host and the grid would keep its fallback name (\"Data grid\"). Read on every\n * change detection: it is a `getAttribute` call, and a dynamic label belongs\n * on the `[ariaLabel]` input, which is reactive.\n */\n protected get hostLabel(): string | undefined {\n const label = this.host.nativeElement.getAttribute('aria-label')?.trim();\n return label ? label : undefined;\n }\n\n protected readonly effectiveLabels = computed<TableUiLabels>(() => ({\n ...tableUiLabels(this.locale()),\n ...this.labels(),\n }));\n\n protected readonly locale = computed(() => {\n const pageLanguage =\n typeof document !== 'undefined' ? document.documentElement.lang : '';\n if (pageLanguage) return pageLanguage;\n const browserLanguage =\n typeof navigator !== 'undefined' ? navigator.language : '';\n return browserLanguage || 'en-US';\n });\n\n /** Column id → the consumer's template. Rebuilt only when content changes. */\n private readonly cellTemplates = computed(() => {\n const templates = new Map<string, TemplateRef<TableUiCellContext>>();\n for (const directive of this.projectedCells()) {\n templates.set(directive.columnId(), directive.template);\n }\n return templates;\n });\n\n /**\n * Sort lookup by column id: direction plus 1-based priority. One map instead\n * of a linear search per rendered header cell.\n */\n private readonly sortIndex = computed(() => {\n const index = new Map<string, { direction: SortDirection; rank: number }>();\n this.sort().forEach((entry, position) =>\n index.set(entry.column, { direction: entry.direction, rank: position + 1 }),\n );\n return index;\n });\n\n /** The fully resolved view model. Nothing in the template computes. */\n protected readonly renderedRows = computed<readonly RenderRow[]>(() => {\n const templates = this.cellTemplates();\n const selected = this.selected();\n const ids = new Map<TableRowId, number[]>();\n const rendered = this.rows().map((row, rowIndex) => {\n const id = this.rowId(row);\n const validId = (typeof id === 'string' && id.trim().length > 0) ||\n (typeof id === 'number' && Number.isFinite(id));\n const positions = validId ? (ids.get(id) ?? []) : [];\n if (validId) {\n positions.push(rowIndex);\n ids.set(id, positions);\n } else if (isDevMode()) {\n throw new Error(`[rivinia/table/ui] Invalid row ID ${String(id)} at row index ${rowIndex}.`);\n }\n return {\n id,\n identityValid: row.identityValid !== false && validId,\n row,\n rowIndex,\n ariaRowIndex:\n (Number.isFinite(row.index) ? row.index : rowIndex) + 2,\n selected: row.identityValid !== false && validId && selected.has(id),\n cells: this.buildCells(row, rowIndex, templates),\n };\n });\n for (const [id, positions] of ids) {\n if (positions.length < 2) continue;\n if (isDevMode()) {\n throw new Error(`[rivinia/table/ui] Duplicate row ID ${JSON.stringify(id)} at row indices ${positions.join(', ')}.`);\n }\n for (const index of positions) rendered[index].identityValid = false;\n }\n return rendered;\n });\n\n protected readonly skeletonList = computed(() => {\n const requested = this.loading() ? this.skeletonRows() : 0;\n const count =\n requested === 'auto' ? Math.max(1, this.rows().length) : requested;\n return Array.from({ length: Math.max(0, Math.floor(count)) });\n });\n\n protected readonly state = computed<TableUiState | null>(() => {\n if (this.loading()) return null;\n const error = this.error();\n if (error !== null && error !== undefined) {\n return { kind: 'error', message: this.errorText(error) };\n }\n if (this.rows().length === 0) {\n return {\n kind: 'empty',\n message: this.emptyMessage() ?? this.effectiveLabels().empty,\n };\n }\n return null;\n });\n\n /**\n * The rows that actually have DOM cells. While loading the skeletons replace\n * them, and an error/empty state replaces them too, so the keyboard must not\n * \"move\" into a row that has no element to focus.\n */\n protected readonly bodyRows = computed<readonly RenderRow[]>(() =>\n this.loading() || this.state() !== null ? [] : this.renderedRows(),\n );\n\n /**\n * The header row is row 1 (WAI-ARIA counts the header), so the grid's row\n * count is the data count + 1. `-1` is legal (\"unknown\"), but a wrong count\n * is worse than an honest one, so `[totalRows]` is the precise answer when\n * the consumer pages on the server.\n */\n protected readonly rowCount = computed(() => {\n const total = this.totalRows();\n if (total !== undefined) return Math.max(0, Math.floor(total)) + 1;\n const rows = this.rows();\n let highest = rows.length;\n for (const row of rows) {\n if (Number.isFinite(row.index) && row.index + 1 > highest) {\n highest = row.index + 1;\n }\n }\n return highest + 1;\n });\n\n // ── Active cell (roving tabindex) ─────────────────────────────────────────\n\n /** Row index in the rendered page; `-1` is the header row. */\n private readonly active = signal<{ readonly row: number; readonly column: number }>({\n row: -1,\n column: 0,\n });\n private pendingFocus = false;\n\n /**\n * The active cell, clamped to what is actually rendered. A stale row index\n * (the data shrank) would otherwise leave the grid with no tab stop at all.\n * While loading there are no data cells, so the header is the tab stop.\n */\n protected readonly effectiveActive = computed(() => {\n const active = this.active();\n const columns = this.columnCount();\n if (columns === 0) return { row: -1, column: 0 };\n const column = Math.min(Math.max(active.column, 0), columns - 1);\n const rowCount = this.bodyRows().length;\n if (active.row < 0 || rowCount === 0) return { row: -1, column };\n return { row: Math.min(active.row, rowCount - 1), column };\n });\n\n constructor() {\n // Focus is applied AFTER the DOM has the new tabindex; focusing during the\n // keydown would land on an element that is about to be re-rendered.\n afterRenderEffect(() => {\n const active = this.effectiveActive();\n this.columnTemplate();\n if (!this.pendingFocus) return;\n this.pendingFocus = false;\n this.focusCell(active.row, active.column);\n });\n }\n\n protected isActive(row: number, column: number): boolean {\n const active = this.effectiveActive();\n return active.row === row && active.column === column;\n }\n\n // ── Header + sorting ──────────────────────────────────────────────────────\n\n protected isSortable(column: TableUiColumn): boolean {\n // A display/actions column has no value to sort: opt in explicitly to\n // avoid \"sorting\" a column whose order would look arbitrary.\n return column.sortable ?? column.accessor !== undefined;\n }\n\n protected sortRank(column: TableUiColumn): number {\n return this.sortIndex().get(column.id)?.rank ?? 0;\n }\n\n protected sortDirection(column: TableUiColumn): SortDirection | null {\n return this.sortIndex().get(column.id)?.direction ?? null;\n }\n\n /**\n * `aria-sort` lives on exactly ONE header (the highest-priority sort), as\n * WAI-ARIA requires; secondary sorted columns are shown with a rank badge\n * and announce `none`, because the attribute has no \"secondary\" value.\n */\n protected ariaSort(\n column: TableUiColumn,\n ): 'ascending' | 'descending' | 'none' | null {\n if (!this.isSortable(column)) return null;\n const entry = this.sortIndex().get(column.id);\n if (!entry || entry.rank !== 1) return 'none';\n return entry.direction === 'asc' ? 'ascending' : 'descending';\n }\n\n protected onHeaderClick(\n column: TableUiColumn,\n columnIndex: number,\n event: MouseEvent,\n ): void {\n if (!this.isSortable(column)) return;\n this.active.set({ row: -1, column: columnIndex });\n this.pendingFocus = false;\n // The click landed on the inner button, which is `tabindex=\"-1\"`; park\n // focus on the CELL so the grid keeps exactly one tab stop.\n this.focusCell(-1, columnIndex);\n this.cycleSort(column, event.shiftKey);\n }\n\n // ── Rows + selection ──────────────────────────────────────────────────────\n\n protected rowId(row: TableUiRow): TableRowId {\n const key = this.rowKey();\n return key ? key(row.data) : row.id;\n }\n\n protected onRowActivate(item: RenderRow): void {\n this.rowActivate.emit(item.row);\n }\n\n /**\n * A double-click is a row activation. It is ignored when it lands on an\n * interactive element inside a projected template: the action the user asked\n * for (Edit, Open, a link) must not also fire the row's own activation.\n */\n protected onRowDoubleClick(item: RenderRow, event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n if (\n target?.closest(\n 'button, a, input, select, textarea, label, [contenteditable=\"true\"]',\n )\n ) {\n return;\n }\n this.onRowActivate(item);\n }\n\n protected onCellClick(\n item: RenderRow,\n cell: RenderCell,\n event: MouseEvent,\n ): void {\n const target = event.target as HTMLElement | null;\n // A button, link or input in a projected template owns its click: toggling\n // the row would fight the action the user actually asked for.\n if (\n target?.closest(\n 'button, a, input, select, textarea, label, [contenteditable=\"true\"]',\n )\n ) {\n return;\n }\n // A double-click is an activation; its second click must not undo the\n // selection the first one made.\n if (event.detail > 1) return;\n\n this.active.set({ row: item.rowIndex, column: cell.columnIndex });\n this.pendingFocus = false;\n this.focusCell(item.rowIndex, cell.columnIndex);\n if (this.selectable()) this.toggleRow(item);\n }\n\n protected onCellFocus(row: number, column: number): void {\n const active = this.active();\n if (active.row === row && active.column === column) return;\n // The user's own focus (click/tab) is already final: do not re-focus after\n // the render, which would fight `:focus-visible` and re-scroll the page.\n this.pendingFocus = false;\n this.active.set({ row, column });\n }\n\n // ── Keyboard ──────────────────────────────────────────────────────────────\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented) return;\n const target = event.target as HTMLElement | null;\n // Only the CELL owns the grid keys. Anything focusable inside a projected\n // template (a button, an input) keeps its native keyboard behaviour.\n if (!target || !target.hasAttribute('data-rv-cell')) return;\n\n const rows = this.bodyRows();\n const active = this.effectiveActive();\n const rtl = this.isRtl(target);\n\n if ((event.ctrlKey || event.metaKey) && (event.key === 'Home' || event.key === 'End')) {\n event.preventDefault();\n if (rows.length === 0) return;\n const row = event.key === 'Home' ? 0 : rows.length - 1;\n this.moveTo(row, event.key === 'Home' ? this.firstColumn(row) : this.lastColumn(row));\n return;\n }\n // Alt+Arrow is the browser's back/forward on some platforms; not ours yet.\n if (event.ctrlKey || event.metaKey || event.altKey) return;\n\n if (active.row < 0) {\n this.onHeaderKey(event, active.column, rows.length);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n this.moveBy(1, 0);\n break;\n case 'ArrowUp':\n // The header is part of the grid, not decoration: up from the first\n // data row reaches the same column's header (and its sort action).\n if (active.row === 0) this.moveTo(-1, active.column);\n else this.moveBy(-1, 0);\n break;\n case 'ArrowLeft':\n this.moveBy(0, rtl ? 1 : -1);\n break;\n case 'ArrowRight':\n this.moveBy(0, rtl ? -1 : 1);\n break;\n case 'Home':\n this.moveTo(active.row, this.firstColumn(active.row));\n break;\n case 'End':\n this.moveTo(active.row, this.lastColumn(active.row));\n break;\n case 'PageDown':\n this.page(1);\n break;\n case 'PageUp':\n this.page(-1);\n break;\n case 'Enter':\n // Enter opens a data row; on a header it sorts (see onHeaderKey).\n this.onRowActivate(rows[active.row]);\n break;\n case ' ':\n if (!this.selectable()) return;\n this.toggleRow(rows[active.row]);\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n\n private onHeaderKey(\n event: KeyboardEvent,\n column: number,\n rowCount: number,\n ): void {\n const rtl = this.isRtl(event.target as HTMLElement);\n switch (event.key) {\n case 'ArrowDown':\n if (rowCount > 0) this.moveTo(0, column);\n break;\n case 'ArrowLeft':\n this.moveTo(-1, column + (rtl ? 1 : -1));\n break;\n case 'ArrowRight':\n this.moveTo(-1, column + (rtl ? -1 : 1));\n break;\n case 'Home':\n this.moveTo(-1, 0);\n break;\n case 'End':\n this.moveTo(-1, this.columnCount() - 1);\n break;\n case 'Enter':\n case ' ': {\n const definition = this.visibleColumns()[column];\n if (definition) this.cycleSort(definition, event.shiftKey);\n break;\n }\n default:\n return;\n }\n event.preventDefault();\n }\n\n private cycleSort(column: TableUiColumn, additive: boolean): void {\n const current = this.sort();\n const position = current.findIndex((entry) => entry.column === column.id);\n const direction = position >= 0 ? current[position].direction : null;\n\n if (additive) {\n if (position < 0) {\n this.sort.set([...current, { column: column.id, direction: 'asc' }]);\n } else if (direction === 'asc') {\n this.sort.set(\n current.map((entry, index) =>\n index === position\n ? { column: entry.column, direction: 'desc' }\n : entry,\n ),\n );\n } else {\n this.sort.set(current.filter((_, index) => index !== position));\n }\n return;\n }\n\n // Clicking the only sorted column cycles asc -> desc -> unsorted.\n if (current.length === 1 && position === 0) {\n this.sort.set(\n direction === 'asc'\n ? [{ column: column.id, direction: 'desc' }]\n : [],\n );\n return;\n }\n // Any other click makes this column the ONLY sort.\n this.sort.set([\n { column: column.id, direction: direction === 'asc' ? 'desc' : 'asc' },\n ]);\n }\n\n /**\n * A page step stays inside the BODY. The header is reachable with ArrowUp,\n * but PageUp from the first data row must not surprise the user by landing on\n * the sort controls; page keys are a data-reading affordance.\n */\n private page(direction: 1 | -1): void {\n const rowCount = this.bodyRows().length;\n if (rowCount === 0) return;\n const active = this.effectiveActive();\n const room = direction > 0 ? rowCount - 1 - active.row : active.row;\n const step = direction * Math.min(this.pageRows(), Math.max(0, room));\n if (step === 0) return;\n this.moveBy(step, 0);\n }\n\n private moveBy(deltaRow: number, deltaColumn: number): void {\n const active = this.effectiveActive();\n const spans = this.spans();\n if (spans) {\n const target = spans.move(\n active.row,\n active.column,\n deltaRow,\n deltaColumn,\n );\n this.moveTo(target.row, target.column);\n return;\n }\n this.moveTo(active.row + deltaRow, active.column + deltaColumn);\n }\n\n /** Clamp to the page, then snap onto the anchor of whatever cell is there. */\n private moveTo(row: number, column: number): void {\n const rows = this.bodyRows().length;\n const columns = this.columnCount();\n column = Math.min(Math.max(column, 0), Math.max(columns - 1, 0));\n row = rows === 0 ? -1 : Math.min(Math.max(row, -1), rows - 1);\n\n const spans = this.spans();\n if (spans && row >= 0) {\n const anchor = spans.at(row, column) ?? spans.owner(row, column);\n if (anchor) {\n row = anchor.row;\n column = anchor.column;\n }\n }\n\n const active = this.effectiveActive();\n if (active.row === row && active.column === column) return;\n this.pendingFocus = true;\n this.active.set({ row, column });\n }\n\n private firstColumn(row: number): number {\n const spans = this.spans();\n if (!spans) return 0;\n for (let column = 0; column < this.columnCount(); column++) {\n if (spans.at(row, column) ?? spans.owner(row, column)) return column;\n }\n return 0;\n }\n\n private lastColumn(row: number): number {\n const spans = this.spans();\n const last = Math.max(this.columnCount() - 1, 0);\n if (!spans) return last;\n for (let column = last; column >= 0; column--) {\n if (spans.at(row, column) ?? spans.owner(row, column)) return column;\n }\n return last;\n }\n\n /** One screen minus one row; 10 when there is no layout to measure (SSR, jsdom). */\n private pageRows(): number {\n const viewport = this.scroller()?.nativeElement.clientHeight ?? 0;\n const cell = this.host.nativeElement.querySelector<HTMLElement>(\n '.rv-table-body .rv-table-cell',\n );\n const rowHeight = cell?.getBoundingClientRect().height ?? 0;\n if (!(viewport > 0) || !(rowHeight > 0)) return 10;\n return Math.max(1, Math.floor(viewport / rowHeight) - 1);\n }\n\n private focusCell(row: number, column: number): void {\n const element = this.host.nativeElement.querySelector<HTMLElement>(\n `[data-rv-cell=\"${row}:${column}\"]`,\n );\n element?.focus({ preventScroll: true });\n element?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });\n }\n\n /**\n * Direction is read from the nearest `[dir]` FIRST, then from computed\n * style: jsdom does not inherit computed direction, and `dir=\"auto\"` needs\n * the computed answer anyway.\n */\n private isRtl(element: HTMLElement): boolean {\n const declared = element.closest('[dir]')?.getAttribute('dir')?.toLowerCase();\n if (declared === 'rtl') return true;\n if (declared === 'ltr') return false;\n if (typeof getComputedStyle !== 'function') return false;\n return getComputedStyle(element).direction === 'rtl';\n }\n\n // ── Internals ─────────────────────────────────────────────────────────────\n\n private buildCells(\n row: TableUiRow,\n rowIndex: number,\n templates: ReadonlyMap<string, TemplateRef<TableUiCellContext>>,\n ): readonly RenderCell[] {\n const spans = this.spans();\n const cells: RenderCell[] = [];\n\n this.visibleColumns().forEach((column, columnIndex) => {\n let rowSpan = 1;\n let colSpan = 1;\n if (spans) {\n const anchor = spans.at(rowIndex, columnIndex);\n if (anchor) {\n rowSpan = Math.max(1, anchor.rowSpan);\n colSpan = Math.max(1, anchor.colSpan);\n } else if (spans.owner(rowIndex, columnIndex)) {\n // Covered by an earlier anchor: no element, no ARIA cell, no tab stop.\n return;\n }\n }\n\n const value = this.readValue(column, row.data);\n const template = templates.get(column.id) ?? null;\n const text = template\n ? ''\n : column.format\n ? column.format(value, row.data, this.locale())\n : value === null || value === undefined\n ? ''\n : String(value);\n\n const overflow: TextOverflow = column.textOverflow ?? 'nowrap';\n // `wrap` never cuts the text, so it needs no full-text description; the\n // other two modes may, and a hidden node is the only way to expose the\n // untruncated value without a `title` (the consumer owns the tooltip).\n const describedBy =\n text !== '' && overflow !== 'wrap'\n ? `rv-table-${this.uid}-full-${rowIndex}-${columnIndex}`\n : null;\n\n cells.push({\n column,\n columnIndex,\n rowSpan,\n colSpan,\n spanned: rowSpan > 1 || colSpan > 1,\n overflow,\n lines: overflow === 'clamp' ? (column.lines ?? 2) : null,\n text,\n describedBy,\n template,\n // CSS grid line numbers are 1-based, and every anchor sits exactly at\n // its matrix position, so placement is deterministic even when a span\n // above or beside it removed cells from the flow.\n gridRow: `${rowIndex + 1} / span ${rowSpan}`,\n gridColumn: `${columnIndex + 1} / span ${colSpan}`,\n context: {\n $implicit: row,\n row,\n data: row.data,\n value,\n column,\n rowIndex,\n },\n });\n });\n\n return cells;\n }\n\n private readValue(column: TableUiColumn, data: unknown): unknown {\n const accessor = column.accessor;\n if (accessor === undefined) return undefined;\n if (typeof accessor === 'function') return accessor(data);\n const record = data as Record<string, unknown> | null | undefined;\n return record === null || record === undefined ? undefined : record[accessor];\n }\n\n private toggleRow(item: RenderRow): void {\n if (!this.selectable() || !item.identityValid) return;\n const id = item.id;\n const selected = new Set(this.selected());\n if (selected.has(id)) selected.delete(id);\n else selected.add(id);\n this.selected.set(selected);\n this.selectionChange.emit({ selected, row: item.row });\n }\n\n private errorText(error: unknown): string {\n if (error instanceof Error && error.message) return error.message;\n if (typeof error === 'string' && error) return error;\n return this.effectiveLabels().error;\n }\n}\n","<!--\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"],"mappings":"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"}
|
|
@@ -420,7 +420,7 @@ const EMPTY_SELECTION = {
|
|
|
420
420
|
function pruneSelection(state, rows) {
|
|
421
421
|
if (state.ids.size === 0) return state;
|
|
422
422
|
const present = /* @__PURE__ */ new Set();
|
|
423
|
-
for (const row of rows) present.add(row.id);
|
|
423
|
+
for (const row of rows) if (row.identityValid !== false) present.add(row.id);
|
|
424
424
|
let dropped = false;
|
|
425
425
|
const next = /* @__PURE__ */ new Set();
|
|
426
426
|
for (const id of state.ids) if (present.has(id)) next.add(id);
|
|
@@ -473,7 +473,7 @@ var RiviniaSelection = class {
|
|
|
473
473
|
this.count = computed(() => this.ids().length, ...ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []);
|
|
474
474
|
}
|
|
475
475
|
select(id) {
|
|
476
|
-
if (!this.enabled) return;
|
|
476
|
+
if (!this.enabled || !this.isUnambiguous(id)) return;
|
|
477
477
|
const state = this.state();
|
|
478
478
|
if (!this.multiple) {
|
|
479
479
|
if (state.mode === "include" && state.ids.size === 1 && state.ids.has(id)) return;
|
|
@@ -498,7 +498,7 @@ var RiviniaSelection = class {
|
|
|
498
498
|
});
|
|
499
499
|
}
|
|
500
500
|
deselect(id) {
|
|
501
|
-
if (!this.enabled) return;
|
|
501
|
+
if (!this.enabled || !this.isUnambiguous(id)) return;
|
|
502
502
|
const state = this.state();
|
|
503
503
|
if (state.mode === "include") {
|
|
504
504
|
const ids = withoutId(state.ids, id);
|
|
@@ -523,7 +523,7 @@ var RiviniaSelection = class {
|
|
|
523
523
|
this.state.set(EMPTY_SELECTION);
|
|
524
524
|
}
|
|
525
525
|
isSelected(id) {
|
|
526
|
-
if (!this.enabled) return false;
|
|
526
|
+
if (!this.enabled || !this.isUnambiguous(id)) return false;
|
|
527
527
|
const state = this.state();
|
|
528
528
|
return state.mode === "include" ? state.ids.has(id) : !state.ids.has(id);
|
|
529
529
|
}
|
|
@@ -534,7 +534,7 @@ var RiviniaSelection = class {
|
|
|
534
534
|
const state = this.state();
|
|
535
535
|
if (state.mode === "include") {
|
|
536
536
|
let ids = state.ids;
|
|
537
|
-
for (const row of page) ids = withId(ids, row.id);
|
|
537
|
+
for (const row of page) if (row.identityValid !== false) ids = withId(ids, row.id);
|
|
538
538
|
if (ids !== state.ids) this.state.set({
|
|
539
539
|
mode: "include",
|
|
540
540
|
ids
|
|
@@ -542,7 +542,7 @@ var RiviniaSelection = class {
|
|
|
542
542
|
return;
|
|
543
543
|
}
|
|
544
544
|
let ids = state.ids;
|
|
545
|
-
for (const row of page) ids = withoutId(ids, row.id);
|
|
545
|
+
for (const row of page) if (row.identityValid !== false) ids = withoutId(ids, row.id);
|
|
546
546
|
if (ids !== state.ids) this.state.set({
|
|
547
547
|
mode: "exclude",
|
|
548
548
|
ids
|
|
@@ -582,28 +582,58 @@ var RiviniaSelection = class {
|
|
|
582
582
|
const state = this.state();
|
|
583
583
|
const ids = [];
|
|
584
584
|
for (const row of this.rows()) {
|
|
585
|
+
if (row.identityValid === false) continue;
|
|
585
586
|
const listed = state.ids.has(row.id);
|
|
586
587
|
if (state.mode === "include" ? listed : !listed) ids.push(row.id);
|
|
587
588
|
}
|
|
588
589
|
return ids;
|
|
589
590
|
}
|
|
591
|
+
isUnambiguous(id) {
|
|
592
|
+
let matches = 0;
|
|
593
|
+
for (const row of this.rows()) {
|
|
594
|
+
if (row.identityValid !== false && row.id === id) matches++;
|
|
595
|
+
if (matches > 1) return false;
|
|
596
|
+
}
|
|
597
|
+
return matches === 1;
|
|
598
|
+
}
|
|
590
599
|
};
|
|
600
|
+
function isValidRowId(id) {
|
|
601
|
+
return typeof id === "string" && id.trim().length > 0 || typeof id === "number" && Number.isFinite(id);
|
|
602
|
+
}
|
|
591
603
|
function buildRows(data, getId) {
|
|
604
|
+
const ids = /* @__PURE__ */ new Map();
|
|
605
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
592
606
|
const rows = new Array(data.length);
|
|
593
607
|
for (let index = 0; index < data.length; index++) {
|
|
594
608
|
const item = data[index];
|
|
609
|
+
const id = getId(item);
|
|
610
|
+
if (!isValidRowId(id)) {
|
|
611
|
+
invalid.add(index);
|
|
612
|
+
if (isDevMode()) throw new Error(`[rivinia/table] Invalid row ID ${String(id)} at row index ${index}; IDs must be non-empty strings or finite numbers.`);
|
|
613
|
+
} else {
|
|
614
|
+
const positions = ids.get(id) ?? [];
|
|
615
|
+
positions.push(index);
|
|
616
|
+
ids.set(id, positions);
|
|
617
|
+
}
|
|
595
618
|
rows[index] = {
|
|
596
|
-
id
|
|
619
|
+
id,
|
|
597
620
|
data: item,
|
|
598
621
|
index
|
|
599
622
|
};
|
|
600
623
|
}
|
|
601
|
-
|
|
624
|
+
for (const [id, positions] of ids) {
|
|
625
|
+
if (positions.length < 2) continue;
|
|
626
|
+
for (const index of positions) invalid.add(index);
|
|
627
|
+
if (isDevMode()) throw new Error(`[rivinia/table] Duplicate row ID ${JSON.stringify(id)} at row indices ${positions.join(", ")}; IDs must be unique and stable within the active data set.`);
|
|
628
|
+
}
|
|
629
|
+
return rows.map((row, index) => invalid.has(index) ? {
|
|
630
|
+
...row,
|
|
631
|
+
identityValid: false
|
|
632
|
+
} : row);
|
|
602
633
|
}
|
|
603
634
|
function reindex(rows) {
|
|
604
635
|
for (let index = 0; index < rows.length; index++) if (rows[index].index !== index) return rows.map((row, position) => ({
|
|
605
|
-
|
|
606
|
-
data: row.data,
|
|
636
|
+
...row,
|
|
607
637
|
index: position
|
|
608
638
|
}));
|
|
609
639
|
return rows;
|