Skip to main content
Light Dark System

Datagrid

<et2-datagrid> | Et2Datagrid
Since 23.1

Virtualized data grid for infinite rows with column sizing, selection, and lazy paging.

et2-datagrid is the low-level, virtualized row renderer used by owner widgets such as et2-nextmatch. It should stay generic: owners supply structure and data, while the datagrid owns virtualization, row DOM creation, selection state, keyboard navigation, column layout, loaders, no-results rendering, and refresh application.

Overview
Configuring the datagrid from an owner widget
Minimal owner widget wiring
Row templates: columns and rows
Row data bindings
Styling row contents
Lifecycle (class internals)
Rendering pipeline (summary)
Key methods
Customization points and overrides

If you are wiring et2-datagrid into another Et2 widget, start with Configuring the datagrid from an owner widget, Minimal owner widget wiring, Key methods, and Customization points and overrides. The lifecycle section is for maintainers changing datagrid internals.


Overview

Et2Datagrid is the reusable grid engine under higher-level list widgets. It renders large, incrementally-loaded datasets by combining Lit rendering with @lit-labs/virtualizer, then adds the grid behavior EGroupware lists need: column sizing and visibility, row selection, keyboard focus, loading and empty states, row refreshes, and optional expanded rows.

It is deliberately not an application widget. It does not know what a filter means, how a server endpoint is called, how a named eTemplate is resolved, or how an application response should be interpreted. Those responsibilities stay in an owner widget such as et2-nextmatch and in the owner’s template/data adapters.

Use et2-nextmatch when you need the standard EGroupware list widget with filters, actions, preferences, and legacy Nextmatch compatibility. Use et2-datagrid directly only when another Et2 webcomponent needs the same virtualized grid mechanics but owns its own application-level behavior.


Configuring the datagrid from an owner widget

An owner widget configures the datagrid by passing:

  • columns — normalized column metadata used for headers, column widths, visibility, and row layout.
  • templateData — prepared row, loader, and attribute-map data, usually produced by Et2RowProvider.
  • dataProvider — an Et2DatagridDataProvider implementation for paged loading and row refreshes.
  • Optional presentation hooks such as rowCustomizer, selectionMode, expansionConfig, and preloaded rows.

Keep app-specific concerns in the owner widget or its provider. The datagrid receives normalized columns, rows, and row templates; it does not resolve application templates, filters, or response payloads itself. It asks the configured provider for already-normalized rows.

RowProvider

Et2RowProvider is the template adapter between an owner widget and the datagrid. It reads either a named eTemplate or slotted markup and returns Et2DatagridTemplateData.

Its responsibilities include:

  • finding the effective header and row template markup
  • extracting column metadata from headers
  • normalizing legacy row wrappers such as <row> into datagrid-renderable markup
  • preparing a reusable HTMLTemplateElement for rows
  • converting row widgets to readonly display behaviour where needed
  • recording row-scoped widget attributes in rowTemplateAttrMap
  • preparing optional loader and no-results template data

Et2RowProvider does not fetch row data. It prepares the shape used to render rows once data is available.

DataProviders

Et2DatagridDataProvider is the data boundary for the datagrid. A provider supplies rows in the datagrid’s normalized shape:

{
	id: string;
	data: any;
}

The required provider methods are:

  • fetchPage(start, pageSize) — load a page of rows and optionally return the total row count.
  • refresh(rowIds, type) — resolve updated rows and removed row ids for refresh operations.
  • normalizeRowId(rowId, ensurePrefix) — convert ids to the datastore form used by the rendered grid.
  • toProviderRowId(dataStoreRowId) — convert rendered/datastore ids back to the provider’s native row id.

Et2DatagridRow.id should be the stable id the grid uses for selection, duplicate suppression, refresh matching, and rendered data-row-id attributes. Providers that work with application-native ids should normalize them in fetchPage() / refresh() and use toProviderRowId() when they need to call back into the application or server with the native id.

Optional methods such as getQuerySignature() and getDataStorePrefix() let the datagrid detect query changes and keep row ids stable across paging, refreshes, nested grids, and action handling.

Et2NextmatchDataProvider is the Nextmatch implementation. It adapts egw.dataFetch() and egw.dataRegisterUID() into the generic provider interface, processes extra Nextmatch response data such as select options and filter values, reads refreshed rows back from the central UID cache, and supports child-grid providers for expandable rows.

Presentation hooks (owner-facing properties)

Property Type Purpose
columns Et2DatagridColumn[] Visible column configuration, including sizing and optional hide expressions.
dataProvider Et2DatagridDataProvider | null Paging adapter used by infinite scroll.
templateData Et2DatagridTemplateData | null Prepared template and metadata used to render each row.
rowIdField string ("id") Row-data field that contains the application row id.
rowCustomizer Et2DatagridRowCustomizer | null Per-row hook for row/meta-cell presentation tweaks.
selectionMode "none" | "single" | "multiple" Row selection behavior (default "multiple").
expansionConfig Et2DatagridExpansionConfig | null Generic expanded-row hooks.
view "row" | "tile" Visual layout mode.
pageSize number (50) Maximum rows requested per page load.
rowStylesheets CSSStyleSheet[] Stylesheets adopted into the row shadow DOM.

Child-grid / layout flags are covered in Customization points.


Minimal owner widget wiring

Most application code should use et2-nextmatch directly. Use et2-datagrid from another Et2 webcomponent when the owner needs datagrid mechanics but has its own way to prepare templates, columns, filters, and data.

The owner is responsible for:

  1. creating an Et2RowProvider with the owner as host,
  2. resolving Et2DatagridTemplateData from a named template or from the owner’s light-DOM slots,
  3. rendering et2-datagrid with property bindings for columns, template data, and data provider,
  4. calling reload() after configuration is ready, or setInitialRows() when rows were already loaded.

This is the minimal pattern used by an Et2 owner widget with slotted column/row templates:

import {html, LitElement, PropertyValues} from "lit";
import {customElement} from "lit/decorators/custom-element.js";
import {state} from "lit/decorators/state.js";
import {Et2Widget} from "../Et2Widget/Et2Widget";
import {Et2Datagrid} from "./Et2Datagrid";
import {Et2RowProvider} from "./Et2RowProvider";
import type {
	Et2DatagridColumn,
	Et2DatagridDataProvider,
	Et2DatagridSelectionDetail,
	Et2DatagridTemplateData
} from "./Et2Datagrid.types";

@customElement("my-grid-owner")
export class MyGridOwner extends Et2Widget(LitElement)
{
	private readonly _rowProvider = new Et2RowProvider(this);

