From d23e45e410173cbdf6ebc69b61a9327f74653f0f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 4 Nov 2025 18:02:56 +0100 Subject: [PATCH] Handle unknown items in target picker (#27795) * Handle unknown items in target picker * Update ha-target-picker-item-row.ts * update colors * fallback to domain icons --- .../ha-target-picker-item-row.ts | 82 +++++++++++++------ src/translations/en.json | 5 ++ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/src/components/target-picker/ha-target-picker-item-row.ts b/src/components/target-picker/ha-target-picker-item-row.ts index 4884d82902..f7a5db23c8 100644 --- a/src/components/target-picker/ha-target-picker-item-row.ts +++ b/src/components/target-picker/ha-target-picker-item-row.ts @@ -6,6 +6,7 @@ import { mdiLabel, mdiTextureBox, } from "@mdi/js"; +import type { HassEntity } from "home-assistant-js-websocket"; import { css, html, LitElement, nothing, type PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -19,9 +20,12 @@ import { computeDomain } from "../../common/entity/compute_domain"; import { computeEntityName } from "../../common/entity/compute_entity_name"; import { getEntityContext } from "../../common/entity/context/get_entity_context"; import { computeRTL } from "../../common/util/compute_rtl"; +import type { AreaRegistryEntry } from "../../data/area_registry"; import { getConfigEntry } from "../../data/config_entries"; import { labelsContext } from "../../data/context"; +import type { DeviceRegistryEntry } from "../../data/device_registry"; import type { HaEntityPickerEntityFilterFunc } from "../../data/entity"; +import type { FloorRegistryEntry } from "../../data/floor_registry"; import { domainToName } from "../../data/integration"; import type { LabelRegistryEntry } from "../../data/label_registry"; import { @@ -111,10 +115,10 @@ export class HaTargetPickerItemRow extends LitElement { } protected render() { - const { name, context, iconPath, fallbackIconPath, stateObject } = + const { name, context, iconPath, fallbackIconPath, stateObject, notFound } = this._itemData(this.type, this.itemId); - const showEntities = this.type !== "entity"; + const showEntities = this.type !== "entity" && !notFound; const entries = this.parentEntries || this._entries; @@ -128,7 +132,7 @@ export class HaTargetPickerItemRow extends LitElement { } return html` - +
${this.subEntry ? html` @@ -148,11 +152,15 @@ export class HaTargetPickerItemRow extends LitElement { />` : fallbackIconPath ? html`` - : stateObject + : this.type === "entity" ? html` ` @@ -160,8 +168,14 @@ export class HaTargetPickerItemRow extends LitElement {
${name}
- ${context && !this.hideContext - ? html`${context}` + ${notFound || (context && !this.hideContext) + ? html`${notFound + ? this.hass.localize( + `ui.components.target-picker.${this.type}_not_found` + ) + : context}` : nothing} ${this._domainName && this.subEntry ? html` { if (type === "floor") { - const floor = this.hass.floors?.[item]; + const floor: FloorRegistryEntry | undefined = this.hass.floors?.[item]; return { name: floor?.name || item, iconPath: floor?.icon, fallbackIconPath: floor ? floorDefaultIconPath(floor) : mdiHome, + notFound: !floor, }; } if (type === "area") { - const area = this.hass.areas?.[item]; + const area: AreaRegistryEntry | undefined = this.hass.areas?.[item]; return { name: area?.name || item, - context: area.floor_id && this.hass.floors?.[area.floor_id]?.name, + context: area?.floor_id && this.hass.floors?.[area.floor_id]?.name, iconPath: area?.icon, fallbackIconPath: mdiTextureBox, + notFound: !area, }; } if (type === "device") { - const device = this.hass.devices?.[item]; + const device: DeviceRegistryEntry | undefined = this.hass.devices?.[item]; - if (device.primary_config_entry) { + if (device?.primary_config_entry) { this._getDeviceDomain(device.primary_config_entry); } @@ -501,24 +517,25 @@ export class HaTargetPickerItemRow extends LitElement { name: device ? computeDeviceNameDisplay(device, this.hass) : item, context: device?.area_id && this.hass.areas?.[device.area_id]?.name, fallbackIconPath: mdiDevices, + notFound: !device, }; } if (type === "entity") { this._setDomainName(computeDomain(item)); - const stateObject = this.hass.states[item]; - const entityName = computeEntityName( - stateObject, - this.hass.entities, - this.hass.devices - ); - const { area, device } = getEntityContext( - stateObject, - this.hass.entities, - this.hass.devices, - this.hass.areas, - this.hass.floors - ); + const stateObject: HassEntity | undefined = this.hass.states[item]; + const entityName = stateObject + ? computeEntityName(stateObject, this.hass.entities, this.hass.devices) + : item; + const { area, device } = stateObject + ? getEntityContext( + stateObject, + this.hass.entities, + this.hass.devices, + this.hass.areas, + this.hass.floors + ) + : { area: undefined, device: undefined }; const deviceName = device ? computeDeviceName(device) : undefined; const areaName = area ? computeAreaName(area) : undefined; const context = [areaName, entityName ? deviceName : undefined] @@ -528,15 +545,19 @@ export class HaTargetPickerItemRow extends LitElement { name: entityName || deviceName || item, context, stateObject, + notFound: !stateObject, }; } // type label - const label = this._labelRegistry.find((lab) => lab.label_id === item); + const label: LabelRegistryEntry | undefined = this._labelRegistry.find( + (lab) => lab.label_id === item + ); return { name: label?.name || item, iconPath: label?.icon, fallbackIconPath: mdiLabel, + notFound: !label, }; }); @@ -597,11 +618,20 @@ export class HaTargetPickerItemRow extends LitElement { border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); } + .error { + background: var(--ha-color-fill-warning-quiet-resting); + } + + .error [slot="supporting-text"] { + color: var(--ha-color-on-warning-normal); + } + state-badge { color: var(--ha-color-on-neutral-quiet); } .icon { + width: 24px; display: flex; } diff --git a/src/translations/en.json b/src/translations/en.json index 192751fa99..6ae6ff6cde 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -695,6 +695,11 @@ "remove_device_id": "Remove device", "remove_entity_id": "Remove entity", "remove_label_id": "Remove label", + "floor_not_found": "Floor not found", + "area_not_found": "Area not found", + "device_not_found": "Device not found", + "entity_not_found": "Entity not found", + "label_not_found": "Label not found", "devices_count": "{count} {count, plural,\n one {device}\n other {devices}\n}", "entities_count": "{count} {count, plural,\n one {entity}\n other {entities}\n}", "target_details": "Target details",