Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@lit/reactive-element": "2.1.2",
"@mdi/js": "7.4.47",
"@replit/codemirror-indentation-markers": "^6.5.3",
"@tanstack/lit-table": "^8.21.3",
"@tanstack/lit-table": "^9.1.2",
"codemirror": "^6.0.2",
"esptool-js": "^0.6.1",
"improv-wifi-serial-sdk": "^2.8.1",
Expand Down
42 changes: 31 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 21 additions & 22 deletions src/components/dashboard/device-table-grid.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { flexRender } from "@tanstack/lit-table";
import type { Cell, Header, HeaderGroup, Row, Table } from "@tanstack/lit-table";
import { html, nothing, type TemplateResult } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { repeat } from "lit/directives/repeat.js";
import type { ConfiguredDevice } from "../../api/types/devices.js";
import type { LocalizeFunc } from "../../common/localize.js";
import { tourAnchor } from "../guided-tour/tour-anchor.js";
import { getActiveTourConfiguration } from "../guided-tour/tour-session.js";
import type { DeviceRow } from "./table-columns.js";
import type { DeviceTable, DeviceTableRow } from "./table-features.js";

export interface DeviceTableHeadProps {
table: Table<DeviceRow>;
table: DeviceTable;
selectMode: boolean;
allSelected: boolean;
onToggleAll: () => void;
}