	@state()
	private _templateData : Et2DatagridTemplateData | null = null;

	@state()
	private _columns : Et2DatagridColumn[] = [];

	@state()
	private _configurationLoading = true;

	private _dataProvider : Et2DatagridDataProvider = new MyProvider();

	private get _datagrid() : Et2Datagrid | null
	{
		return this.shadowRoot?.querySelector("et2-datagrid") as Et2Datagrid | null;
	}

	private _handleSelectionChanged(event : CustomEvent<Et2DatagridSelectionDetail>)
	{
		this.dispatchEvent(new CustomEvent("my-grid-selection-changed", {
			detail: event.detail,
			bubbles: true,
			composed: true
		}));
	}

	private _handleActiveRowChanged(event : CustomEvent<{activeRowId : string | null; activeRowIndex : number}>)
	{
		this.dispatchEvent(new CustomEvent("my-grid-active-row-changed", {
			detail: event.detail,
			bubbles: true,
			composed: true
		}));
	}

	private _handleColumnsChanged(event : CustomEvent<{columns : Et2DatagridColumn[]}>)
	{
		this._columns = event.detail.columns;
	}

	private _handleLoadingError()
	{
		this.dispatchEvent(new CustomEvent("my-grid-loading-error", {
			bubbles: true,
			composed: true
		}));
	}

	async firstUpdated(changedProperties : PropertyValues)
	{
		super.firstUpdated(changedProperties);

		const templateData = await this._rowProvider.fromSlots();
		this._templateData = templateData;
		this._columns = templateData?.columns || [];
		this._configurationLoading = false;

		await this.updateComplete;
		await this._datagrid?.reload();
	}

	render()
	{
		return html`
            <et2-datagrid
                    ._parent=${this}
                    .columns=${this._columns}
                    .templateData=${this._templateData}
                    .dataProvider=${this._dataProvider}
                    .configurationLoading=${this._configurationLoading}
                    row-id-field="id"
                    selection-mode="multiple"
                    @et2-selection-changed=${this._handleSelectionChanged}
                    @et2-active-row-changed=${this._handleActiveRowChanged}
                    @et2-columns-changed=${this._handleColumnsChanged}
                    @et2-loading-error=${this._handleLoadingError}
            >
                <slot name="noResults" slot="noResults"></slot>
            </et2-datagrid>
        `;
	}
}

The provider class is implemented separately; see dataProvider override for the minimal contract. Owners commonly listen to et2-selection-changed, et2-active-row-changed, et2-columns-changed, and loading events when they need to synchronize outer action state, focus, preferences, or error handling.

The slotted markup belongs to the owner element, not directly to et2-datagrid; Et2RowProvider reads these slots from its host:


<my-grid-owner>
    <tr class="th" slot="columns">
        <et2-nextmatch-header id="title" label="Title"></et2-nextmatch-header>
        <et2-nextmatch-header id="owner" label="Owner"></et2-nextmatch-header>
    </tr>

    <tr class="$class" slot="row">
        <et2-description id="title" noLang="1"></et2-description>
        <et2-description id="owner" noLang="1"></et2-description>
    </tr>
</my-grid-owner>

._parent=${this} is needed for Et2Widget-owned grids that rely on the owner widget’s array manager context during row hydration. Et2Nextmatch uses the same binding for its root grid.

If rows are already available, seed the grid instead of fetching the first page:

await this.updateComplete;
this._datagrid?.setInitialRows(preloadedRows);

For named eTemplates, replace fromSlots() with fromTemplate(templateName):

const templateData = await this._rowProvider.fromTemplate("myapp.index.rows");

Row templates: columns and rows

Columns

Columns are derived from header widgets/elements and include key, title, and optional width/minWidth/disabled metadata.

From Named Templates

When using et2-nextmatch template="app.index.rows":

  • Header definition is parsed from the template header row (.th, thead, or grid header structure).
  • Column widgets (such as et2-nextmatch-header, et2-nextmatch-sortheader) are used to build column definitions.
  • Legacy Nextmatch visibility/size preferences are mapped and applied by et2-nextmatch.

From Slotted Templates

When using slots (template attribute not set):

  • Columns come from slot="columns".
  • Any wrapper can carry the slot (tr, div, et2-box, etc.).
  • The columns wrapper itself is not treated as a column; its child elements are used as column definitions.

Example:


<et2-nextmatch>
    <tr class="th" slot="columns">
        <et2-nextmatch-sortheader id="n_family" label="Name"></et2-nextmatch-sortheader>
        <et2-nextmatch-header id="note" label="Note"></et2-nextmatch-header>
        <et2-vbox>
            <et2-nextmatch-header id="tel_work" label="Business phone"></et2-nextmatch-header>
            <et2-nextmatch-header id="tel_cell" label="Mobile phone"></et2-nextmatch-header>
            <et2-nextmatch-header id="tel_home" label="Home phone"></et2-nextmatch-header>
        </et2-vbox>
    </tr>
    <tr slot="row">...</tr>
</et2-nextmatch>

Rows

Row rendering is driven by a row template.

Row data bindings

Use direct bindings for row data in templates:

  • $field resolves an own field of the current row.
  • $[parent.child] resolves a nested row path.
  • $row is reserved for renderers that need the complete row context, such as VFS and customfields row renderers. For value-capable renderers it receives the row object; other uses retain the row id/key.

The datagrid resolves direct row bindings before transformAttributes(). This avoids an ArrayMgr row perspective for the usual row and lets widgets receive concrete values. Non-row expressions remain in the normal manager context: $cont[...], _cont, @..., and @@... are not row bindings.


<row class="$class $[category.css_class]">
    <et2-description id="title" noLang="1"></et2-description>
    <et2-date-time id="modified" value="$[timestamps.modified]" readonly="true"></et2-date-time>
    <et2-image src="$icon" label="$type_label"></et2-image>
    <et2-date-duration disabled="!$used_time" value="$duration"></et2-date-duration>
    <et2-hbox onclick="egw.open($id, 'infolog');"></et2-hbox>
</row>

For value-capable row widgets, a simple id="title" remains the widget id and is hydrated from rowData.title when that own field exists and no explicit value is present. Use value="$field" for a differently named or nested row value. Compound/action ids such as edit_status[$id] remain ids and are not value bindings.

Legacy row syntax remains supported without changing existing templates:

<!-- Legacy compatibility: equivalent to id="title" -->
<et2-description id="${row}[title]" noLang="1"></et2-description>
<et2-description id="$row_cont[title]" noLang="1"></et2-description>

When direct resolution cannot safely handle an expression, the datagrid retains the ArrayMgr perspective compatibility path. Do not add new row templates that depend on that fallback.

