251 files

This commit is contained in:
Home Assistant Version Control committed 2026-10-16 12:38:25 +00:00
1 parent 3acf0cdf8d
commit 64eb0c647c
251 files changed
+6380 -14560

No files matched your search

@@ -130,7 +130,7 @@ import { INITIAL_STICKY, nextStickyState, stickyStateOnSelect, stickyTop, type S
import { invalidateSettingsCache, parseSettings, type SettingsWire } from "./helpers/settings-cache";
import type { PartsCostMode } from "./helpers/parts-cost";
import { canWrite } from "./helpers/permissions";
import { renderStatusBadge } from "./renderers/status";
import { renderStatusBadge, statusKey, statusLabel, type StatusLike } from "./renderers/status";
import { TOAST_MS, ACTION_TOAST_MS } from "./helpers/toast";
import { primaryTriggerEntity } from "./helpers/trigger-entities";
import { buildHistoryEntryDraft } from "./helpers/history-draft";
@@ -355,6 +355,9 @@ export class MaintenanceSupporterPanel extends LitElement {
@state() private _virtStart = 0;
@state() private _virtEnd = 0; // 0 = uninitialized → initial slice
private _virtRowHeight = 53;
/** Set by the first render and by every scroll/resize: the next window
* pass may re-measure the row height (see _updateVirtualWindow). */
private _virtMeasure = true;
private _virtTotalRows = 0;
private _virtScrollAttached = false;
private _virtRaf = 0;
@@ -619,8 +622,9 @@ export class MaintenanceSupporterPanel extends LitElement {
// Virtualized task table: attach the scroll listener once `.content`
// exists (lit keeps the node stable across renders), then reconcile the
// window with the just-rendered DOM (row-height probe + clamps). Only
// sets state when the window actually moved, so this can't loop.
// window with the just-rendered DOM (clamps). Only sets state when the
// window actually moved, and the row-height estimate holds still until
// the next scroll or resize, so this can't loop.
const content = this.shadowRoot?.querySelector(".content");
if (content && !this._virtScrollAttached) {
content.addEventListener("scroll", this._onVirtualScroll, { passive: true });
@@ -715,6 +719,7 @@ export class MaintenanceSupporterPanel extends LitElement {
/** rAF-throttled scroll/resize handler for the virtualized task table. */
private _onVirtualScroll = (): void => {
this._virtMeasure = true;
if (this._virtRaf) return;
this._virtRaf = requestAnimationFrame(() => {
this._virtRaf = 0;
@@ -728,10 +733,22 @@ export class MaintenanceSupporterPanel extends LitElement {
const content = this.shadowRoot?.querySelector<HTMLElement>(".content");
const table = this.shadowRoot?.querySelector<HTMLElement>(".task-table.virtual");
if (!content || !table) return;
// Probe the real row height from a rendered row (uniform rows; the
// hidden sizer row is height 0 and excluded).
const probe = table.querySelector<HTMLElement>(".task-row:not(.virt-sizer)");
if (probe && probe.offsetHeight > 20) this._virtRowHeight = probe.offsetHeight;
// The row-height estimate is the mean of the rendered rows (the hidden
// sizer row is height 0 and excluded), taken on the first render and
// after each scroll or resize, never from the render the new window
// caused. Rows are not uniform: a sensor row carries a value and a
// sparkline under its date. Probing the first row after every render
// fed a loop: window A measured a short row, the new estimate moved the
// window to B, B's first row was a tall one, the estimate moved it back
// to A, and the page froze (2026-10-13, a 125-task install).
if (this._virtMeasure) {
const rows = [...table.querySelectorAll<HTMLElement>(".task-row:not(.virt-sizer)")];
const mean = rows.length ? rows.reduce((n, r) => n + r.offsetHeight, 0) / rows.length : 0;
if (mean > 20) {
if (Math.abs(mean - this._virtRowHeight) >= 2) this._virtRowHeight = Math.round(mean);
this._virtMeasure = false;
}
}
const listTop =
table.getBoundingClientRect().top - content.getBoundingClientRect().top + content.scrollTop;
const w = computeWindow({
@@ -3495,8 +3512,8 @@ export class MaintenanceSupporterPanel extends LitElement {
* (colour-blind accessibility) — icon + text + colour together. The label
* is its own span so narrow/tight rows can drop it (#150: the pill ate a
* third of a phone row) while `title`/`aria-label` keep the text reachable. */
private _statusBadge(archived: boolean, isDone: boolean, status: string) {
return renderStatusBadge({ archived, is_done: isDone, status }, this._lang);
private _statusBadge(task: StatusLike) {
return renderStatusBadge(task, this._lang);
}
private _setOverviewTab(tab: OverviewTab): void {
@@ -3869,7 +3886,7 @@ export class MaintenanceSupporterPanel extends LitElement {
let anyNfc = false;
let anyPriority = false;
for (const r of rows) {
const label = r.archived ? t("archived", L) : (r.is_done ? t("completed", L) : t(r.status, L));
const label = statusLabel(statusKey(r), L);
if (label.length > widest.length) widest = label;
if (!r.enabled) anyDisabled = true;
if (r.nfc_tag_id) anyNfc = true;
@@ -4625,7 +4642,7 @@ export class MaintenanceSupporterPanel extends LitElement {
</label>
` : nothing}
<span class="cell-badges">
${this._statusBadge(!!row.archived, row.is_done, row.status)}
${this._statusBadge(row)}
${!row.enabled ? html`<span class="badge-disabled">${t("disabled", L)}</span>` : nothing}
${row.nfc_tag_id ? html`<span class="nfc-badge" title="${t("nfc_linked", L)}"><ha-icon icon="mdi:nfc-variant"></ha-icon></span>` : nothing}
${row.priority === "high" ? html`<span class="priority-badge priority-high" title="${t("priority_high", L)}"><ha-icon icon="mdi:chevron-double-up"></ha-icon></span>` : nothing}
@@ -4871,7 +4888,7 @@ export class MaintenanceSupporterPanel extends LitElement {
).map((task) => html`
<div class="task-row${!task.enabled ? ' task-disabled' : ''}">
<span class="cell-badges">
${this._statusBadge(!!task.archived, !!task.is_done, task.status)}
${this._statusBadge(task)}
${!task.enabled ? html`<span class="badge-disabled">${t("disabled", L)}</span>` : nothing}
${task.nfc_tag_id ? html`<span class="nfc-badge" title="${t("nfc_linked", L)}"><ha-icon icon="mdi:nfc-variant"></ha-icon></span>` : nothing}
${task.document_count