export interface DeviceTableBodyProps {
table: Table<DeviceRow>;
rows: Row<DeviceRow>[];
table: DeviceTable;
rows: DeviceTableRow[];
selectMode: boolean;
selectedDevices: Set<string>;
highlightConfiguration: string | null;
Expand All @@ -40,7 +39,7 @@ export function renderDeviceTableHead(p: DeviceTableHeadProps): TemplateResult {
return html`
<thead>
${p.table.getHeaderGroups().map(
(hg: HeaderGroup<DeviceRow>) => html`
(hg) => html`
<tr role="row">
${
p.selectMode
Expand All @@ -54,19 +53,25 @@ export function renderDeviceTableHead(p: DeviceTableHeadProps): TemplateResult {
</th>`
: nothing
}
${hg.headers.map((header: Header<DeviceRow, unknown>) => {
${hg.headers.map((header) => {
const sorted = header.column.getIsSorted();
const canSort = header.column.getCanSort();
const ariaSort =
sorted === "asc"
? "ascending"
: sorted === "desc"
? "descending"
: "none";
const sortIcon =
sorted === "asc"
? "chevron-up"
: sorted === "desc"
? "chevron-down"
: "unfold-more-horizontal";
return html`
<th
role="columnheader"
aria-sort=${
sorted === "asc"
? "ascending"
: sorted === "desc"
? "descending"
: "none"
}
aria-sort=${ariaSort}
class="${canSort ? "sortable" : ""} ${
sorted ? "sorted" : ""
} col-${header.column.id}"
Expand All @@ -84,13 +89,7 @@ export function renderDeviceTableHead(p: DeviceTableHeadProps): TemplateResult {
? html`<wa-icon
class="sort-icon"
library="mdi"
name=${
sorted === "asc"
? "chevron-up"
: sorted === "desc"
? "chevron-down"
: "unfold-more-horizontal"
}
name=${sortIcon}
></wa-icon>`
: nothing
}
Expand Down Expand Up @@ -161,7 +160,7 @@ export function renderDeviceTableBody(p: DeviceTableBodyProps): TemplateResult {
</td>`
: nothing
}
${row.getVisibleCells().map((cell: Cell<DeviceRow, unknown>) => {
${row.getVisibleCells().map((cell) => {
// The stacked mobile layout (table-styles.ts) shows each
// cell's column header as a field label. It's a real
// span (not a CSS ::before) so screen readers announce
Expand Down
67 changes: 27 additions & 40 deletions src/components/dashboard/device-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,13 @@ import {
mdiUpload,
} from "@mdi/js";
import {
type ColumnDef,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
type ColumnVisibilityState,
functionalUpdate,
type PaginationState,
type SortingState,
TableController,
type VisibilityState,
type Updater,
} from "@tanstack/lit-table";
import type { Row, Table } from "@tanstack/lit-table";
import type { PropertyValues } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
Expand Down Expand Up @@ -59,6 +55,13 @@ import {
import { tableCellStyles } from "./table-cell-styles.js";
import type { ToggleableColumn } from "./table-column-toggle.js";
import { createDeviceColumns, type DeviceRow } from "./table-columns.js";
import {
type DeviceColumnDef,
type DeviceTable,
deviceTableFeatures,
type DeviceTableFeatures,
type DeviceTableRow,
} from "./table-features.js";
import { tableLayoutStyles } from "./table-styles.js";

import "@home-assistant/webawesome/dist/components/icon/icon.js";
Expand Down Expand Up @@ -89,12 +92,7 @@ registerMdiIcons({
upload: mdiUpload,
});

const coreRowModel = getCoreRowModel<DeviceRow>();
const sortedRowModel = getSortedRowModel<DeviceRow>();
const filteredRowModel = getFilteredRowModel<DeviceRow>();
const paginatedRowModel = getPaginationRowModel<DeviceRow>();

const DEFAULT_HIDDEN_COLUMNS: VisibilityState = {
const DEFAULT_HIDDEN_COLUMNS: ColumnVisibilityState = {
comment: false,
area: false,
labels: false,
Expand Down Expand Up @@ -147,7 +145,7 @@ export class ESPHomeDeviceTable extends LitElement {

/** Column visibility from preferences — the host mirrors saves back, so a remount reseeds it. */
@property({ attribute: false })
initialColumnVisibility: VisibilityState | null = null;
initialColumnVisibility: ColumnVisibilityState | null = null;

/** Page size from preferences — the host mirrors saves back, so a remount reseeds it. */
@property({ type: Number, attribute: "initial-page-size" })
Expand All @@ -157,7 +155,7 @@ export class ESPHomeDeviceTable extends LitElement {
private _sorting: SortingState = [];

@state()
private _columnVisibility: VisibilityState = { ...DEFAULT_HIDDEN_COLUMNS };
private _columnVisibility: ColumnVisibilityState = { ...DEFAULT_HIDDEN_COLUMNS };

@state()
private _pageSize = 25;
Expand All @@ -181,34 +179,27 @@ export class ESPHomeDeviceTable extends LitElement {
@query(".table-scroll")
private _scrollContainer!: HTMLDivElement;

private _tableController = new TableController<DeviceRow>(this);
private _tableController = new TableController<DeviceTableFeatures, DeviceRow>(this);
private _rows: DeviceRow[] = [];
private _visibleConfigs: string[] = [];
private _columns: ColumnDef<DeviceRow>[] = [];
private _columns: DeviceColumnDef[] = [];
private _prevLocalize: LocalizeFunc | null = null;

// ─── Stable callbacks ───

private _handleSortingChange = (
updater: SortingState | ((old: SortingState) => SortingState)
) => {
this._sorting = typeof updater === "function" ? updater(this._sorting) : updater;
private _handleSortingChange = (updater: Updater<SortingState>) => {
this._sorting = functionalUpdate(updater, this._sorting);
fireEvent(this, "table-sort-change", this._sorting);
};

private _handleVisibilityChange = (
updater: VisibilityState | ((old: VisibilityState) => VisibilityState)
) => {
this._columnVisibility =
typeof updater === "function" ? updater(this._columnVisibility) : updater;
private _handleVisibilityChange = (updater: Updater<ColumnVisibilityState>) => {
this._columnVisibility = functionalUpdate(updater, this._columnVisibility);
fireEvent(this, "table-visibility-change", this._columnVisibility);
};

private _handlePaginationChange = (
updater: PaginationState | ((old: PaginationState) => PaginationState)
) => {
private _handlePaginationChange = (updater: Updater<PaginationState>) => {
const current = { pageSize: this._pageSize, pageIndex: this._pageIndex };
const next = typeof updater === "function" ? updater(current) : updater;
const next = functionalUpdate(updater, current);
const pageSizeChanged = next.pageSize !== this._pageSize;
this._pageSize = next.pageSize;
this._pageIndex = next.pageIndex;
Expand All @@ -218,7 +209,7 @@ export class ESPHomeDeviceTable extends LitElement {
};

private _globalFilterFn = (
row: Row<DeviceRow>,
row: DeviceTableRow,
_columnId: string,
filterValue: unknown
): boolean => {
Expand All @@ -227,7 +218,7 @@ export class ESPHomeDeviceTable extends LitElement {
// dashboard's select-all scoping helper matches the same rows
// this filter makes visible (single source of truth).
const q = (filterValue as string).trim().toLowerCase();
return matchesDeviceRow(row.original as DeviceRow, q);
return matchesDeviceRow(row.original, q);
};

// ─── Lifecycle ───
Expand Down Expand Up @@ -284,7 +275,7 @@ export class ESPHomeDeviceTable extends LitElement {
comment: d.comment || "",
area: d.area || "",
// Resolve labels here once per render rather than from the
// cell renderer — TanStack's sortingFn / filterFn read the
// cell renderer — TanStack's sortFn / filterFn read the
// accessor value, so they need the resolved objects rather
// than opaque ids.
labels: resolveLabelIds(d.labels, this._labelCatalog),
Expand Down Expand Up @@ -327,10 +318,7 @@ export class ESPHomeDeviceTable extends LitElement {
onSortingChange: this._handleSortingChange,
onColumnVisibilityChange: this._handleVisibilityChange,
onPaginationChange: this._handlePaginationChange,
getCoreRowModel: coreRowModel,
getSortedRowModel: sortedRowModel,
getFilteredRowModel: filteredRowModel,
getPaginationRowModel: paginatedRowModel,
features: deviceTableFeatures,
globalFilterFn: this._globalFilterFn,
});

Expand All @@ -342,7 +330,6 @@ export class ESPHomeDeviceTable extends LitElement {
);
const rows = table.getRowModel().rows;
this._visibleConfigs = table.getFilteredRowModel().rows.map((r) => r.original.config);
const pgState = table.getState().pagination;
const toggleCols: ToggleableColumn[] = table
.getAllColumns()
.filter((c) => c.getCanHide())
Expand Down Expand Up @@ -380,7 +367,7 @@ export class ESPHomeDeviceTable extends LitElement {
</table>
</div>
<esphome-table-pagination
page-index=${pgState.pageIndex}
page-index=${effectivePageIndex}
page-count=${table.getPageCount()}
page-size=${this._pageSize}
total-rows=${table.getFilteredRowModel().rows.length}
Expand Down Expand Up @@ -476,7 +463,7 @@ export class ESPHomeDeviceTable extends LitElement {
`;
}

private _renderControls(table: Table<DeviceRow>, toggleCols: ToggleableColumn[]) {
private _renderControls(table: DeviceTable, toggleCols: ToggleableColumn[]) {
return html`
<div class="controls">
<slot name="toolbar"></slot>
Expand Down
4 changes: 2 additions & 2 deletions src/components/dashboard/prefs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SortingState, VisibilityState } from "@tanstack/lit-table";
import type { ColumnVisibilityState, SortingState } from "@tanstack/lit-table";
import type { UserPreferences } from "../../api/types/system.js";
import { SortDirection } from "../../api/types/system.js";
import type { ESPHomePageDashboard } from "../../pages/dashboard.js";
Expand Down Expand Up @@ -41,7 +41,7 @@ export function saveTablePreference(host: ESPHomePageDashboard, e: CustomEvent):
: null,
};
} else if (type === "table-visibility-change") {
const visibility = (e as CustomEvent<VisibilityState>).detail;
const visibility = (e as CustomEvent<ColumnVisibilityState>).detail;
host._tableColumnVisibility = visibility;
patch = { table_column_visibility: visibility };
} else if (type === "table-page-size-change") {
Expand Down
4 changes: 2 additions & 2 deletions src/components/dashboard/render-content.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SortingState, VisibilityState } from "@tanstack/lit-table";
import type { ColumnVisibilityState, SortingState } from "@tanstack/lit-table";
import { html, type TemplateResult } from "lit";
import type { AdoptableDevice, ConfiguredDevice } from "../../api/types/devices.js";
import type { ESPHomePageDashboard } from "../../pages/dashboard.js";
Expand Down Expand Up @@ -193,7 +193,7 @@ export function renderTable(host: ESPHomePageDashboard): TemplateResult {
.selectedDevices=${host._selectedDevices}
.highlightConfiguration=${host._recentlyAdopted}
@table-sort-change=${(e: CustomEvent<SortingState>) => host._saveTablePreference(e)}
@table-visibility-change=${(e: CustomEvent<VisibilityState>) =>
@table-visibility-change=${(e: CustomEvent<ColumnVisibilityState>) =>
host._saveTablePreference(e)}
@table-page-size-change=${(e: CustomEvent<number>) => host._saveTablePreference(e)}
@row-click=${(e: CustomEvent<ConfiguredDevice>) =>
Expand Down
Loading
Loading