From Named Templates

Named row templates are normalized from legacy eTemplate structures (<row>) into datagrid renderable markup. Common legacy patterns continue to work.

From Slotted Templates

Slotted row template comes from slot="row".

  • Preferred wrapper: <tr slot="row">
  • Legacy wrapper: <row slot="row">
  • Any other wrapper is preserved as-is (for example <sl-card slot="row">)

If the wrapper is <tr> or <row>, bare child widgets are allowed and are auto-wrapped into <td> cells.

Example:

<tr class="$class $cat_id" slot="row">
    <et2-description id="n_family" noLang="1"></et2-description>
    <et2-textarea id="note" readonly="true" noLang="1"></et2-textarea>
    <et2-vbox>
        <et2-url-phone id="tel_work"
                       readonly="true" class="telNumbers" statustext="Business phone"
        ></et2-url-phone>
        <et2-url-phone id="tel_cell"
                       readonly="true" class="telNumbers" statustext="Mobile phone"
        ></et2-url-phone>
        <et2-url-phone id="tel_home"
                       readonly="true" class="telNumbers" statustext="Home phone"
        ></et2-url-phone>
    </et2-vbox>
</tr>

State Templates

Et2Datagrid has two optional state templates:

  • slot="loader" — placeholder content while rows are loading.
  • slot="noResults" — content shown when loading is complete and no rows are available.

These can be supplied in either a named .xet template parsed by Et2RowProvider or as live slotted children of the owner component.

State template precedence is:

  1. Named-template state content extracted into templateData.loaderTemplate or templateData.noResultsTemplate.
  2. Live slotted content forwarded by the owner component, for example <slot name="noResults" slot="noResults"></slot>.
  3. The datagrid’s built-in default state UI.

The named .xet template is the app-level definition and is preferred when present. Live slots are the lower-level component API for owners that do not provide state content through templateData. The built-in default is only the final fallback.

Any wrapper can be used for state template content.

Example:


<tr slot="loader">
    <td colspan="2">
        <sl-skeleton effect="sheen" style="width:100%"></sl-skeleton>
    </td>
</tr>

<div slot="noResults" class="mail-empty-placeholder">
    This mailbox is empty
</div>

Styling row contents

Rows are rendered inside the et2-datagrid shadow DOM. Normal page CSS does not reach row contents unless the target is exposed as a CSS part.

Preferred row-template styling uses et2-styles inside the row template definition. Et2RowProvider extracts those styles and passes them into the datagrid row shadow DOM. The et2-styles element can be placed anywhere inside the row template definition; it does not need to be inside the <row> element.


<template id="app.index.rows">
    <grid>
        <columns>
            <column id="title"></column>
        </columns>
        <row>
            <et2-description class="entry-title" id="title" noLang="1"></et2-description>
        </row>
    </grid>

    <et2-styles src="row.css"></et2-styles>
</template>

src="row.css" resolves relative to the .xet file containing the row template. Inline CSS inside et2-styles is also supported.

If the row template contains et2-styles, et2-nextmatch does not load the application’s templates/default/app.css into the datagrid row shadow DOM. If no row-local et2-styles is present, app.css is still loaded as the compatibility fallback.

Static Widget Style

For a simple static change that applies to every row, add a class to the row template and style it in the row template stylesheet.


<row>
    <et2-description class="entry-title" id="title" noLang="1"></et2-description>
    <et2-description id="owner" noLang="1"></et2-description>
</row>
.entry-title {
	color: var(--sl-color-primary-700);
	font-weight: var(--sl-font-weight-semibold);
}

Static Widget Internals

To style a widget’s exposed internal part from the stylesheet, use ::part() on the widget in the row template.


<row>
    <et2-image class="status" label="Open"></et2-image>
</row>
.status::part(base) {
	min-width: 0;
	padding-inline: var(--sl-spacing-small);
}

Static Exported Parts

Use exportparts when normal application CSS outside the datagrid needs to style a row widget’s internal part. The datagrid gathers row-template exportparts and forwards them through et2-nextmatch.


<row>
    <et2-hbox class="contact-methods" exportparts="base:contact-methods__base">
        <et2-url-phone id="tel_work" readonly="true"></et2-url-phone>
        <et2-url-phone id="tel_cell" readonly="true"></et2-url-phone>
        <et2-url-email id="email" readonly="true"></et2-url-email>
    </et2-hbox>
</row>
et2-nextmatch::part(contact-methods__base) {
	flex-wrap: wrap;
	align-content: flex-start;
	row-gap: var(--sl-spacing-2x-small);
}

Dynamic Row Class

For dynamic row state, put a class expression on the row or widget and style it in the stylesheet. This is the most direct option when the server already supplies a class such as overdue, readonly, or cat_<ID>.


<row class="$class priority_$priority">
    <et2-description class="entry-title" id="title" noLang="1"></et2-description>
    <et2-description class="entry-status" id="status" noLang="1"></et2-description>
</row>
tr.overdue .entry-status {
	color: var(--sl-color-danger-700);
	font-weight: var(--sl-font-weight-semibold);
}

tr.priority_high .entry-title {
	border-inline-start: 3px solid var(--warning-color);
	padding-inline-start: var(--sl-spacing-x-small);
}

Dynamic Widget Class

When only one widget needs dynamic styling, you can put the class expression on that widget instead of the whole row.

<row>
    <et2-description class="entry-status status_$status_class" id="status" noLang="1"></et2-description>
</row>
.entry-status.status_warning {
	color: var(--sl-color-warning-700);
}

.entry-status.status_error {
	color: var(--sl-color-danger-700);
}

Dynamic CSS Property

When an owner widget needs to change the same row styling for all rendered rows, set a CSS custom property on the datagrid or owner widget and use it from the row stylesheet. This keeps row DOM updates out of application code and lets the browser apply the change to currently rendered and newly virtualized rows.

this.datagrid.style.setProperty("--app-row-details-display", showDetails ? "block" : "none");
<row>
    <et2-description class="entry-title" id="title" noLang="1"></et2-description>
    <et2-description class="entry-details" id="details" noLang="1"></et2-description>
</row>
.entry-details {
	display: var(--app-row-details-display, none);
}

Built-in row sizing properties

Et2Datagrid exposes row sizing CSS custom properties for owner widgets and apps that need to tune standard row layout:

Property Default Effect
--row-height 3em Estimated row height used for virtual spacer rendering and empty rows.
--row-cell-max-height 10em Maximum height for normal row cells before vertical scrolling.

