import{type TemplateResult,type PropertyValues}from'lit';import{LyraElement}from'../../../internal/lyra-element.js';import'../../overlays/empty/empty.class.js'; /** How `loading` renders. `'spinner'` (the default) replaces the grid with an indeterminate * spinner; `'skeleton'` keeps the real grid — ``, ``, filter field, pagination * footer — and fills the body with placeholder rows so the table sketches its shape instead of * collapsing to a spinner and back on a cold load. */ export type TableLoadingAppearance='spinner'|'skeleton'; /** Which inline-start/inline-end edge a column aligns or sticks to. */ export type TableEdgeAlign='start'|'end'; /** Explicit interaction that opens a column's editor. */ export type TableColumnEditTrigger='double-click'|'always'; /** One choice offered by an `editType: 'select'` column's editor (`columns[].editOptions`) -- * the same `value`/`label` shape this library already uses for a flat list of choices (see * `LyraSegmentedItem`). */ export interface TableColumnEditOption{value:string;label:string;} /** ``'s `selectionMode` property: `'none'` disables row selection, * `'single'` allows one selected row at a time, `'multiple'` allows any * number through row activation. */ export type TableSelectionMode='none'|'single'|'multiple'; /** ``'s `expansionMode` property, mirroring `TableSelectionMode` member for member: * `'none'` leaves `expandedRowKeys` fully consumer-controlled (the default, and the only * behaviour before it existed), `'single'` self-manages at most one expanded row at a time, and * `'multiple'` self-manages any number. */ export type TableExpansionMode='none'|'single'|'multiple'; /** ``'s `sortMode` property: `'client'` orders `rows` in the browser from * `sortKey`/`sortDir`, `'server'` renders `rows` in the order given. Mirrors the * `paginationMode` split of the same two names. */ export type TableSortMode='client'|'server'; /** Whether only the active header or every sortable header displays an indicator. */ export type TableSortIndicators='active'|'all'; /** ``'s `scrollMode`: which element scrolls when the table overflows. * * `'self'` (default) makes `[part="base"]` the scroll container, which is what pairs with * `--lr-table-max-height` and what makes the sticky header pin inside the table's own viewport. * * `'page'` hands scrolling back to the page. Necessary because a scroll container clips *both* * axes -- CSS gives no way to scroll one axis and not the other -- so an uncapped table that is * still `overflow: auto` creates a sticky containing block that never scrolls, and its header * scrolls off with the document instead of pinning. With `'page'` the header's nearest scrollport * is the page, so it pins there; the cost is that a table wider than its host overflows the page * rather than scrolling inside itself. * * `'auto'` uses page flow while the rendered content fits the table's allocation, then switches * `[part="base"]` to the same contained scrolling as `'self'` only while it actually overflows * horizontally. */ export type TableScrollMode='self'|'page'|'auto'; /** Canonical table sort direction. */ export type TableSortDirection='asc'|'desc'; /** Cancelable sort proposal detail. */ export interface TableSortRequestDetail{readonly phase:'request';readonly sortKey:string;readonly sortDir:TableSortDirection;} /** Accepted sort transaction detail. */ export interface TableSortCommitDetail{readonly phase:'commit';readonly sortKey:string;readonly sortDir:TableSortDirection;} /** One discriminated detail vocabulary shared across the sort request and commit phases. */ export type TableSortDetail=TableSortRequestDetail|TableSortCommitDetail;interface TableColumnCommon{key:string;label:string; /** Renders custom content into this column's , in place of the plain `label` text -- e.g. a * drag-to-resize handle or an interactive header affordance. Omit for the default plain-text * `label` rendering (unchanged output). Receives the column definition itself -- there is no * per-row data at header scope. */ headerCell?:(column:TableColumn)=>unknown; /** CSS length (e.g. '120px', '20%') for this column's width. Omit for today's intrinsic/auto * sizing (unchanged). When any column defines `width`, the table switches to * `table-layout: fixed` so declared widths are authoritative rather than advisory. */ width?:string; /** CSS length for this column's minimum width (e.g. '80px'). Has no effect unless at least one * column in the table also defines `width` (see `width`'s own doc). */ minWidth?:string; /** CSS length for this column's maximum width (e.g. '320px'). Pixel values also bound pointer * and keyboard resizing; other CSS lengths still constrain the rendered column. */ maxWidth?:string; /** Enables pointer and keyboard resizing from this column's header. The table keeps the live * width internally and emits `lr-column-resize` on every resize step; only the final, * drag-end/keypress-committed emission is cancelable (see the event's own doc). */ resizable?:boolean;sortable?:boolean; /** This column's own initial sort direction the first time header activation makes it the active * `sortKey` -- consulted before the element-level `defaultSortDir`, which remains the fallback * when this is omitted. Lets a mixed text/numeric table give one column (e.g. a "last updated" * column that should read newest-first) the opposite initial direction from every other column, * without flipping the element-level default for all of them. Like `defaultSortDir`, this only * decides the direction chosen the first time this column becomes active, or the first time * *after* a different column was active — re-activating a column that is already `sortKey` still * only toggles between `'asc'` and `'desc'`. */ defaultSortDir?:TableSortDirection; /** Backs client-mode sorting (`sortMode: 'client'`, the default) for this column. Returns the * comparable value for `row` — a finite number sorts numerically, a string sorts through a * locale-aware `Intl.Collator` (`numeric: true`, so `item2` precedes `item10`), and * `null`/`undefined` (or a non-finite number) sorts *last* regardless of direction, so flipping * `sortDir` never floats a block of blanks to the top. * * Omit it to sort by this column's rendered `cell()` output instead, stringified — which only * produces a meaningful order when `cell()` returns a string or number. Define `sortValue` * whenever `cell()` returns a template or element, or the column would sort by a constant. * Ignored entirely when `sortMode` is `'server'` (the caller is assumed to have already ordered * `rows`) or when the column is not `sortable`. */ sortValue?:(row:T)=>string|number|null|undefined;align?:TableEdgeAlign; /** Responsive priority — `undefined` (the default) means "always visible". * `'low'` columns hide first (narrowest container), `'medium'` next, as * `[part='base']`'s container-query width shrinks; both can be forced back * on via `[part='reveal-columns-button']`. */ priority?:'medium'|'low'; /** Pins this column's header/cell to one edge with `position: sticky` so it stays visible while * the table scrolls horizontally. Both directions use CSS logical properties, so RTL flips * automatically. */ sticky?:TableEdgeAlign; /** Renders a sticky-bottom footer cell for this column, computed from every currently-rendered * row (post-sort, pre-pagination) -- e.g. a column total. Omit for a column with no footer * value; a `` renders at all only when at least one column defines this. */ footer?(rows:readonly T[]):unknown; /** Applied directly to the generated `` via `styleMap` -- e.g. a computed heat-tint * background that a `cell()`-returned inner element can't paint into the cell's own padding. * Omit for no per-cell style override (the default; unchanged output). * * Precedence with `heatValue`: an inline `style=` attribute always wins the CSS cascade over an * external stylesheet rule regardless of specificity, so a `background`/`backgroundColor` * returned here silently and completely overrides this same column's `heatValue` tint (which is * painted by a shadow-stylesheet rule, not inline) -- combine the two only when that override is * the intended effect. */ cellStyle?(row:T):Record |undefined; /** Applied as the generated ``'s native `title`, symmetrical with `cellStyle` -- e.g. the * untruncated text behind an ellipsized cell, or a formatted timestamp behind a relative one. * Returning `undefined` (or an empty string) omits the attribute entirely rather than rendering * `title=""`, which would suppress an ancestor's own tooltip. The attribute is also suppressed * while that cell is in inline-edit mode, so the tooltip can't shadow the editor. * * Accessibility: some screen readers announce a `` as the cell's accessible name, * replacing the cell's own content rather than supplementing it (the same caveat `lr-stat`'s * `exactValue` carries). Use it for a longer form of what the cell already shows, never for * information that exists nowhere else. */ cellTitle?(row:T):string|undefined; /** Numeric accessor backing the heat-tint background. A column that omits this is excluded from * tinting (e.g. a label column) — its presence on any column is the opt-in signal for heat-tint * mode as a whole, mirroring how `expandedContent` alone signals expand-mode (no separate * boolean). Returns `null`/`undefined` for a cell with no value: excluded from both the domain * computation and the tint (reads as "no data", not "zero"). A `cellStyle` on the same column * that returns `background`/`backgroundColor` silently wins over this tint -- see `cellStyle`'s * own doc for why. */ heatValue?(row:T):number|null|undefined; /** Reads the value shown in the inline editor. When omitted, `row[key]` is * used for record-like rows. For `editType: 'select'`, this is the selected option's * `value` -- match one entry of `editOptions`, or none of them renders selected. */ editValue?:(row:T)=>string|number; /** Accessible name (`aria-label`) for this row's inline editor, read once per row exactly like * `editValue`/`cellTitle`. When omitted, every editor in the column shares the same interpolated * `tableEditCell` name (`Edit {column}`) -- indistinguishable from its column siblings, which is * harmless for `editTrigger: 'double-click'` (only one editor is ever open at a time) but not for * `editTrigger: 'always'`, where each row's editor is a permanent, individually focusable Tab * stop: an unset `editLabel` there leaves a keyboard or screen-reader user with no way to tell * which row a given editor belongs to (WCAG 2.4.6, 1.3.1). Return the row's own full name (e.g. * combining the column label with a row identifier) -- this is consumer-owned text like * `cellTitle`/`editValue`, not passed through the table's own localization. */ editLabel?:(row:T)=>string; /** Native editor type used when `editTrigger` is set. `'select'` renders a native `` rather than throwing. */ editType?:'text'|'number'|'select'; /** Choices offered by an `editType: 'select'` column's editor. Ignored for every other * `editType`. */ editOptions?:TableColumnEditOption[];} /** One column definition. `cell` is required for every `editTrigger` except `'always'` -- the * table's render path calls it to paint that column's plain-text resting state, which an * `'always'` column never has (its editor is unconditional from first paint, see `editTrigger`'s * own doc). Supplying `cell` on an `'always'` column stays valid; it is simply never required. */ export type TableColumn =(TableColumnCommon &{ /** Enables inline editing for this cell, opened by a double-click or, once the cell holds * keyboard focus (Tab/arrow into the row, then Right/Left onto the cell -- see the * class-level keyboard doc), by `F2` or `Enter`. One editor is open at a time; the table * emits the proposed value through `lr-cell-edit` and never mutates `row` -- apply the * change in the consumer and pass the updated `rows` back in. Omit for a column with no * inline editor at all. */ editTrigger?:'double-click'; /** Renders this cell's content. Required whenever the column can be in its plain-text * resting state -- i.e. always, for a `'double-click'` or unset `editTrigger`. */ cell:(row:T)=>unknown;})|(TableColumnCommon &{ /** `'always'` renders a persistent editor in every body cell of this column from first * paint -- a settings/rate-style column the user is expected to type straight into. The * table emits the proposed value through `lr-cell-edit` and never mutates `row`. * * These editors are plain tab stops outside the roving header/row/cell tabindex model, and * bind their `value` as a content attribute, so native dirty-value-flag semantics apply: * once the user has typed into one, an out-of-band `rows` update to that same cell no * longer replaces what they are still editing. An untouched editor picks up a new `rows` * value normally. */ editTrigger:'always'; /** Omittable for this `editTrigger: 'always'` arm: the persistent editor renders * unconditionally, so the table's render path never falls back to this renderer. Still * consulted as the `sortValue`-less sort fallback when `sortable` is set -- define * `sortValue` on such a column instead of relying on this being present. */ cell?:(row:T)=>unknown;}); /** Every event `` emits. `K` is the row-key type the element was parameterized with * (`LyraTable` -> `rowKey: number` in every detail below), defaulting to the * `string | number` union an unparameterized table has always carried. */ export interface LyraTableEventMap{blur:CustomEvent;focus:CustomEvent;'lr-priority-columns-visibility-change':CustomEvent>;'lr-sort-request':CustomEvent;'lr-sort':CustomEvent;'lr-row-click':CustomEvent>;'lr-row-expand-request':CustomEvent>;'lr-row-expand-toggle':CustomEvent>;'lr-load-more':CustomEvent;'lr-retry':CustomEvent;'lr-selection-change':CustomEvent>;'lr-filter-change':CustomEvent>;'lr-page-change':CustomEvent>;'lr-cell-edit':CustomEvent>;'lr-column-resize':CustomEvent>;} /** * `` — a sort/select-aware data table. * * A sortable-header activation first proposes a cancelable `lr-sort-request`. If accepted, client * mode writes `sortKey`/`sortDir` and reorders the rendered rows before emitting `lr-sort`; server * mode leaves those properties controlled and emits the same committed transaction so the caller * can fetch and supply the corresponding row order. Single/multiple selection is self-managed in * one `selectedRowKeys` store; row expansion follows the same opt-in shape through * `expansionMode`/`expandedRowKeys` -- see that property's own doc block below for the * request/commit detail. The * direction chosen the first time a column becomes the active `sortKey` comes from that column's * own `columns[].defaultSortDir` when set, falling back to the element-level `defaultSortDir` * (`'asc'` by default) otherwise -- letting one column in an otherwise-ascending table (e.g. a * "last updated" column) start descending. Re-activating the column that is already `sortKey` * still only toggles between `'asc'` and `'desc'`. * * Header/row activation is delegated: one `click` and one `keydown` * listener on `` resolve the target via `closest('[data-col-key]' * | '[data-row-key]')` and a key→object lookup map, instead of allocating * fresh per-column/per-row closures on every render. Both listeners inspect * the delegated event's composed path for actual native, role, or tabindex * semantics (see `INTERACTIVE_SELECTOR`) so a button/link/input inside a cell * owns its own activation instead of triggering `lr-row-click`. A passive * custom element remains part of the row activation surface; an opaque * closed-shadow control marks its host with `data-table-interactive`. * * Keyboard focus follows a roving-tabindex pattern (one `tabindex="0"` stop * among the header cells, one among the body rows — see `focusedColKey()` / * `focusedRowKey()`), matching this repo's other `role="grid"`/composite * widgets. Left/Right/Home/End move within the header row; Up/Down/Home/End * move within the body; Down from the header enters the body's roving stop, * and Up from the body's first row returns to the header's roving stop. * Enter/Space still only sort/activate (see `activateColumn()` / * `activateRow()`). When controlled rows or columns replace the focused * member, focus follows the same stable key when it survives and otherwise * clamps to the nearest surviving index; an update never reclaims focus once * the user has moved it outside the table. Effective locale changes use the same current * page identity for activation, editing, and direct row-focus restoration. * * A column with `editTrigger: 'double-click'` additionally gives its own resting cell a * `tabindex="-1"` roving-focus stop — reachable, once the row itself has focus, with ArrowRight * (ArrowLeft under RTL) to enter at the first editable cell in the row and step forward, ArrowLeft * (ArrowRight under RTL) to step back and, from the first editable cell, return focus to the row — * never through Tab, so a table with one or more editable columns gains no new Tab stop, only a * new arrow-reachable one, and a table with none renders no `tabindex`/`part='cell'[data-editable]` * at all. `F2` or `Enter` on that focused cell opens its editor (`startEditing()`); `editCell()` is * the same effect as a public method, for a consumer's own key binding, menu action, or other * trigger. `Enter` on the row itself (not a focused cell) still only activates the row, exactly as * before — the two coexist because the table tells them apart from which of the two currently has * focus, not from the key alone. Escape and Enter inside the open editor keep cancelling/committing * as already documented below; either one now also returns focus to the cell that opened it (the * editor's own DOM node is what closes), matching a conventional grid's F2/Escape contract and * closing the WCAG 2.1.1 (Keyboard) gap a pointer-only `double-click` trigger otherwise leaves. * Priority-hidden columns hide their header, body, and footer cells together; revealing * priority columns restores all three bands. * Blank and later-duplicate column keys are omitted first-wins at assignment. Rows retain their * caller-owned records, then one canonical `rowKey` projection omits blank and later-duplicate * identities before filtering, counts, pagination, focus, actions, and events. * * Set `aria-label` on the host to give the `role="grid"` element an * accessible name; it's forwarded into the shadow DOM's `
`. * * `columns[].priority` ('medium' | 'low') hides that column once measured overflow says * `[part='base']` actually needs the room -- `'low'` first, `'medium'` next if the table would still * overflow with just `'low'` gone -- never at a fixed container width; `[part='reveal-columns-button']` * forces them all back into view. The public `hasHiddenPriorityColumns` * property reports only whether a priority column is actually hidden right * now, measured via `ResizeObserver` on `[part='base']` plus a post-render DOM * check. The toggle separately measures whether priority columns would hide * at the current allocation, so it stays available while force-visible mode * is active without making `hasHiddenPriorityColumns` contradict the rendered * state. `priorityColumnsVisible` defaults to `false` and toggles itself on * `[part='reveal-columns-button']` activation with no external wiring * required, but is also settable up front (property or the reflected * `priority-columns-visible` attribute) to restore a previously-persisted * preference, and readable back — directly or via the `lr-priority-columns-visibility-change` * event — to persist the current one. `columns[].sticky` pins a column's * header/cells to the inline-start (`'start'`) or inline-end (`'end'`) * edge while the table scrolls horizontally. Every member of the priority-column family -- * `revealColumnsLabel`/`hideColumnsLabel` (which only ever reach the DOM on * `[part='reveal-columns-button']`), `priorityColumnsVisible` (which only overrides a hide rule * there is none of) and `storageKey` (which persists nothing else) -- is inert unless at least one * column declares `priority`, so configuring any of them without one logs a one-time, * production-silent, page-bounded development `console.warn` naming the members that will do * nothing (the same dev-diagnostic shape as an unnamed grid's own warning). The read-only * `priorityColumnsToggleAvailable` reports whether the reveal button is currently offered at all, * which is the same measured state the button itself renders from. * * `expandedContent` (a table-level `(row: T) => unknown`, not a per-column * hook, since the resulting panel spans every column via `colspan`) makes * every row render a leading chevron-toggle cell before its data columns. * `canExpand` optionally gates which rows actually get an interactive * toggle — a row that fails it still gets a blank leading cell for column * alignment. Which rows are currently open lives in `expandedRowKeys` (a set of row keys, per * `rowKey`/`keyOf()`), and `expansionMode` decides who writes it — mirroring `selectionMode` * member for member. Under the default `'none'` the set is fully consumer-owned: the table only * reads it and emits `lr-row-expand-toggle` on activation, exactly as it always has. Under * `'single'` or `'multiple'` the table proposes each change with the cancelable * `lr-row-expand-request` first and, unless a listener vetoes it, writes `expandedRowKeys` itself * before announcing the applied change with `lr-row-expand-toggle`. `'single'` keeps at most one * row open; the row it closes to make room gets its own `lr-row-expand-toggle` (`expanded: false`) * just before the accepted one, so the per-row event stays a complete account of what opened and * closed. Flipping `expansionMode` to `'single'` coerces an already-larger set down to its * first key the same way `selectionMode` does for `selectedRowKeys`. * * Neither mode clears keys when the visible rows change: filtering, sorting and pagination leave * `expandedRowKeys` alone, so a row scrolled, filtered or paged out of view returns expanded, and * a key matching no current row simply renders nothing until one exists again. That is * `selectedRowKeys`' own convention — valid off-view keys stay controlled state so a * server-paginated table can keep them. * * Selection is opt-in through the `selectionMode` property. Use `single` or * `multiple` to self-manage row selection; the default `none` remains * presentational. `selectedRowKeys` contains the raw keys in every mode; single mode enforces one. * * `rowElement(rowKey)`, `cellElement(rowKey, columnKey)` and `expandedContentElement(rowKey)` * resolve a rendered ``/`` of the data row rather than a descendant of it: * `rowElement`/`cellElement` reach `cell(row)` output only, and `expandedContentElement` returns * the `[part='expanded-cell']` holding `expandedContent(row)` output. All three read the current * render output, so `await table.updateComplete` first and treat `null` as "not rendered right * now". They resolve the `data-row-key`/`data-col-key`/`data-expanded-row-key` attributes those * elements carry: `data-col-key` is the column's own `key`, while `data-row-key` and * `data-expanded-row-key` are a type-tagged encoding of the row key (`string:a` vs `number:1`) * that keeps a numeric key distinct from the string that stringifies the same way. The panel * deliberately does not repeat `data-row-key`, so every `[data-row-key]` query still resolves * exactly one element per row. All three attributes are stable public API; prefer the methods over * building a selector from them, since a consumer-supplied key is not safe to interpolate into CSS * unescaped. * * `filterable` adds a compact search field above the grid. `filterText` is * controlled and emits `lr-filter-change`; `filter` can provide a typed * predicate, otherwise the row is matched against its JSON representation. * The internal filter and cell-editor native value events are contained at * their translation boundaries; hosts receive `lr-filter-change` and * `lr-cell-edit` instead. * `pageSize` bounds pagination through the existing `` primitive (100 rows by * default, normalized to 1..500). Client mode owns the accepted page and slices `rows`; server mode * leaves `page` controlled, bounds the supplied page to `pageSize`, and uses `totalItems` for the * navigation summary. `unknownTotal` (server mode only) forwards ``'s own * indeterminate mode for a caller with no total -- previous/next only, no numbered list, no * item-range summary -- with `hasNext` as the one extra signal that mode needs; see both * properties' own docs. `loading` keeps the table shell busy; `loadingAppearance` * chooses how — the default `'spinner'` replaces the grid with an indeterminate * spinner, while `'skeleton'` keeps the real ``/`` (and the * filter/pagination chrome) and fills the body with `skeletonRows` placeholder * rows, so column geometry survives the load instead of collapsing and * reflowing. Loading takes precedence over both empty branches. Because a skeleton needs a * column schema, a skeleton request received before `columns` arrives temporarily falls back to * the spinner rather than showing the no-columns empty state. Initial declarative loading stays * silent; every post-mount transition into either * loading appearance appends to the shared light-DOM polite sink — including repeated cycles — * while every placeholder opts out of ``'s own announcement. * Columns with `editTrigger: 'double-click'` open a native text/number/select editor on * double-click, `F2`, or `Enter` on the cell's own roving focus stop (see the keyboard paragraph * above), and emit `lr-cell-edit`; row mutation remains consumer-owned. `editType: 'select'` * renders a native `` -- a column with no `editOptions` renders an empty, valueless ``/``) getters * expose the same rows `footer(rows)`/`grandTotal(rows)`/the heat-tint domain already see, so a * consumer that needs "what the grid currently shows" -- e.g. to export it -- reads one of these * instead of re-implementing filtering, sorting, and pagination itself. Both return a fresh, frozen * array on every read; mutating the result cannot reach the table's own internal state. * * The built-in empty state is addressable rather than fixed: every `` the table renders * carries `part="empty"` and re-exports its own inner parts as `empty-heading`/`empty-description`/ * `empty-icon`/`empty-actions`/`empty-base`, the two *data*-empty branches (no rows at all, and * filtered/paginated down to zero) render it as the fallback content of a named `empty` slot so a * consumer can replace it wholesale, and `emptyCompact` overrides each branch's built-in `compact` * default. The no-columns branch is deliberately **not** slot-replaceable — it reports a * configuration problem (`noColumnsHeading`), not "this query returned nothing", and a single slot * covering all three would collapse that distinction. * * A separate `error` state reports a failed load without discarding grid context: while `error` is * set, ``'s single row becomes a failed-load `` (the same `error`-prefixed exported * parts as the empty state, plus a built-in `[part='retry-button']`), behind its own `error` slot — * but ``, the filter field, and pagination all stay mounted around it, unlike either * data-empty branch above, which replace them too. Precedence when more than one state could apply * at once: `loading` beats `error` beats every empty branch, so a `loading` table never flashes a * stale `error`, and an `error` table never falls through to "no rows"/"no columns" copy * underneath it. The retry button's `lr-retry` is cancelable: the built-in action clears `error`, * and `preventDefault()` leaves it set for a consumer that owns its own retry timing. * * `layout` sets a floor on the `
` from the identity a consumer already has, for code that has to * reach content its own `cell(row)` or `expandedContent(row)` callback rendered into this shadow * root (measuring it, scrolling it into view, or applying a style `::part()` cannot express, since * only pseudo-classes may follow a part selector). One method per callback, because the expansion * panel is a *sibling* `
`'s `table-layout`: `'fixed'` forces it even with no column * widths, while the default `'auto'` still resolves to `fixed` whenever a column declares a `width` * or a drag-resize is in flight (column resizing does not work under `table-layout: auto`). * * @customElement lr-table * @event lr-sort-request - Cancelable sort proposal. Frozen readonly * `detail: { phase: 'request', sortKey, sortDir }`. Vetoing it leaves sort state and rows * unchanged and suppresses `lr-sort`. * @event lr-sort - Accepted sort transaction. Frozen readonly * `detail: { phase: 'commit', sortKey, sortDir }`. Client mode also updates `sortKey`/`sortDir`; * server mode leaves them controlled while reporting the accepted proposal. * @event lr-row-click - A row was activated. `detail: { row }`. * @event lr-load-more - The "load more" control was activated. * @event lr-retry - The built-in `[part='retry-button']` was activated, only rendered while * `error` is set. Cancelable: the default action clears `error`; `preventDefault()` leaves it * set instead. * @event lr-priority-columns-visibility-change - `priorityColumnsVisible` was toggled by * `[part='reveal-columns-button']`. Frozen readonly `detail: { visible: boolean }`. * @event lr-row-expand-request - Cancelable proposal before a self-managed expansion change. * Frozen readonly `detail: { row, rowKey, expanded }`, where `expanded` is the state being * proposed. Emitted only while `expansionMode` is `'single'` or `'multiple'`; vetoing it skips * the built-in `expandedRowKeys` write and suppresses the following `lr-row-expand-toggle`, * leaving the row's expansion fully controlled. * @event lr-row-expand-toggle - The row-expand chevron was activated. * Frozen readonly `detail: { row, rowKey, expanded }`, where `expanded` is the state the * activation resolves to. Fired only when `expandedContent` is set and the row passes * `canExpand`. Under the default `expansionMode: 'none'` it does not itself mutate * `expandedRowKeys` — the consumer updates it and passes the new value back in. Under a * self-managed mode it follows an unvetoed `lr-row-expand-request` and the write has already * landed, so a listener reading `expandedRowKeys` sees the new state. `'single'` additionally * fires it once with `expanded: false` for the row it just closed to make room, immediately * before the accepted one, so a host mirroring open rows from this event alone stays correct — * with one boundary: a displaced row that is filtered or paged out of view has no `row` object * to describe, so that case is reported only through `expandedRowKeys`. The `'single'` coercion * that runs when `expansionMode` itself becomes `'single'` reports through `expandedRowKeys` * alone for the same reason — a key that matches no rendered row cannot carry a `row`. * @event lr-selection-change - Opt-in row selection changed, from a row activation or from a * `selectionMode` flip to `'single'` coercing an existing multi-row selection down to one key. * Frozen readonly `detail: { rowKeys: readonly K[] }` — `readonly (string | number)[]` on an * unparameterized table, the default `K`. Not cancelable in either case: it announces a * selection that has already changed rather than proposing one. * @event lr-filter-change - The filter field changed. Frozen readonly `detail: { text }`. * @event lr-page-change - A pagination control requested a page. Frozen readonly `detail: { page }`. * @event lr-cell-edit - An inline editor committed a value. `detail: { row, columnKey, value }`. * @event lr-column-resize - A resizable column changed width by pointer or keyboard. `detail: * { columnKey, width }`, where `width` is in CSS pixels. A pointer drag fires this once per pixel of * movement as non-cancelable live feedback, then once more, **cancelable**, for the final * width committed at drag-end; a keyboard step (Home/End/Arrow) is already a single discrete * action and fires that one cancelable commit directly. `preventDefault()` on a cancelable * emission reverts the column to its pre-gesture width -- unless the listener resolved the * resize itself during that same synchronous dispatch, in which case the width it applied stands * instead of being rolled back over. * @event focus - Re-dispatched from the internal filter/cell-editor native inputs' own `focus` — * bubbling and composed (unlike the native event, which is neither). * @event blur - Re-dispatched from the internal filter/cell-editor native inputs' own `blur`, for * the same reason as `focus`. * @csspart base - The root wrapper around the `
` and its footer controls. * @csspart table - The `
` element. * @csspart caption - The `` element. * @csspart header-cell - Each ``. * @csspart cell - Each body ``, only rendered when at least one column defines `footer`. * @csspart footer-row - The single footer row. * @csspart footer-cell - A single footer cell. * @csspart cell-editor - The native inline cell editor -- an `` for `editType: 'text'`/ * `'number'`, a `` rendered beneath a * row whose key is in `expandedRowKeys`. Carries `data-expanded-row-key`, not `data-row-key`: * it is a sibling of the data row, so repeating that attribute would make every * `[data-row-key]` query resolve two elements per open row. * @csspart expanded-cell - The single `colspan`-spanning `` that replaces the row content while `error` is * set. Present only in the in-grid branch: when `columns` is empty there is no grid to keep * mounted, so the failed-load content renders standalone and neither this part nor `error-cell` * exists. * @csspart error-cell - The ``, filter, and pagination * stay mounted rather than being replaced along with it. * @csspart error-base - Exported from the built-in error ``'s own `base` part. * @csspart error-icon - Exported from the built-in error ``'s `icon` part. * @csspart error-heading - Exported from the built-in error ``'s `heading` part. * @csspart error-description - Exported from the built-in error ``'s `description` part. * @csspart error-actions - Exported from the built-in error ``'s `actions` part; wraps * `retry-button`. * @csspart retry-button - The built-in retry control rendered into the error state's `actions`. * @slot empty - Replaces the built-in empty state on the two *data*-empty branches (no rows at * all, and filtered/paginated down to zero). Left unfilled, the built-in `[part='empty']` * `` renders as this slot's fallback content. The no-columns branch renders its own * `noColumnsHeading` state and is not slot-replaceable. * @slot error - Replaces the built-in failed-load state, including its retry button, while `error` * is set. Left unfilled, the built-in `[part='error']` `` renders as this slot's * fallback content. * @cssprop [--lr-table-resize-min-width=var(--lr-size-3rem)] - Default minimum width for a * resizable column without an explicit pixel `minWidth`. Inherits from theme ancestors. * @cssprop [--lr-table-resize-handle-opacity=0.12] - Hover/focus opacity of the resize handle. * Legacy shared-state hook; inherits from theme ancestors. * @cssprop [--lr-table-resize-handle-hover-bg=var(--lr-color-brand)] - Resize-handle hover/focus background. * @cssprop [--lr-table-resize-handle-hover-opacity=var(--lr-table-resize-handle-opacity,0.12)] - Resize-handle hover/focus opacity. * @cssprop [--lr-table-resize-handle-active-bg=var(--lr-table-resize-handle-hover-bg,var(--lr-color-brand))] - Resize-handle pressed background. * @cssprop [--lr-table-resize-handle-active-opacity=calc(var(--lr-table-resize-handle-hover-opacity,var(--lr-table-resize-handle-opacity,0.12))*2)] - Resize-handle pressed opacity. * @cssprop [--lr-table-cell-color=inherit] - Text colour of body cells; inherits the host's own * colour by default. * @cssprop [--lr-table-cell-link-color=var(--lr-color-brand)] - Colour of an anchor returned from a * column's `cell(row)`. Such an anchor renders inside this component's shadow root, so page CSS * cannot reach it and `::part()` cannot select past the first compound selector to reach it * either; without this hook it computes to the UA default link blue. Set `revert` for the UA * default. * @cssprop [--lr-table-cell-link-hover-color=var(--lr-table-cell-link-color,var(--lr-color-brand))] - * Colour of that anchor on hover and `:focus-visible`, which also thicken its underline. * @cssprop [--lr-table-cell-padding=var(--lr-space-s)] - Padding of a header cell, a body cell, and * the row-total cell. * @cssprop [--lr-table-cell-padding-compact=var(--lr-space-xs) var(--lr-space-s)] - Padding of a * group-header cell and a footer cell, which default to a tighter block/inline shorthand than * `--lr-table-cell-padding` rather than sharing it outright. * @cssprop [--lr-table-font-size=inherit] - Font size of the `
` element, rendered only when `caption` is set. * @csspart head - The `
` header cell. * @csspart resize-handle - The focusable separator used to resize a `resizable` column. Its * numeric ARIA range remains in CSS pixels while `aria-valuetext` reports the current value * through the effective locale. * @csspart row - Each body `
`. An `editTrigger: 'double-click'` column's resting (not * currently editing) cell additionally carries `[data-editable]` and its own `tabindex="-1"` * roving-focus stop -- see the keyboard paragraph above. * @csspart row-total-cell - Each body row's trailing `` holding `rowTotal(row)`, rendered only * when `rowTotal` is set. The corresponding footer-row cell (holding `grandTotal`) is a * `footer-cell` instead, matching every other footer cell. * @csspart foot - The `
` inside * `expanded-row`, containing `expandedContent(row)`. Resolved from script by * `expandedContentElement(rowKey)`. * @csspart group-row - A non-focusable group header row. * @csspart group-cell - The full-width group header cell. * @csspart filter - The optional row-filter input. * @csspart filter-label - The `
` inside `[part='error-row']`, spanning every column, that holds * the failed-load content. * @csspart error - The built-in `` host rendered in the row body while `error` is set. * Unlike `[part='empty']`'s no-rows branches, its surrounding `
` element and, through normal * inheritance, every cell inside it. The rest of the font shorthand (family, weight, etc.) keeps * inheriting from the host regardless of this override. * @cssprop [--lr-table-max-height=none] - Cap on the scroll container's block size, past which the * table body scrolls. * @cssprop [--lr-table-heat-tint-lo=var(--lr-color-brand-quiet)] - Low endpoint of the heat-tint * ramp used by `heatValue` columns. Inherits from theme ancestors. * @cssprop [--lr-table-heat-tint-hi=var(--lr-color-brand)] - High endpoint of the heat-tint ramp * used by `heatValue` columns. Inherits from theme ancestors. * @cssprop [--lr-table-heat-t] - This cell's position on the heat-tint ramp, as a percentage * string. Set inline by the component on each `[data-heat]` cell; not consumer-settable. * @cssprop [--lr-table-row-selected-bg=var(--lr-color-brand-quiet)] - Background of a row whose * `aria-selected` is `true`, including that row's own `sticky` column cell -- a sticky cell * otherwise paints its own opaque surface and would hide the selected fill. Shadow Parts forbids * an attribute selector after `::part()`, so `::part(row)[aria-selected]` is invalid CSS and the * selected row could otherwise only be restyled by hijacking the library-wide * `--lr-color-brand-quiet` token. * @cssprop [--lr-table-row-stripe-bg=transparent] - Background of alternating body rows, including * each row's own `sticky` column cell. The token is read only on rows carrying the internal * stripe marker, so it can be set on the table or an ancestor without affecting group, expanded, * hover, or selected rows. * @cssprop [--lr-table-header-sorted-bg=var(--lr-color-surface)] - Background of the currently-sorted column's * header cell (`[aria-sort]` other than `none`), including a `sticky` column's own header cell. * Same rationale as `--lr-table-row-selected-bg`: `::part(header-cell)[aria-sort]` is invalid CSS, * so this token is the supported way to recolor the sorted header without hijacking a * library-wide token. * @cssprop [--lr-table-header-sorted-color=inherit] - Text color of the currently-sorted column's * header cell. * @cssprop [--lr-table-sticky-offset=0] - Distance a `sticky` column pins from the inline edge. * Measured and set inline per column by the component so multiple sticky columns stack instead * of overlapping; falls back to `0` for the first one, or before the first measurement pass. * @cssprop [--lr-theme-scrollbar-width=auto] - Opt-in theme-level scrollbar width honored by the * `base` scroll container; unset, renders identically to before. Set on `:root` or any ancestor * to retune every internal scroll container in the library at once. * @cssprop [--lr-theme-scrollbar-gutter=auto] - Opt-in theme-level scrollbar gutter honored by the * `base` scroll container; see `--lr-theme-scrollbar-width`. * @status stable * @since 4.0.0 */ export declare class LyraTableextends LyraElement>{static styles:import("lit").CSSResultGroup[];private _columns; /** Clone-owned readonly column-definition sequence, bounded to the first 10,000 source * positions. Blank keys and later duplicate keys are omitted (first valid occurrence wins) * before any header, cell, sort, focus, or event path. Column objects and callbacks retain their * identities; reassign the collection to update. */ get columns():readonly TableColumn[];set columns(value:readonly TableColumn[]);private _rows; /** Clone-owned readonly row sequence, bounded to the first 10,000 rows. Row objects are retained * here; the rendered model applies `rowKey` as one unique nonempty first-wins identity * projection before filtering, counts, pagination, focus, actions, and events. Reassign the * collection to update. */ get rows():readonly T[];set rows(value:readonly T[]); /** Floor for the `
`'s `table-layout`. `'fixed'` forces the fixed algorithm even when no * column declares a `width`, so every column shares the available width evenly and long cell * content is clipped/wrapped instead of stretching its column. The default `'auto'` is only a * floor: it still resolves to `fixed` whenever a column declares a `width`, a column has been * drag-resized, or a resize gesture is in flight — resizing does not work under * `table-layout: auto`. * * Two consequences of the fixed algorithm are worth knowing before opting in: with no declared * widths the *first* row (header row included) determines every column's width, so revealing a * `priority`-hidden column via `[part='reveal-columns-button']` re-measures and changes all of * them; and `columns[].minWidth`/`maxWidth` are silently ignored by `table-layout: fixed` * (declare `width` instead when you need a specific column sized). */ private _layout;get layout():'auto'|'fixed';set layout(next:'auto'|'fixed');sortKey:string;sortDir:TableSortDirection; /** `'active'` preserves the active-column chevron alone. `'all'` also reserves the same icon * space with a muted bidirectional indicator in inactive sortable headers, including on touch * screens. This presentation choice does not change sorting, focus or aria-sort semantics. */ sortIndicators:TableSortIndicators; /** `'client'` (the default) orders `rows` itself, in the browser, from `sortKey`/`sortDir` and * the active column's `sortValue`. `'server'` renders `rows` in exactly the order given, * assuming the caller has already sorted them — mirroring `paginationMode`'s identical * client/server split. Header activation first emits cancelable `lr-sort-request`; an accepted * activation emits `lr-sort` either way, but only client mode mutates these properties. * * With no `sortKey` set (the default) `'client'` is a no-op: the input order is preserved * verbatim, so an existing consumer that only listens for `lr-sort` sees unchanged rendering * until a header is actually activated. */ sortMode:TableSortMode; /** The direction applied whenever header activation switches sorting to a *different* column — * including the first column ever sorted — for any column that does not declare its own * `columns[].defaultSortDir` (that column-level value wins first when set). Re-activating the * column that is already `sortKey` toggles between `'asc'` and `'desc'` instead, so this never * overrides a direction the user just chose for the column they are still on. Defaults to * `'asc'`; set `'desc'` for a most-recent-first or highest-first table. */ defaultSortDir:TableSortDirection; /** Accessible name for the `role="grid"` — a typed alternative to setting `aria-label` on the * host. When set it becomes the grid's `aria-label`; a host `aria-label` is used as a fallback * when this is unset. Consumer-supplied text, so it is NOT run through `this.localize()`. An * explicitly empty string is a real override (renders `aria-label=""`) rather than falling * back to the host `aria-label`. */ accessibleLabel?:string; /** Optional visible caption rendered as the table's `` (declared *and* drag-resized widths included), the same ``, the filter * field and the pagination footer — and fills `` with placeholder rows, so a cold load * sketches the grid's shape rather than collapsing to a spinner and reflowing when the rows * land. Kept separate from `loading` rather than widening it to a string union, so * `?loading=${…}` bindings and `el.loading === true` checks keep working. * When `columns` is empty, a skeleton request temporarily renders the spinner: loading still * takes precedence over the no-columns empty state, but there is no schema to sketch yet. * * Column *widths* only stay pixel-identical across the load if the browser isn't sizing them * from cell content: declare `columns[].width`, or set `layout="fixed"`. Under the default * `table-layout: auto`, placeholder cells have no intrinsic width, so the columns re-measure * when real content arrives — exactly as they do between any two different data sets. */ loadingAppearance:TableLoadingAppearance; /** Number of placeholder rows rendered by `loadingAppearance="skeleton"`. `0` (the default) * renders 3 placeholders for the ordinary bounded default, or derives a non-default explicit * `pageSize` (capped at 20). Positive explicit values are also capped at 20. Ignored entirely * under the default spinner appearance. */ skeletonRows:number; /** Inserts a non-focusable group header row wherever this key changes between consecutive * rendered rows. Supply `rows` with each group already contiguous — the table does not * re-order them to make them so, and the group order it renders is their first-appearance * order in `rows`. A client-mode sort (`sortMode: 'client'`) is applied *within* each group * rather than across the whole set, so sorting a grouped table on a column unrelated to the * group key reorders rows inside their groups and leaves the grouping itself intact. Sorting on * a column whose value is constant inside every group — the group column itself, most obviously * — reorders the *groups* by that value instead, since there is nothing to reorder within * them. */ groupBy?:(row:T)=>string|number;groupLabel?:(key:string|number,rows:readonly T[])=>unknown; /** Maximum rows mounted per page. Defaults to 100 and normalizes into 1..500 so a bare table * never creates an unbounded row-by-column DOM projection. */ pageSize:number; /** Current page. Client pagination updates it on accepted navigation; server pagination leaves it * controlled and only emits `lr-page-change`. */ page:number; /** Total item count for server pagination; `-1` derives it from filtered rows. */ totalItems:number;paginationMode:'client'|'server'; /** Marks server pagination as indeterminate -- the caller has no total item count, only whether * one more page exists (`hasNext`). Forwarded to the nested `` as its own * indeterminate mode (a `total="-1"` sentinel; see that component's docs), which then renders * previous/next only, with no numbered page list and no item-range summary. A dedicated boolean * rather than a second magic `totalItems` value: `totalItems="-1"` already means "derive from * the currently matching rows", and stacking a different meaning onto another negative number * would be exactly the kind of easy-to-mistake sentinel this library avoids elsewhere. Ignored * outside `paginationMode: 'server'` -- client mode always knows the exact row count it slices, * so it never needs the indeterminate layout. */ unknownTotal:boolean; /** Whether at least one more page exists past the current one. Consulted only alongside * `unknownTotal`; forwarded verbatim to the nested ``'s own `hasNext`. Defaults to * `true` so an indeterminate server pager stays navigable until the caller's API reports * otherwise. */ hasNext:boolean; /** Renders a full-width panel beneath a row when that row's key is in * `expandedRowKeys`. Table-level (not per-column) since the panel spans * every column via `colspan`. Setting this makes every row render a * leading chevron-toggle cell before all data columns; omit for no * leading cell at all (unchanged output). The returned content renders inside this * component's shadow root, behind the `expanded-cell` part -- page-level CSS selectors cannot * reach it, and `::part(expanded-cell)` only reaches that wrapping `` rather than part of the row. */ expandedContent?:(row:T)=>unknown; /** Gates whether a given row gets an interactive chevron/toggle at all, * when `expandedContent` is set. Omit to make every row expandable. A * row that fails this check still gets a leading cell (for column * alignment) but it renders empty — no button, no `aria-expanded`, no * click handler. */ canExpand?:(row:T)=>boolean; /** Accessible name for one row's expand/collapse chevron, read once per render for that row, * exactly like a column's `editLabel`/`cellTitle`. Consumer-owned text: it is used verbatim and * never passed through the localization runtime. * * Omit it and every chevron in the table shares the same localized `expand`/`collapse` name, * which carries no row context. That is fine for a handful of rows, but each chevron is its own * Tab stop, so a long table otherwise announces the same two names over and over with no way to * tell the rows apart. There is no default row context to add here: this component has no * row-header notion to derive one from (`rowKey` is an opaque identity, not a label). */ rowExpandLabel?:(row:T,expanded:boolean)=>string; /** Open/closed state, bounded to 10,000 keys and keyed the same way as `rowKey`/ * `selectedRowKeys`. Under the default `expansionMode: 'none'` the table never mutates this * itself — it only reads it to decide which rows currently render `expandedContent`, and the * consumer toggles it in response to `lr-row-expand-toggle`. Under `expansionMode: 'single'` * or `'multiple'` the table writes it on each accepted activation, exactly as `selectionMode` * does for `selectedRowKeys`, and a `preventDefault()` on `lr-row-expand-request` hands one * change back to the consumer. Reads return immutable detached `ReadonlySet` * facades; malformed and whitespace-only string keys are omitted and valid off-page keys remain * controlled in every mode — filtering, sorting and pagination never clear them. Reassign a new * set to update. */ private _expandedKeys;get expandedRowKeys():ReadonlySet;set expandedRowKeys(value:ReadonlySet); /** Re-types an internally computed key set for a write to `selectedRowKeys`/`expandedRowKeys`. * Every key this component derives is the `string | number` union `keyOf()` produces, while a * parameterized element's public surface promises the narrower `K`; the two are only * convertible through `unknown`, so the conversion lives in these two helpers rather than at * each of the six call sites. Nothing is normalized away by going through it -- both setters * re-run `keySet()` on whatever they receive. */ private asKeySet; /** The {@link asKeySet} counterpart for a `K`-typed event detail's key list. */ private asKeyList; /** Overrides the auto-derived heat-tint domain (min/max of every `heatValue` result across every * currently-rendered row — post-sort, pre-pagination, the same rows `footer(rows)` already sees). * Unset computes the domain automatically from the data, spanning every `heatValue`-defining * column together (a single shared scale across the whole grid, not one scale per column). */ heatTintScale?:{min?:number;max?:number;}; /** Renders a trailing ``'s row content with a built-in failed-load state, while the * surrounding ``, filter field, and pagination stay mounted — unlike either data-empty * branch below, which this state overrides and which replace that chrome too. Precedence when * more than one applies at once: `loading` beats `error` beats every empty branch, so a * `loading` table never shows a stale `error`, and an `error` table never falls through to * "no rows"/"no columns" copy underneath it. Reflected so `[error]` is selectable from * page-level CSS the same way `[loading]` already is. */ error:boolean; /** Optional failed-load heading override. Omission localizes `tableLoadFailed`; a supplied * string, including the built-in English text or an empty string, renders verbatim. Has no * effect once the `error` slot is filled. */ errorHeading?:string; /** Optional failed-load description. Empty by default (no description line); a supplied string, * including the built-in English value, renders verbatim — never localized, the same contract * as `emptyDescription`. Has no effect once the `error` slot is filled. */ errorDescription:string; /** Opts this table into announcing a failed-load state it already carries when it first mounts, * through the same shared assertive region and the same heading text the later `error` * transition announces, so the two paths cannot drift. Leave unset for a table that is part of * the page a user is arriving on: the built-in error state renders in document order and * repeating it is noise. Set it when the table is created in response to a user action — a * reload that rejects mounts a fresh `error` table whose failure would otherwise never be * spoken. Read once, on the first update: a later reconnection or adoption stages the same * state again rather than replaying the announcement, and later `error` transitions announce * either way. Deliberately not forwarded to the composed `[part='error']` ``, whose * own `announce` stays unset so the failure is spoken once, not twice. Remove any host * `role="status"`/`role="alert"` hand-added before this property existed once it is set -- * otherwise the failure is announced a third time, through the native role as well. */ announce:boolean;emptyHeading?:string;emptyDescription:string; /** Overrides the built-in `[part='empty']` state's `compact` rendering. Leave `undefined` (the * default) to keep each branch's own built-in behavior: the whole-table states (no columns, no * rows) render spacious, while the in-table filtered/paginated-to-zero state — which sits below * the filter field inside `[part='base']` — renders compact. `empty-compact="false"` forces the * spacious rendering everywhere. Has no effect once the `empty` slot is filled. */ emptyCompact?:boolean;noColumnsHeading?:string;noColumnsDescription:string;revealColumnsLabel?:string;hideColumnsLabel?:string; /** Whether the current rendered allocation actually hides at least one `priority` column. This * read-only state becomes false once `priorityColumnsVisible` reveals them; the toggle remains * available at narrow allocations through an internal capability measurement. */ hasHiddenPriorityColumns:boolean;private priorityToggleAvailable; /** Whether the reveal/hide control is currently offered at all -- the public, read-only * counterpart of the measurement `[part='reveal-columns-button']` itself renders from. True * while at least one `priority` column is actually hidden at the current allocation, and it * stays true once `priorityColumnsVisible` has revealed those columns (otherwise the control * would remove itself the moment it was used, stranding the columns visible). Always false with * no `priority` column declared, which is the state the inert-configuration development * warning describes. Remeasured from the live DOM after every render and on every container * resize, so read it after `await table.updateComplete`. */ get priorityColumnsToggleAvailable():boolean; /** Forces `priority`-hidden columns back into view, overriding the * measured-overflow hide rules in table.styles.ts. Toggles itself on * `[part='reveal-columns-button']` activation by default — no external * wiring is required for the button to work. Also settable from outside * (property or the reflected `priority-columns-visible` attribute) to restore a * previously-persisted preference. The single * `lr-priority-columns-visibility-change` event reports button-driven changes. * @default false */ priorityColumnsVisible:boolean; /** Persists `priorityColumnsVisible` to `localStorage` across reloads when set. Namespaced as * `lr-table:${storageKey}`. Restoration never overwrites a `priorityColumnsVisible` the consumer * declared on the same mount (`priority-columns-visible` present, or a * `.priorityColumnsVisible=${...}` binding) -- including a binding that pins it to `false`, its * own default. The same "explicit beats persisted" guarantee as `lr-app-rail`'s and * `lr-widget`'s `storage-key` restores, which share this one's write-tracking mechanism. */ storageKey?:string;private get storageFullKey(); /** Skips the very first `updated()` pass so mounting never writes to storage -- `willUpdate()` * restored `priorityColumnsVisible` on that first pass, and Lit has already flipped `hasUpdated` to true * by the time `updated()` runs, so a dedicated flag is needed. Mirrors `lr-app-rail`'s * `persistReady`. */ private persistReady;private announcementSink?;private errorAnnouncementSink?;private firstUpdateAnnouncementsReady; /** Roving-tabindex position among header cells; `null` until a header is * clicked/navigated to, at which point `focusedColKey()` falls back to * the first column. */ private activeColKey; /** Roving-tabindex position among body rows; `null` until a row is * clicked/navigated to, at which point `focusedRowKey()` falls back to * the first surviving `selectedRowKeys` member (if it matches a row) or the first row. */ private activeRowKey;private editingCell; /** The persistent (`editTrigger: 'always'`) editor cell that most recently took focus, recorded by * the delegated `focusin` handler. `repeat()` is keyed by row key, so a re-sort *moves* the * `` node (its typed value rides along) rather than recreating it -- but a DOM move drops * focus, so `updated()` puts it back. Deliberately non-reactive: it tracks focus, and writing it * must never schedule a render. */ private focusedEditorCell; /** Whether `focusedEditorCell` still actually held focus when the in-flight update started. * Captured in `willUpdate()`, i.e. before `render()` has had the chance to move the node out * from under it. Without this, a record left behind by a user who has since clicked away * entirely (no `focusin` reaches this component to clear it) would let any later, unrelated * update yank focus back into the table. */ private editorHadFocusBeforeUpdate; /** Focused roving member captured before a controlled collection update removes or moves its * DOM node. `targetKey` is resolved from the new collection before render; `updated()` only has * to put focus on the already-correct `tabindex="0"` owner. */ private rovingFocusSnapshot;private _resizedColumnWidths; /** Opened immediately before each cancelable `lr-column-resize` commit and read immediately * after it. Both commit paths apply the new width optimistically and roll it back when the * event is vetoed -- a write that lands *after* the synchronous dispatch, so without this it * also overwrites a width a listener resolved for itself from inside that dispatch (it refuses * the proposed step and drives the component's own resize affordance instead). Tracking that a * write happened, rather than comparing before/after widths, is what keeps "the listener chose * this width" distinguishable from "nothing touched it". */ private readonly resizeWriteGuard; /** Accessor-backed purely so every write marks `resizeWriteGuard`; the map itself is replaced, * never mutated in place, exactly as before. */ private get resizedColumnWidths();private set resizedColumnWidths(value);private resizeState?; /** Window that owns the active resize gesture's global pointer listeners. */ private resizeEventWindow?;private rowsByKey;private rowsLocale?;private columnsByKey; /** Watches `[part='base']` and `[part='table']` for the size changes that * `recomputeHiddenPriorityColumns()`'s measured-overflow check reacts to — so a * `priority` column flipping hidden/visible from an *external* width * change (a window resize, an ancestor flex-layout reflow, ...) is caught * even though no Lit-tracked property changed. Mirrors * lite-chart.ts's connectedCallback()/disconnectedCallback() ResizeObserver * lifecycle. */ private resizeObserver?; /** Owner-bound rAF for the coalesced `resizeObserver` callback below — an animated ancestor resize (a * CSS transition/drag on a containing panel) can fire the observer once per animation frame, * and each tick's full synchronous read+write pass (offsetParent over every priority header, * a fresh `[data-col-key]` query per sticky column, an aria-valuenow write per resize handle) * would otherwise run unbatched on every single one of them. Mirrors lite-chart.ts's/ * heatmap.class.ts's own `drawRafId` coalescing pattern. */ private layoutFrame; /** The `[part='base']` element `resizeObserver` is currently observing — * `render()`'s columns/rows-empty branches swap in the built-in * (or `empty`-slotted) empty state instead, * a different template shape that gives `[part='base']` a fresh DOM * identity on the next non-empty render, so `updated()` re-observes * whenever this no longer matches the live element. */ private observedBase?; /** The rendered `
`. Also names the grid (via * `aria-labelledby`) when no `accessibleLabel`/host `aria-label` is set. Consumer-supplied * text, not localized. */ caption:string; /** Stable id for the ``, so `aria-labelledby` can point at it. */ private readonly captionId; /** Derives each row's stable identity for `repeat()`'s DOM-reconciliation * key and the delegated click/keydown row lookup (`rowsByKey`, * `data-row-key`). When omitted, `keyOf()` falls back to the row's index * in `rows`, which is only a safe identity while `rows` never reorders — * provide `rowKey` whenever `rows` can be sorted, filtered, or otherwise * re-ordered across renders, or row identity (selection, focus, click * targets) can silently attach to the wrong row. Empty string identities and later duplicates * are omitted before every rendered/count/focus/action/event path; the first valid occurrence * wins. */ rowKey?:(row:T)=>K;selectionMode:TableSelectionMode; /** Who owns `expandedRowKeys`, mirroring `selectionMode`'s three members. `'none'` (the default, * and the behaviour this component shipped with) leaves the set entirely consumer-controlled: * activation only reports `lr-row-expand-toggle`. `'single'` and `'multiple'` self-manage the * set behind the cancelable `lr-row-expand-request`, with `'single'` keeping at most one row * open and coercing an already-larger set down to its first key when this property becomes * `'single'`. Closing a row to make room for another is an expansion change like any other, so * `'single'` reports the displaced row with its own `lr-row-expand-toggle` (`expanded: false`) * ahead of the accepted one -- unless that row is filtered or paged out of view, which leaves * it no `row` to describe. The property-driven coercion above reports through * `expandedRowKeys` alone for the same reason. No mode ever clears keys because the visible * rows changed -- see the class JSDoc's note on off-view keys, which follows * `selectedRowKeys`' convention. */ expansionMode:TableExpansionMode; /** Which element scrolls when the table overflows; see `TableScrollMode`. `'auto'` keeps page * flow while content fits and contains horizontal overflow only when needed. Defaults to * `'self'`, which is the pre-10.0 behaviour. */ scrollMode:TableScrollMode;private _selectedKeys; /** Selected raw row keys in every selection mode, bounded to 10,000 keys. Single mode replaces * this set with exactly one key per row activation; multiple mode toggles membership. Reads * return immutable detached `ReadonlySet` facades; malformed and whitespace-only string keys are * omitted while valid off-page keys are retained for server pagination. Reassign a new set to * update. */ get selectedRowKeys():ReadonlySet;set selectedRowKeys(value:ReadonlySet);filterable:boolean;filterText:string;filter?:(row:T,text:string)=>boolean; /** Optional filter-copy overrides. Omission localizes the matching message key; supplied * strings, including the built-in English text or an empty string, render verbatim. */ filterLabel?:string;filterPlaceholder?:string; /** Forwarded to the filter input's, and (when the active column's `editType` is `'text'`, the * default) the inline cell-editor input's, native `spellcheck`. Defaults to `true`, matching * the native element's own default. `spellcheck="false"` is parsed as `false` (see * `spellcheckConverter` above). No effect on a `'number'` or `'select'` cell editor. */ spellcheck:boolean; /** Forwarded to the same inputs' native `autocapitalize`. Empty string omits the attribute * (browser default). */ autocapitalize:string; /** Forwarded to the same inputs' native `autocorrect` (Safari/WebKit-specific). Empty string * omits the attribute (browser default). Named `autoCorrect` (capital `C`), not `autocorrect`, * to dodge a TS `lib.dom.d.ts` collision -- same fix as ``/``. */ autoCorrect:string;loading:boolean; /** Optional loading-copy override. Omission localizes `tableLoading`; a supplied string renders verbatim. */ loadingLabel?:string; /** How `loading` renders. `'spinner'` (the default, unchanged output) replaces the whole grid * with an indeterminate spinner. `'skeleton'` instead renders the real table — the same * `
`, not the descendants * this callback returns (`::part()` is a pseudo-element; only pseudo-classes may follow it, so * `::part(expanded-cell) .child` never matches, the same limitation `cell(row)`'s returned * anchors run into). Style such content by returning already-styled elements -- inline * `style`, or elements that reference this table's own `--lr-*` design tokens, which inherit * across the shadow boundary like any custom property -- rather than depending on a * page-level selector to find it. When script has to reach the rendered panel anyway (to * measure it or scroll it into view), `expandedContentElement(rowKey)` resolves that ``; * `rowElement()` does not, since the panel is a sibling `
` on every body row holding this row's total. Same * "consumer computes/renders, table only positions" contract as the existing per-column * `footer(rows)` — does not assume addition, so a non-sum aggregate works identically. Omit for * no trailing column at all (unchanged output). */ rowTotal?:(row:T)=>unknown; /** Renders the bottom-right cell (row-total column × footer row). Only rendered when both * `rowTotal` is set **and** at least one column defines `footer` — otherwise there is no footer * row for it to occupy, and this renders nothing. */ grandTotal?:(rows:readonly T[])=>unknown;hasMore:boolean; /** Optional copy overrides. Omission localizes the matching message key; supplied strings, * including the built-in English text or an empty string, render verbatim. */ moreLabel?:string; /** Non-`false` replaces `
` is observed separately because intrinsic content can grow its width * without changing `[part='base']`'s own border box. */ private observedTable?;private readonly observedHeaders; /** Last-measured rendered width (border box, summed across every header sharing that tier) of * each priority tier while it was actually visible -- `display: none` collapses an element's own * box to nothing, so a hidden tier's contribution to the table's full (everything-visible) width * has to come from here instead of a live measurement. Refreshed every layout pass in which that * tier's headers are actually rendered (including while `priorityColumnsVisible` force-reveals * them), which for a newly-hidden tier is every pass up to and including the one that first hides * it -- so the cache is always populated before it is ever needed. Never reset on disconnect: a * reconnect's first measurement pass re-hides from a stale-but-still-reasonable cached width * sooner than it otherwise could, and a genuinely stale entry self-corrects the next time that * tier is visible. */ private readonly priorityTierNaturalWidth; /** Last-measured natural (unstretched) rendered width of every ALWAYS-visible (no `priority`) * header combined, mirroring `priorityTierNaturalWidth` above for the one group that is never * itself hidden. It exists for the same reason: once any tier is hidden, `[part='table']`'s own * `inline-size: 100%` (table.styles.ts) makes the browser's auto table layout stretch every * still-rendered column to fill whatever room the hidden ones vacated, so `[part='base']`'s own * `scrollWidth` stops carrying the always-visible group's true content width and starts tracking * `clientWidth` instead -- reconstructing "how wide would everything be" from that stretched * number silently turns "would the full set fit" into "does the CURRENT container happen to have * spare room", which never re-admits a hidden tier once the remainder alone fits any container. * Refreshed only on a pass where `[part='base']` is genuinely too wide for its container * (`scrollWidth - clientWidth` exceeds the tolerance, computed from whatever the previous pass * left rendered) -- the one condition under which nothing on screen has spare room to stretch * into, so every currently-rendered header's own rect reports its real content width. That * condition necessarily holds on the pass that first decides to hide anything (the hide decision * itself requires it), so this is always populated before `recomputeHiddenPriorityColumns()` ever * needs it for a widening pass, and a stale entry only lingers while nothing overflows -- exactly * when substituting it for a live, potentially-stretched reading is the correction, not a risk. */ private alwaysVisibleNaturalWidth;private parsePixelLength; /** The themed floor a column may be dragged/keyed down to, in used pixels. `rem`/`em` resolve * against the live root/own font size through the shared `resolveCssLength()` -- a hardcoded * `* 16` would pick the wrong floor on a page whose root font-size isn't the browser default. * A token in a unit with no used pixel length here (`ch`, `pt`, `calc()`, a bare `%` with no * base) falls back to DEFAULT_RESIZE_MIN_WIDTH_PX rather than being read as raw pixels. */ private minimumResizeWidth;private maximumResizeWidth;private currentResizeWidth;private resizeValueText;private resizeColumnTo;private renderedColumnWidth;private onResizePointerDown;private onResizePointerMove;private onResizeKeyDown;private renderResizeHandle;private syncResizeHandleValues;private onResizePointerEnd;private detachResizePointerListeners;private rollbackResizePreview;private cancelResizeGesture; /** Coalesces however many `resizeObserver` callback ticks land in one animation frame (an * animated/dragged ancestor resize can fire the observer once per frame) into a single * read+write pass, instead of re-running `recomputeHiddenPriorityColumns()` / `applyStickyOffsets()` / * `syncResizeHandleValues()` -- each its own DOM query plus per-element measurement -- on every * tick. A second tick that lands while a frame is already pending is a no-op; the id resets once * the scheduled frame runs, so the very next tick after that schedules a fresh one. */ private scheduleLayoutSync;protected firstUpdated(changed:PropertyValues):void;connectedCallback():void;disconnectedCallback():void;private releaseAnnouncementSink;private syncAnnouncementSink;private localizedOverride;private loadingText;private observeBase;private observeTable; /** Applies the opt-in responsive scroll policy from rendered geometry. A one-pixel tolerance * absorbs CSSOM's integer rounding of fractional layouts, matching the library's other * overflow-aware controls. Existing `'self'` and `'page'` modes never retain this internal * measurement marker, so their established CSS remains authoritative. */ private syncAutoScrollMode;private observeHeaders; /** Every currently-rendered `` -- exactly the rows on screen * right now. Same defensive-copy guarantee as `viewRows`. */ get pageRows():readonly T[];private onFilterInput;private onClearFilter;private stopOwnedEvent;private onNativeFocus;private onNativeBlur;private onPaginationChange; /** The header cell that currently owns `tabindex="0"`. */ private focusedColKey; /** The body row that currently owns `tabindex="0"`. */ private focusedRowKey;private rowsAffectingRovingFocusChanged; /** Captures a direct header/row focus owner before `repeat()` moves or removes it. Nested * controls and persistent editors deliberately do not qualify: they own their own focus * contracts, and the editor restoration path below handles the one supported moved-node case. */ private captureRovingFocus; /** Resolves the captured key against the new controlled collection before render, so the new * DOM immediately assigns `tabindex="0"` to the member `updated()` will focus. */ private resolveRovingFocusTarget;protected willUpdate(changed:PropertyValues):void; /** Each sticky column's cumulative inline-start offset — the sum of the *rendered * width* of every earlier sticky column — so multiple sticky columns * stack left-to-right instead of all pinning to inset-inline-start: 0 and * overlapping. Table columns are intrinsically sized (not fixed-width), so * this can't be computed in CSS alone; it requires measuring the actual * laid-out `offsetWidth` of each earlier sticky column's header cell. */ private stickyOffsets; /** `[lo, hi]` of every `heatValue` result across every matching row (post-sort, pre-pagination) and * every `heatValue`-defining column, or `null` when heat-tint mode is off or there's no usable * domain (no numeric values and no override). `heatTintScale` overrides either or both bounds. */ private computeHeatDomain; /** This cell's tint share as a CSS percentage string (e.g. `"42.00%"`), or `null` when the column * has no `heatValue`, the domain is unavailable, or this row's value is missing/non-finite. */ private heatShare;private applyStickyOffsets; /** Applies stickyOffsets()'s measured per-column offsets as an inline * `--lr-table-sticky-offset` custom property on every header cell and * body cell in that column (addressed by the shared `data-col-key` * attribute). This is a post-render DOM measurement — column widths * aren't known until after the browser has laid out this update's * render() output — so it runs from `updated()`, not `willUpdate()`, * intentionally kept as a separate pass from the rowsByKey/columnsByKey * rebuild above: those two must stay in `willUpdate()` so `render()`'s * own `focusedRowKey()` call sees the current update's identity maps * (e.g. freshly-assigned `selectedRowKeys` resolving to the correct * roving-tabindex row on the very first paint), whereas the sticky-offset * measurement can only run after that same paint has happened. Only runs * when `hasSticky` is true (opt-in) and simply recomputes on every * update; column widths are measured per update so the current layout * reflects the rendered columns. */ protected updated(changed:PropertyValues):void; /** Restores only focus that the current render displaced. If another internal control or an * outside element owns focus now, the snapshot is discarded instead of stealing it back. */ private restoreRovingFocus; /** Header activation (click, Enter, Space) proposes one canonical sort transaction. Client mode * owns the accepted state; server mode leaves state controlled while reporting the same commit. */ private activateColumn;private activateRow; /** Whether any column opts into persistent (`editTrigger: 'always'`) editors -- * the table-level flag inferred from the columns themselves, mirroring how * `heatValue`/`width` opt their own modes in with no separate boolean. */ private get hasAlwaysOnEditors();private editorValue; /** Double-click only ever *opens* an editor, so an `'always'` column is * deliberately excluded: its editor is already open, and setting * `editingCell` for it would render a second, competing editor in the same * cell. */ private startEditing;private commitEdit; /** The `[part='cell-editor']` rendered in one specific body cell, or `null` when that row/column * is not currently rendered (paginated away, filtered out, column removed) or holds no editor. * Matched by walking `data-row-key`/`data-col-key` rather than by interpolating them into a * selector: both are consumer-supplied strings, and the row key additionally carries an encoding * prefix (`string:a`), so neither is safe to splat into CSS unescaped. */ private editorElementFor; /** Records which persistent editor holds focus, and drops the record as soon as focus lands on * anything else inside the grid. Only `'always'` columns are tracked: a double-click editor is * closed (and its node removed) by the very updates this would restore focus across. */ private onTableFocusIn; /** Puts focus back into the persistent editor this update moved it out of. Runs from `updated()`, * after `render()`'s DOM moves have landed. A row that left the rendered set entirely * (pagination, filtering) only clears the record: yanking focus to whichever unrelated row now * occupies that position would be worse than losing it. */ private restoreAlwaysOnEditorFocus; /** The editor owns its own keys. `stopPropagation()` is unconditional and stays that way: inside * a text field arrow keys are caret movement, not grid navigation. * * Enter commits either flavor. For a double-click editor that also closes it (`commitEdit` * clears `editingCell`); a persistent editor has no closed state to fall back to, so it stays * open and keeps focus. * * Escape cancels a double-click edit, which is a real action, so it is consumed. A persistent * editor has nothing to cancel back to -- `editingCell` was never set for it -- so Escape is * left uncancelled and an ancestor dialog/popover still closes on it. */ private onEditorKeyDown;private onTableDoubleClick; /** Chevron activation. Under the default `expansionMode: 'none'` this reports the activation and * nothing else, which is all it ever did. Under a self-managed mode it runs the library's one * request/commit helper: the cancelable `lr-row-expand-request` carries the proposed state, and * only an unvetoed proposal writes `expandedRowKeys` and then announces the applied change with * `lr-row-expand-toggle`. * * No `VetoWriteGuard` is passed, matching ``'s group-collapse pair: a request * listener's own `expandedRowKeys` assignment runs inside `emit()`, i.e. before `commit()` * writes, so today it is overwritten and only `preventDefault()` stops the built-in write. * Passing a guard would newly let that assignment suppress the commit — a behaviour change * rather than the shared shape. A listener that wants to own one change vetoes it. */ private activateExpandToggle; /** The rendered `` for one row key, or `null` when that row is not in the current render * output (filtered out, paginated away, or never present). Public counterpart of the * `data-row-key` attribute the row carries: the attribute's value is a type-tagged encoding * (`string:a` vs `number:1`), and a consumer-supplied key is not safe to interpolate into a CSS * selector unescaped, so resolve it through here instead of building one. Reads the DOM as it * stands — `await table.updateComplete` before calling it after changing any input. */ rowElement(rowKey:K):HTMLElement|null; /** The rendered `` of the data * row, not a descendant of it, so it carries its own `data-expanded-row-key` attribute (encoded * exactly like `data-row-key`, and left off the data-row attribute so every existing * `[data-row-key]` row query keeps resolving one element per row). Same `updateComplete` * contract as {@link rowElement}. */ expandedContentElement(rowKey:K):HTMLElement|null; /** Row lookup by the already-encoded `data-row-key` token, shared by the public API above and * the internal editor-focus bookkeeping, which only ever holds encoded tokens. Walks the rows * rather than interpolating the token into a selector, for the escaping reason above. */ private rowElementForToken;private cellElementForToken; /** Header cells currently in the tab sequence — excludes columns hidden by * a `priority`-driven measured-overflow rule (table.styles.ts/ * `recomputeHiddenPriorityColumns()`), so Left/Right/ * Home/End never strand the roving tab stop on a `display: none` cell * that `.focus()` would silently no-op on. Scoped to `th` — body ``, is what keeps the grid's geometry stable across the load. * They carry no `data-row-key` and no `tabindex`: they are not data rows, so the delegated * click/keydown handlers and the roving tab stop ignore them. */ private renderSkeletonRows; /** The failed-load content itself, shared by the in-grid error row and the standalone no-columns * error branch in `render()` so the two cannot drift in copy, parts, or slot name -- both go * through the shared {@link renderDataState} ladder renderer (`internal/data-state-renderer.ts`), * which owns the `error`-prefixed part naming, the `error` slot wrapping, and the cancelable * `lr-retry` request/commit pair. `compactDefault` differs between callers: the row sits inside * an existing grid and defaults to compact, while the standalone branch owns the whole component * box and matches the other full-area empty states. */ private renderErrorContent; /** The single full-width row rendered in `` while `error` is true. Mirrors the `empty` * slot/`` shape (same exported-part naming scheme, `error`-prefixed) so the two states * stay visually and structurally consistent, plus the built-in retry affordance -- but unlike * either data-empty branch in `render()`, the caller keeps ``, the filter field, and * pagination mounted around this row instead of replacing them too. */ private renderErrorRow;render():TemplateResult;}declare global{interface HTMLElementTagNameMap{'lr-table':LyraTable;}}export{};
` sharing `tier` -- usually one, but `columns` may declare more * than one column at the same tier, and they hide/reveal together. */ private priorityTierHeaders; /** Recomputes the measured-overflow priority-hide state from the live DOM: writes * `data-hide-priority-low`/`-medium` on `[part='base']` (table.styles.ts's replacement for a * fixed `@container` breakpoint), and separately tracks whether the reveal/hide toggle remains * useful while force-visible. Shares its overflow signal -- `[part='base']`'s `scrollWidth` versus * its `clientWidth` -- with `syncAutoScrollMode()`'s `scroll-mode="auto"` check, so the two * responsive systems key off the same measurement instead of disagreeing about whether the table * is actually too wide for its container. A tier hides only once hiding it would actually help: * `'low'` first (reconstructing the fully-visible width from every group's cached natural width * below), then `'medium'` on top of that if the table would still overflow with just `'low'` gone. * Writing the same decision this function already reached is a no-op `toggleAttribute()` call, * which keeps the `ResizeObserver` round-trip this triggers (hiding a column changes * `[part='base']`'s/`[part='table']`'s own measured size) a fixed point rather than a layout * thrash: the next pass reconstructs the same full width from the same cached natural widths, * reaches the same decision, and writes nothing further -- in either direction, hiding or * restoring. Called from `updated()` (covers a change driven by * `columns`/`rows`/`priorityColumnsVisible` rather than a container resize) and * from the `ResizeObserver` callback (covers a container resize with no * Lit-tracked property change at all). */ private recomputeHiddenPriorityColumns;private rehomeFocusedColumn;private keyOf; /** The one identity projection shared by filtering, sorting, pagination, focus, actions, events, * totals, and rendering. Identity is resolved once per source occurrence and retained on the * entry, so a stateful `rowKey` callback cannot make those consumers disagree within one model. */ private cachedCanonicalRowEntries;private canonicalRowEntriesInputs;private canonicalRowEntries; /** Memoized across update cycles and re-validated against the exact inputs the computation * reads — `rows` and `filter` by identity, the trimmed filter text, and (only when there is * text to case-fold at all) the effective locale. This method is read (directly or * transitively, via `matchingTotalItems`/`pageCount`/`appliedPage`/`renderedEntries()`) around * a dozen times across one `willUpdate()` + `render()` pass, and an *unrelated* reactive * update (a roving-tabindex move, an inline-editor open, ...) shouldn't re-run the * `JSON.stringify()`-per-row default filter over the full `rows` array even once — comparing * the recorded inputs instead of dropping the cache on every update keeps both cases to a * single filtering pass. The locale is compared as its resolved string, so a change that * arrives without a matching reactive-property key (an ancestor `lang` edit picked up on the * next update, `setLyraLocale()`'s keyless `requestUpdate()`) still recomputes. */ private cachedMatchingEntries;private matchingEntriesInputs;private matchingEntries; /** Read-time-safe view of `pageSize` -- finite, truncated and bounded to 1..500. * Mirrors ``'s own identically-named getter (this component composes that * primitive for the actual pagination UI in `render()`, but slices `rows` itself for client-mode * pagination, so it needs the same safe count independently). */ private get normalizedPageSize(); /** Placeholder row count actually rendered by `loadingAppearance="skeleton"`. An explicit, * positive `skeletonRows` wins after applying the shared bound; otherwise the count is derived * from the normalized `pageSize` under the same bound. */ private get effectiveSkeletonRows(); /** `totalItems: -1` (the default) is a sentinel meaning "derive from filtered rows" -- normalize * first so a non-finite/garbage `totalItems` degrades to that same derived-count fallback * instead of propagating NaN, while a genuine non-negative value is still honored verbatim. */ private get matchingTotalItems(); /** `unknownTotal`, gated to the one mode it applies to -- client mode always knows the exact row * count it is slicing, so it never enters the nested ``'s indeterminate layout. */ private get serverUnknownTotal();private get pageCount(); /** Read-time-safe view of `page`, clamped to `[1, pageCount]` -- mirrors * ``'s own `currentPage` getter and, like ``'s `currentTime` * setter, clamps against a dynamic, just-computed upper bound rather than a fixed one. */ private get appliedPage(); /** Memoized on the same principle as `matchingEntries()` above, and re-validated against the * exact inputs the sort reads. `renderedEntries()` is read several times per update pass (the * `rowsByKey` rebuild, `focusedRowKey()`, and `render()` itself), and a miss costs one * consumer-supplied `sortValue()`/`cell()` call per row on top of the comparison pass — sorting * once per reader would multiply that callback work by the number of readers for no benefit. * `columns` is compared by identity because the ordering reads `sortValue`/`cell` off the * active column object; `locale` because it selects the collator. */ private cachedSortedEntries;private sortedEntriesInputs; /** `matchingEntries()` reordered by the active sort column. A no-op — the filtered array is * returned by identity, never copied — under `sortMode: 'server'`, with no `sortKey`, or when * `sortKey` names a column that is missing or not `sortable`, which is what keeps a table that * never sets `sortKey` rendering its input order verbatim. * * When `groupBy` is set the sort is applied *within* each group rather than across the whole * set. `render()` emits a group header wherever the group key changes between consecutive * rendered rows, so a flat global sort on a column uncorrelated with the group key would * interleave the groups and emit a header before nearly every row — the grouping would be * visually destroyed by sorting. Groups keep their first-appearance order in `rows` (a `Map` * iterates in insertion order); the group key is deliberately *not* collated, because the * consumer controls group order through the order it supplies `rows` in, exactly as it does * when no sort is active. * * The one exception: when the active column's value is constant inside *every* group, the * within-group sort is provably inert, so the *groups* are ordered by that constant value * instead. Without it, sorting on the group column would flip `aria-sort` and the chevron while * changing nothing — announcing an ordering the table never applied. */ private sortedEntries;private renderedEntries; /** `rows` after filtering and (client-mode) sorting, ignoring pagination -- the same set * `columns[].footer(rows)`/`grandTotal(rows)` and the heat-tint domain already compute over. * Lets a consumer that needs "what the grid currently shows" (e.g. exporting the visible rows) * read this instead of re-implementing filtering/sorting itself. Reads `sortedEntries()`'s * existing memoized cache, so an unrelated update (a roving-tabindex move, an inline-editor * open, ...) costs nothing extra here. Always a fresh, frozen array: mutating what this returns * cannot reach or corrupt the table's own internal state. */ get viewRows():readonly T[]; /** `viewRows` sliced to the page currently rendered in `
` at one `(rowKey, columnKey)` pair, or `null` when either the row is not * currently rendered or the column is not in `columns`. `columnKey` is the column's own `key`, * which is exactly what the cell's public `data-col-key` attribute carries. Same * `updateComplete` contract as {@link rowElement}. */ cellElement(rowKey:K,columnKey:string):HTMLElement|null; /** Opens the inline editor at one `(rowKey, columnKey)` pair -- the imperative entry point behind * the `F2`/`Enter` keyboard shortcuts (`onRowKeyDown`) and the double-click pointer path * (`onTableDoubleClick`), for a consumer that wants to bind its own key, menu action, or other * trigger to the same effect. A no-op, not a throw, for an unknown row key, an unknown column * key, or a column with no `editTrigger` at all -- exactly `startEditing`'s own guard, so this * never opens a second, competing editor and never targets a column that has no editor to open. * * An `editTrigger: 'always'` column's editor is already open from first paint, so there is * nothing to *start*; this instead moves focus into that column's already-rendered editor, * mirroring what `F2`/`Enter`/double-click do for a `'double-click'` column. Fire-and-forget -- * await `table.updateComplete` first if the newly-opened editor needs to be read back * synchronously afterward (e.g. via {@link cellElement}). */ editCell(rowKey:K,columnKey:string):void; /** The rendered `[part='expanded-cell']` holding one row's `expandedContent(row)` output, or * `null` when that row is not currently rendered, is not expanded, or renders no panel at all. * {@link rowElement} deliberately cannot reach this: the panel is a *sibling* `
`s * now carry the same `data-col-key` attribute (for the sticky-offset * measurement pass) but must never be treated as header cells here. */ private visibleHeaders;private focusHeader;private focusRow;private toggleColumns;private onTableClick;private onTableKeyDown;private onHeaderKeyDown; /** The editable (`editTrigger: 'double-click'`) `` that owns `target`, if any -- i.e. `target` * itself is that cell, since it is the only body-cell shape that ever carries a `tabindex` (see * the render template). Used to tell "a row's own keydown" apart from "a keydown on the row's * own cell-level focus stop" without a second piece of `@state`: the answer is recomputed from * `e.target` on every keydown instead of tracked. */ private editableCellFor; /** This row's own editable cells, in DOM (visual, LTR) order, skipping one a priority-collapse * hid (`offsetParent === null`, matching `visibleHeaders()`) or an inert ancestor would refuse * `focus()` on silently (matching `lr-menu`'s `isNavigable()` shape) -- so ArrowLeft/ArrowRight * below can never strand the cell-level focus stop on a target that cannot actually take it. */ private editableCellsInRow; /** `tr` is always the row that owns the keydown -- resolved by the delegated caller via * `closest('[data-row-key]')` -- regardless of whether `e.target` is the row itself or one of * its own editable cells, so every branch below that reads `tr` (the roving ArrowUp/ArrowDown/ * Home/End switch) keeps working unchanged from a cell-focused keydown, with zero code of its * own aware that cell-level focus exists. Only Enter/F2 (open an editor) and Space (still always * row activation, on a cell exactly as on the row -- neither key is part of the conventional * grid editing idiom) need to know which of the two actually has focus, via `editableCellFor`. */ private onRowKeyDown; /** ArrowLeft/ArrowRight roving between a row's own tab stop and its editable cells -- the * smallest addition that keeps the row's existing ArrowUp/ArrowDown/Home/End roving (above) * fully authoritative: this only ever moves focus sideways, within the current row, and a row * with no editable cell keeps its pre-existing behavior exactly (neither arrow key did anything * at the row level before this method existed, so `cells.length === 0` intentionally leaves the * key unhandled rather than calling `preventDefault()`). * * Direction is visual, not literal, matching `onHeaderKeyDown`'s own `isRtl()` mirroring: under * RTL, ArrowLeft moves toward the visual end (forward) and ArrowRight toward the visual start * (backward). From the row itself (`cell === null`), the forward key enters at the first * editable cell and the backward key enters at the last, so a table with editable columns on * both a left- and right-heavy layout is reachable from either direction. From a cell, the * backward key at the first editable cell returns focus to the row (mirroring the header's own * "ArrowUp from the first body row returns to the header" boundary); the forward key at the last * editable cell simply stays put, matching the header's own clamped ends. */ private onRowLateralKeyDown; /** One body cell's inline editor. * * For `editType: 'text'`/`'number'`, the persistent (`editTrigger: 'always'`) and double-click * flavors differ in exactly one binding: the persistent editor binds `value` as a **content * attribute**, the double-click one keeps the `.value` **property**. Native HTML sets an input's * dirty-value flag on the user's first edit, after which content-attribute updates no longer * overwrite what is displayed -- so an out-of-band `rows` update to a cell the user is already * typing into leaves their draft alone, with no is-focused bookkeeping of the library's own. An * untouched persistent editor has no dirty flag set, so it still picks up a new `rows` value * normally. A double-click editor is short-lived and opens against the value it is editing, so the * property binding's deliberate re-assert is right for it. Two templates rather than one because a * lit template literal fixes each binding's kind at authoring time. * * `editType: 'select'` renders a native `` rather * than throwing). `