Use --row-cell-max-height when row content is being clipped or scrolls too early. It applies to normal row td / th cells; expanded rows and tile view use separate sizing paths.

et2-nextmatch {
	--row-cell-max-height: 16em;
}

Lifecycle (class internals)

The datagrid coordinates Lit’s reactive lifecycle, the @lit-labs/virtualizer, a debounced request queue, and a deferred row-upgrade pass. This section follows a row from initial setup to a hydrated, interactive DOM node.

Et2Datagrid lifecycle Reactive update cycle Async data pipeline constructor() requestUpdate() property change schedules render willUpdate() structure detection, column prefs, sourceColumnKeys render() virtualizer config, virtual items, state _renderVirtualRow() -> _buildRowElement() clone template, placeholders, _markRowElement _ensureMetaCell updated() post-render structure sync, stylesheets firstUpdated() first time only Deferred hydration (async) MutationObserver -> _processRowUpgradeQueue _applyRowElementAttributes loadMore() / reload() or placeholder -> _requestChunkForRowIndex() _queueRequest() (debounced 100ms) _fetchPage() dataProvider.fetchPage() await rows + total _reconcileRowRenderState() -> requestUpdate() reactive lifecycle async data fetch deferred hydration cross-cutting / entry

Construction

constructor() (Et2Datagrid.ts:490) is intentionally light: it measures the browser scrollbar width once and binds stable method references so listeners and the virtualizer can add/remove them cleanly. No DOM or data work happens here. The default properties (columns, dataProvider, pageSize, selectionMode, and the private _columnManager / _columnState instances) are set by the property decorators above.

First paint

firstUpdated() (Et2Datagrid.ts:542) runs once after the first render. It:

  • attaches the passive scroll listener (_maybePrefetchOnScroll) to the scroll body,
  • installs the MutationObserver row-upgrade watcher via _initRowUpgradeObserver(),
  • sets up column-resize interact handlers.

Structural change detection

willUpdate() (Et2Datagrid.ts:556) runs before each render whenever a reactive property changed. For structure-defining inputs — templateData, view, rowIdField, columns, columnPreferenceName, noColumnPersistence, noVisibleHeader — it:

  • invalidates the loaded column-preference key so _loadColumnPreferencesIfNeeded() re-reads persisted column state,
  • captures _sourceColumnKeys from templateData.sourceColumns (used to remap cells after column reordering),
  • rebuilds the visible header node set via _prepareVisibleHeaders(),
  • marks _postRenderStructureSyncNeeded so updated() can reapply column sizes/visibility,
  • clears the row-upgrade queue and resets virtualizer caches when the structure changed.

When columns change it also rebuilds the cached customfield column state (_rebuildCustomfieldColumnStateCache()), which row hydration reads to avoid per-row header scans.

updated() (Et2Datagrid.ts:618) performs the post-render structure sync (column sizes, visibility), re-adopts rowStylesheets, re-runs the row-upgrade observer/scan, and restores focus to the active row after a render if needed.

Data-load trigger

There are two ways rows start loading:

  1. Owner-drivenloadMore() (Et2Datagrid.ts:4197) and reload() (Et2Datagrid.ts:3982) request from index 0. reload() wipes all loaded rows (_clearRows()), resets total, and re-fetches. These are the public entry points an owner widget calls.
  2. Virtualizer-driven — when the virtualizer renders an index that has no loaded row, _renderVirtualRow() (Et2Datagrid.ts:1835) emits a placeholder and calls _requestChunkForRowIndex() (Et2Datagrid.ts:1934). This is how infinite-scroll paging and scroll-into-view prefetch happen. scroll events also re-arm queued-request processing via _maybePrefetchOnScroll().

Both paths funnel into the same debounced queue.

Fetch

The request queue coalesces bursts (fast scrolling) and dedupes by a deterministic key built from start, count, and provider query signature (_requestKey()):

_queueRequest() (Et2Datagrid.ts:1205) records the request and bumps _pendingPlaceholderCount (embedded grids reserve a single loading row).

_scheduleQueuedRequestProcessing() (Et2Datagrid.ts:1219) debounces (default 100 ms).

_processQueuedRequests() (Et2Datagrid.ts:1349) marks the request in-flight, sets loading / fetching, dispatches et2-loading-start, and calls _fetchPage().

_fetchPage() (Et2Datagrid.ts:1243) awaits dataProvider.fetchPage(start, count) and:

  • sets fetchFailed / fetchErrorMessage and _hasFetchedOnce,
  • writes this.total from the response when present,
  • merges rows into _rowsByIndex[index], deduplicating via displayedRowIds, then rebuilds the flat this.rows array,
  • in finally, decrements placeholders, removes the in-flight key, fires et2-loading-done or et2-loading-error, and calls _reconcileRowRenderState().

_reconcileRowRenderState() (Et2Datagrid.ts:1394) prunes expanded rows that became non-expandable after refresh and, when autoActivateFirstRow is set and no active row exists, pins activeRowIndex = 0 so keyboard navigation works as soon as the first row appears.

loading, fetching, and total are @state() so the relevant templates re-render. The _stateTemplate() (Et2Datagrid.ts:4250) resolves the high-level visual state — initial loading, fetch error, missing template, or empty — and renders the loader, error, or no-results UI accordingly.

Row generation

render() (Et2Datagrid.ts:4444) is the heart of presentation. For each render it:

  • computes visibleColumns and resolves CSS custom properties (--column-sizes, --column-count, --scrollbar-space, --embedded-virtualized-height),
  • computes rowCount = _virtualRowCount() (Et2Datagrid.ts:2022), which is total when known or the materialized count otherwise,
  • builds the virtualizer items via _getVirtualItems() (Et2Datagrid.ts:1982) — inserting an expanded item immediately after each expanded parent,
  • passes a stable keyFunction ( _virtualRowKey, Et2Datagrid.ts:2062) and the renderItem callback (_renderVirtualRow) to virtualize().

_renderVirtualRow() (Et2Datagrid.ts:1835) is invoked by the virtualizer for every visible slot:

  • if the row exists in _rowsByIndex, it calls _buildRowElement() and serializes the result with unsafeHTML (rows are stamped as fast, inert DOM strings for throughput),
  • otherwise it renders a placeholder (skeleton or the slot loader) and triggers a chunk request.

_buildRowElement() (Et2Datagrid.ts:1544) is where a logical row becomes a DOM node:

  1. It clones the prepared <template> (document.importNode(template.content, true)) — or falls back to a simple <tr> built from column values when no template exists.
  2. _populateCloneWithRow() (Et2Datagrid.ts:2401) walks text nodes and resolves direct and legacy row placeholders via Et2RowProvider.resolveSimpleRowPlaceholders().
  3. _populateRowRootAttributes() (Et2Datagrid.ts:2423) resolves root-level placeholder attributes (e.g. dynamic row classes) via Et2RowProvider.customizeRowRootAttributes().
  4. _markRowElement() (Et2Datagrid.ts:2116) stamps accessibility/identity attributes: role, data-row-id, data-row-index, aria-rowindex, aria-selected, and tabindex.
  5. _ensureMetaCell() (Et2Datagrid.ts:1602) ensures the leading metadata cell exists and wires the row expander when the row is expandable; it also invokes rowCustomizer (see Customization points).
  6. The node is tagged with the loading class and _applyColumnLayoutToRowElement() applies column track sizing.

Hydration (deferred row binding)

Row templates are stamped as inert strings, so widget binding is deferred to keep scrolling/rendering responsive. Direct row bindings are resolved from the current rowData during that deferred pass; an ArrayMgr perspective is only a compatibility fallback.

_initRowUpgradeObserver() (Et2Datagrid.ts:1755) installs a MutationObserver on the scroll body that calls _upgradeRenderedRows() whenever rows are added/moved.

_upgradeRenderedRows() (Et2Datagrid.ts:2211) finds newly realized physical rows, skips already-upgraded nodes for the same row identity, and enqueues them.

_processRowUpgradeQueue() (Et2Datagrid.ts:2326) processes a bounded batch per animation frame (≤ 8 rows, 8 ms budget) so input/paint stay smooth. For each row it calls _applyRowElementAttributes().

_applyRowElementAttributes() (Et2Datagrid.ts:2436) is the hydration core. It:

  • reads rowTemplateAttrMap for each element carrying data-et2nm-id,
  • resolves direct and normalized legacy row expressions from rowData before widget transformation,
  • supplies direct id bindings as widget values when the widget supports values,
  • uses the normal content manager for non-row expressions and opens a row-scoped ArrayMgr perspective only when direct resolution cannot safely handle an expression,
  • for et2-customfields-list elements, applies cached customfield state via _applyCustomfieldRowState(),
  • for the row root, applies stored root attributes,
  • for other widgets, calls transformAttributes(stored) (or falls back to setAttribute with mgr.expandName()),
  • re-runs the row customizer and finally removes the loading class.

Because the virtualizer can materialize rows after updated(), _scheduleRenderedRowsUpgradeScan() (Et2Datagrid.ts:2249) re-scans for up to ~30 frames to catch late handoff.

Refresh and reload

refresh(row_ids, type) (Et2Datagrid.ts:4001) applies a targeted refresh without a full reload. For DELETE it removes rows client-side ( _removeRowsById, Et2Datagrid.ts:4121); otherwise it awaits dataProvider.refresh() and merges results via _applyRefreshedRows() (Et2Datagrid.ts:4048), which swaps row data in place, bumps a render version (triggering a re-render of that row via the virtual key), and pulses the changed rows for visual feedback.

reload() (Et2Datagrid.ts:3982) wipes loaded state and re-fetches from index 0.


Rendering pipeline (summary)

Row rendering is split into preparation and per-row hydration:

  1. Et2RowProvider finds the row template, normalizes it, extracts columns, and compiles it into a reusable template. Row-independent values are resolved at this stage, including static $cont / @... expressions and literal strings.
  2. Et2RowProvider records row-scoped widget attributes in the template attribute map instead of permanently resolving them on the reusable template.
  3. Et2Datagrid clones the prepared row template for each row needed by the virtualizer.
  4. Et2Datagrid applies recorded row-scoped widget attributes to the clone using that row’s rowData, which came from the configured Et2DatagridDataProvider.

This order keeps static template work shared across all rows while preserving correct row-specific values for each physical row clone. The virtualizer determines which row indexes need DOM nodes and manages their addition and removal; the data provider supplies row objects, and the prepared template controls how each row object becomes DOM.


Key methods

Visibility legend: public (called by owner widgets), private (internal only).

Method Visibility Stage Purpose
loadMore() public load Request the first (or next missing) page from index 0.
reload() public load Clear all rows and re-fetch; reset total and error state.
setInitialRows(rows) public load Seed preloaded row data (skips the initial provider fetch) and fire et2-loading-done.
refresh(row_ids, type) public refresh Targeted in-place refresh or removal without a full reload.
selectSingleRow(rowId) public selection Select one loaded row and emit et2-selection-changed.
selectAllRows() public selection Select all currently loaded rows when selectionMode is multiple.
clearSelection() public selection Clear selected rows and optionally emit the selection event.
focusFirstRow() public focus Move active focus to the first loaded row.
focusRowById(rowId) public focus Move active focus to a loaded row by id.
clearActiveRow() public focus Clear active-row focus state.
openColumnSelection(event?) public columns Open the column chooser from an owner-level control.
render() Lit render Builds the virtualizer config, headers, state template, and CSS vars.
_getVirtualItems() private render Builds virtualizer item list, inserting expanded items after parents.
_virtualRowCount() private render Resolves slot count (total vs materialized count).
_virtualRowKey() private render Stable key per row/expanded/placeholder; includes structure + render version.
_renderVirtualRow() private generate Per-slot callback: build row DOM or emit placeholder + chunk request.
_requestChunkForRowIndex() private generate Queue a page fetch for the chunk owning an unloaded index.
_buildRowElement() private generate Clone template, resolve placeholders, mark, ensure meta cell, layout.
_populateCloneWithRow() private generate Resolve direct and legacy row text placeholders in the cloned fragment.
_populateRowRootAttributes() private generate Resolve placeholder attributes on the row root.
_markRowElement() private generate Stamp a11y/identity attributes (role, data-row-*, aria-*, tabindex).
_ensureMetaCell() private generate Ensure leading metadata cell + expander; invoke rowCustomizer.
_applyColumnLayoutToRowElement() private generate Apply column track sizing to the row.
_initRowUpgradeObserver() private hydrate MutationObserver that enqueues realized rows for hydration.
_upgradeRenderedRows() private hydrate Find newly realized rows, skip upgraded, enqueue.
_processRowUpgradeQueue() private hydrate Bounded per-frame hydration: bind widgets, customfields, customizer.
_applyRowElementAttributes() private hydrate Apply row attrs directly; use an ArrayMgr perspective only as fallback.
_scheduleRenderedRowsUpgradeScan() private hydrate Re-scan frames to catch virtualizer-late rows.
_queueRequest() private fetch Record a debounced request + placeholder count.
_processQueuedRequests() private fetch Dispatch queued requests, fire et2-loading-start.
_fetchPage() private fetch Await dataProvider.fetchPage(), merge rows, fire done/error.
_reconcileRowRenderState() private fetch Prune stale expansions, pin first active row, request render.
_stateTemplate() private state Resolve loading/error/missing-template/empty UI.
_applyRefreshedRows() private refresh Merge provider refresh result; pulse changed rows.
_removeRowsById() private refresh Remove rows client-side; fix counts/selection.

Customization points and overrides

rowCustomizer

rowCustomizer is a Et2DatagridRowCustomizer callback invoked for every realized row, both during row generation (_ensureMetaCell) and again after hydration (_rerunRowCustomizer, Et2Datagrid.ts:2530). Use it for presentation tweaks that depend on row data — badges, meta-cell indicators, or DOM adjustments the row template cannot express.

import type {Et2DatagridRowCustomizer} from "./Et2Datagrid.types";

const rowCustomizer : Et2DatagridRowCustomizer = ({rowElement, rowData, rowIndex, metaCell}) =>
{
	// Add a per-row indicator into the leading metadata cell.
	if(rowData.overdue)
	{
		metaCell.classList.add("row-overdue");
	}
	// Toggle a class on the whole row based on data.
	rowElement.classList.toggle("row-priority-high", rowData.priority === "high");
};

this.datagrid.rowCustomizer = rowCustomizer;

The context is { rowElement, rowData, rowIndex, metaCell }. metaCell is the leading column-0 cell (td[data-dg-meta-cell="1"] in row view, [data-dg-meta-cell="1"] in tile view).

expansionConfig (expandable rows)

Et2DatagridExpansionConfig owns the content of expandable rows; the datagrid owns the expander mechanics and column alignment. The contract is intentionally free of Nextmatch-specific hierarchy — Nextmatch maps its parent_id / is_parent fields into these hooks.

Hook Purpose
isExpandable(row, rowIndex) Return true for rows that should render an expander.
renderExpandedContent(context) Render detail content immediately after the parent row. Can return another et2-datagrid.
expandedRowIds? Optional controlled set of expanded row ids.
onExpandedRowIdsChanged? Called with the next expanded-id set when expansion toggles.
emptyTemplate? Optional empty-state renderer for custom detail UI.
import {Et2Datagrid} from "./Et2Datagrid";
import type {Et2DatagridExpansionConfig} from "./Et2Datagrid.types";

const expansionConfig : Et2DatagridExpansionConfig = {
	// Expand only parent rows (Nextmatch hierarchy contract).
	isExpandable: (row) => !!row.data.is_parent,

	renderExpandedContent: ({row, parentGrid}) =>
	{
		// Return another datagrid as the child detail view.
		const parent = parentGrid as Et2Datagrid;
		const child = document.createElement("et2-datagrid") as Et2Datagrid;
		child.dataProvider = new ChildProvider(row.data.id);
		child.columns = parent.columns;
		child.templateData = parent.templateData;
		child.embeddedVirtualized = true;     // no own scrollport; parent scrolls
		child.noColumnPersistence = true;     // columns owned by parent
		return child;
	},

	// Controlled expansion state (optional).
	expandedRowIds: new Set<string>(),
	onExpandedRowIdsChanged: (next) =>
	{
		// Persist or reflect expansion state in the owner widget.
	},
};

this.datagrid.expansionConfig = expansionConfig;

The expanded content receives Et2DatagridExpandedRowContext (row, rowIndex, parentGrid, columnSizes, metaColumnWidth) so it can align itself to the parent column tracks.

dataProvider override

Subclass or implement Et2DatagridDataProvider to supply rows from any source. A minimal custom provider:

import type {
	Et2DatagridDataProvider,
	Et2DatagridPageResult,
	Et2DatagridRefreshResult,
	Et2DatagridUpdateType
} from "./Et2Datagrid.types";

class MyProvider implements Et2DatagridDataProvider
{
	async fetchPage(start : number, pageSize : number) : Promise<Et2DatagridPageResult>
	{
		const page = await myApi.getRows(start, pageSize);
		return {
			rows: page.rows.map(r => ({id: this.normalizeRowId(r.id, true), data: r})),
			total: page.total
		};
	}

	async refresh(row_ids : string[], _type : Et2DatagridUpdateType) : Promise<Et2DatagridRefreshResult>
	{
		const providerIds = row_ids.map(id => this.toProviderRowId(this.normalizeRowId(id, true)));
		const rows = await myApi.getRowsById(providerIds);
		return {
			rows: rows.map(r => ({id: this.normalizeRowId(r.id, true), data: r})),
			removedRowIds: []
		};
	}

	// Datastore ids may carry a type prefix (e.g. "calendar::123"). Keep them stable.
	normalizeRowId(rowId : string | number, ensurePrefix = false) : string
	{
		const id = String(rowId);
		return ensurePrefix && !id.includes("::") ? `myapp::${id}` : id;
	}

	toProviderRowId(dataStoreRowId : string) : string
	{
		return dataStoreRowId.replace(/^myapp::/, "");
	}

	// Optional: lets the grid skip refetching when the query is unchanged.
	getQuerySignature() : string
	{
		return myApi.currentQuerySignature();
	}
}

this.datagrid.dataProvider = new MyProvider();

getQuerySignature() should change whenever the underlying query (filters, sort) changes, so the request-dedup key (_requestKey) and virtual keys invalidate stale rows. getDataStorePrefix() is used by nested/child grids to namespace row ids.

Row-scoped deferred attributes (rowTemplateAttrMap)

Row templates may carry attributes that depend on the row, such as class="$class priority_$priority". Et2RowProvider does not permanently resolve these on the shared template; instead it records them in templateData.rowTemplateAttrMap keyed by the generated widget id (data-et2nm-id). During hydration, _applyRowElementAttributes() resolves direct row bindings against the current rowData so each physical clone gets its own concrete values. Unsupported expressions retain the ArrayMgr perspective fallback.

As an app author you normally do not populate rowTemplateAttrMap directly — you write the expression in the template and let Et2RowProvider record it. The mechanism matters when you build custom row templates or providers and need row-specific widget attributes that cannot be expressed as static template text.

Column hide expressions and headers

Column metadata (Et2DatagridColumn) may carry a disabled flag (fixed hidden/unavailable in the chooser) and a hidden flag (currently hidden but user-toggleable). Headers are live elements (column.header) and may expose methods such as getCustomfieldVisibility() — used by the datagrid to cache customfield state per column (_rebuildCustomfieldColumnStateCache). Column hide/show expressions are evaluated by _parseColumnBooleanExpression() (Et2Datagrid.ts:2709) against the content array manager. Custom column widgets can therefore drive visibility and sizing through their own header element.

Child-grid and layout flags

Property Effect
inheritColumnSizes Let --column-sizes inherit from the host (child grids whose tracks are owned by a parent).
noColumnPersistence Disable loading/saving column preferences (child grids).
noColumnResize Disable interactive column resizing owned by another component.
noVisibleHeader Hide only the visible header row; <thead> still renders for a11y/sizing.
autoHeight Grow to fit rows instead of creating an own scroll body (expanded children).
embeddedVirtualized Embedded virtualized grid inside an ancestor scrollport — keeps lazy paging but no own scrollport.
autoActivateFirstRow Mark the first loaded row active (subgrids disable this).
view = "tile" Non-row wrapping virtualized layout; every entry remains its own item.

Slots

Name Description
header Header content used when no column definitions are available.
noResults Optional empty-state content shown when there are no rows.
expand-icon Optional icon shown for collapsed expandable rows.
collapse-icon Optional icon shown for expanded rows.

Learn more about using slots.

Properties

Name Description Reflects Type Default
accesskey Accesskey provides a hint for generating a keyboard shortcut for the current element. The attribute value must consist of a single printable character. string -
actions Set Actions on the widget Each action is defined as an object: move: { type: “drop”, acceptedTypes: “mail”, icon: “move”, caption: “Move to” onExecute: javascript:mail_move” } This will turn the widget into a drop target for “mail” drag types. When “mail” drag types are dropped, the global function mail_move(egwAction action, egwActionObject sender) will be called. The ID of the dragged “mail” will be in sender.id, some information about the sender will be in sender.context. The etemplate2 widget involved can typically be found in action.parent.data.widget, so your handler can operate in the widget context easily. The location varies depending on your action though. It might be action.parent.parent.data.widget To customise how the actions are handled for a particular widget, override _link_actions(). It handles the more widget-specific parts. object -
align Used by Et2Box to determine alignment. Allowed values are left, right string -
autoActivateFirstRow Automatically mark the first loaded row active. Subgrids disable this so simply expanding a row does not create multiple active rows. boolean true
autoHeight
auto-height
Let the grid grow to fit its rows instead of creating its own scroll body. Used for expanded child grids so the parent grid remains the only vertical scroller. boolean false
class CSS Class. This class is applied to the outside, on the web component itself. Due to how WebComponents work, this might not change anything inside the component. string -
columnPreferenceName
column-preference-name
Optional explicit preference key for persisted column state. When omitted, datagrid derives key from owner component + row template id. string ""
columns
Visible column configuration, including sizing and optional hide expressions. Et2DatagridColumn[] []
configurationLoading
configuration-loading
External loading flag for configuration/template setup before first data render. boolean false
data Set the dataset from a CSV string -
dataProvider
Paging adapter used by infinite scroll to fetch additional rows from the server. Et2DatagridDataProvider | null null
deferredProperties
Any attribute that refers to row content cannot be resolved immediately, but some like booleans cannot stay a string because it’s a boolean attribute. We store them for later, and parse when they’re fully in their row. If you are creating a widget that can go in a nextmatch row, and it has boolean attributes that can change for each row, add those attributes into deferredProperties - -
disabled Defines whether this widget is visibly disabled. The widget is still visible, but clearly cannot be interacted with. Widgets disabled in the template will not return a value to the application code, even if re-enabled via javascript before submitting. To allow a disabled widget to be re-enabled and return a value, disable via javascript in the app’s et2_ready() instead of an attribute in the template file. boolean false
dom_id
Get the actual DOM ID, which has been prefixed to make sure it’s unique. string -
embeddedVirtualized
embedded-virtualized
Render as an embedded virtualized grid inside an ancestor scrollport. Unlike simple auto-height, this mode keeps lazy paging but does not create an independent scrollport. It starts with one loading row, then keeps the host, root CSS variable, and virtualizer body height synchronized to the larger of the virtualizer estimate and the actual rendered row stack. boolean false
emptyStateActionMenu
empty-state-action-menu
Show an empty-state action menu button. The button dispatches a composed contextmenu event from the empty row so owners can use their normal row action-menu routing. boolean false
emptyStateText
empty-state-text
Optional replacement for the default empty-state headline text. Keeps default empty-state template structure while allowing Nextmatch-level customization. string ""
expansionConfig
Optional generic expanded-row hooks supplied by consumers such as Et2Nextmatch. Et2DatagridExpansionConfig | null null
fetchErrorMessage
Optional provider error message shown in error state. string ""
fetchFailed
Error state set when the latest fetch failed. boolean false
fetching
Guard flag used to prevent overlapping fetchPage() calls. boolean false
hidden The widget is not visible. As far as the user is concerned, the widget does not exist. Widgets hidden with an attribute in the template may not be created in the DOM, and will not return a value. Widgets can be hidden after creation, and they may return a value if hidden this way. boolean -
id
Get the ID of the widget string -
inheritColumnSizes
inherit-column-sizes
Let --column-sizes inherit from the host instead of computing it from local columns. Used by child grids whose visual tracks are owned by a parent grid, while local columns still define cell order/visibility. boolean false
label The label of the widget This is usually displayed in some way. It’s also important for accessability. This is defined in the parent somewhere, and re-defining it causes labels to disappear string -
loading
True while a fetch cycle is active, including initial and incremental page loads. boolean false
noColumnPersistence Disable loading and saving column preferences. Useful for child grids whose columns are owned by a parent grid. boolean false
noColumnResize Disable interactive column resizing for grids whose column sizing is owned by another component. boolean false
noColumnSelection Hide the column chooser action in the header when true. boolean false
noLang Disable any translations for the widget boolean -
noVisibleHeader
no-visible-header
Hide only the visible header row. The table <thead> remains rendered for accessibility and sizing semantics. boolean false
pageSize Maximum number of rows requested per page load. number 50
parentId Parent is different than what is specified in the template / hierarchy. Widget ID of another node to insert this node into instead of the normal location string -
parentRowId
Parent row id when this grid is rendered as expanded child content. string ""
rowCustomizer
Optional hook invoked for each realized row to customize row/meta-cell presentation. Et2DatagridRowCustomizer | null null
rows
Rows currently materialized in the DOM/in-memory list. Et2DatagridRow[] []
selectionMode
selection-mode
Row selection behavior: none, single, or multiple. Et2DatagridSelectionMode "multiple"
statustext Tooltip which is shown for this element on hover string -
styles
WebComponent * - -
templateData
Prepared template and metadata used to render each row. Et2DatagridTemplateData | null null
total
Total row count reported by provider, or null when unknown. number | null null
view Visual layout mode. Row is the default table layout. Et2DatagridView "row"
options
Get property-values as object
use widget methods
object -
supportedWidgetClasses
et2_widget compatability
Legacy compatability. Some legacy widgets check their parent to see whats allowed
array []
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
et2-columns-changed Fired when column order, width, or visibility changes. CustomEvent
et2-loading-error Fired when a row fetch request fails. CustomEvent
et2-loading-done Fired when row data is ready: after all in-flight fetches complete, or after setInitialRows() seeds preloaded rows. CustomEvent
et2-loading-start Fired when one or more row fetch requests are dispatched. CustomEvent
et2-datagrid-leave-child-grid EVENT NEEDS A DESCRIPTION CustomEvent
et2-datagrid-enter-expanded-row EVENT NEEDS A DESCRIPTION CustomEvent
et2-column-selection-items EVENT NEEDS A DESCRIPTION CustomEvent
et2-column-selection-apply EVENT NEEDS A DESCRIPTION CustomEvent
et2-active-row-changed Fired when keyboard or pointer navigation changes the active row. CustomEvent
et2-selection-changed Fired when row selection changes. CustomEvent

Learn more about events.

Methods

Name Description Arguments
checkCreateNamespace() Checks whether a namespace exists for this element in the content array. If yes, an own perspective of the content array is created. If not, the parent content manager is used. Constructor attributes are passed in case a child needs to make decisions -
clear() Reset all grid runtime state including selection and fetch markers. -
clone() Creates a copy of this widget. _parent: et2_widget
createElementFromNode() Create a et2_widget from an XML node. First the type and attributes are read from the node. Then the readonly & modifications arrays are checked for changes specific to the loaded data. Then the appropriate constructor is called. After the constructor returns, the widget has a chance to further initialize itself from the XML node when the widget’s loadFromXML() method is called with the node. _node: , _name:
getArrayMgr() Returns the array manager object for the given part managed_array_type: string
getArrayMgrs() Returns an associative array containing the top-most array managers. _mgrs: object
getChildren() Get child widgets Use .children to get web component children -
getInstanceManager() Returns the instance manager -
getPath() Returns the path into the data array. By default, array manager takes care of this, but some extensions need to override this -
getRoot() Returns the base widget Usually this is the same as getInstanceManager().widgetContainer -
loadFromXML() Loads the widget tree from an XML node _node:
loadingFinished() Needed for legacy compatability. promises: Promise[]
loadMore() Trigger next page load when allowed by current state. -
openColumnSelection() Open the column selection dialog. This is public so containers can expose the same column chooser outside the datagrid header. event: Event
parseXMLAttrs() The parseXMLAttrs function takes an XML DOM attributes object and adds the given attributes to the _target associative array. This function also parses the legacyOptions. N.B. This is only used for legacy widgets. WebComponents use transformAttributes() and do their own handling of attributes. _attrsObj: , _target: object, _proto: et2_widget
refresh() Apply a targeted row refresh without forcing a full grid reload. The provider decides which rows changed or disappeared; the datagrid only updates rows it already has materialized locally. row_ids: string[], type: Et2DatagridUpdateType
reload() Clear current rows and load from first page. -
selectSingleRow() Select exactly one row by id and synchronize visual/accessibility state. rowId: string
set_label() NOT the setter, since we cannot add to the DOM before connectedCallback() TODO: This is not best practice. Should just set property, DOM modification should be done in render https://lit-element.polymer-project.org/guide/templates#design-a-performant-template value: string
setArrayMgr() Sets the array manager for the given part _part: string, _mgr: object
setArrayMgrs() Sets all array manager objects - this function can be used to set the root array managers of the container object. _mgrs: object
setInitialRows() Seed datagrid with preloaded rows and skip initial fetch. rows: any[]
setInstanceManager() Set the instance manager Normally this is not needed as it’s set on the top-level container, and we just return that reference manager: etemplate2
_get_action_links() Get all action-links / id’s of 1.-level actions from a given action object This can be overwritten to not allow all actions, by not returning them here. actions:
_handleClick() Click handler calling custom handler set via onclick attribute to this.onclick _ev: MouseEvent
_handleColumnSelectionClick() Handle column selection action from the header button. event: MouseEvent
_headerTemplate() Render the visible column header row (or fallback header slot). visibleColumns: Et2DatagridColumn[]
_link_actions() Link the actions to the DOM nodes / widget bits. actions: object
_set_label() Do some fancy stuff on the label, splitting it up if there’s a %s in it Normally called from updated(), the “normal” setter stuff has already been run before this is called. We only override our special cases (%s) because the normal label has been set by the parent value: string
destroy() et2_widget compatability
true
-
set_class() Set the widget class
Use this.class or this.classList instead
new_class: string
set_disabled() Wrapper on this.disabled because legacy had it.
Use widget.disabled for visually disabled, widget.hidden for visually hidden. Disabled vs Readonly vs Hidden
value: boolean
set_statustext() supports legacy set_statustext
use this.statustext
value: string

Learn more about methods.

Custom Properties

Name Description Default
--row-height Estimated row height used for spacer rendering. 3em
--row-cell-max-height Maximum height for individual row cells before vertical scrolling. 10em
--meta-column-width Width of leading metadata column; expandable grids default it wide enough for the expander. 0px
--row-expander-size Width and height of the row expand/collapse button. 20px
--row-expander-icon-size Size of the default CSS triangle expander icon. 6px
--column-sizes Grid-template column track definition used by header/body rows.
--column-count Column count fallback when explicit track sizes are not set. 1
--scrollbar-space Reserved right-side space in header for body scrollbar alignment. 0px
--column-selection-width Width of the header column selection action. 16px
--embedded-virtualized-height Synced reserved height for an embedded virtualized grid with no own scrollbar.

Learn more about customizing CSS custom properties.

Parts

Name Description
base Root wrapper around the grid header and body.
header Visible column header row container.
body Scrollable container for state content and table.
state State message container (loading, empty, template missing, or fetch error).
state-action-menu Empty-state action menu button.
resize-helper Helper bar shown while resizing a column.
table Internal table element with ARIA grid semantics.
rows Table body that hosts virtualized row content.
meta-column Leading header column used for row metadata indicators.
row-meta Leading per-row metadata cell (column 0), customizable by consumers.
row-expander Expand/collapse button rendered in the row metadata cell.
row-expander-icon Icon wrapper inside the row expander button.
expanded-row Cell containing consumer-provided expanded row content.
column A visible header column wrapper.
column-selection Column selection action container in the header.

Learn more about customizing CSS parts.