diff --git a/src/components/entity/ha-entity-name-picker.ts b/src/components/entity/ha-entity-name-picker.ts
index 4899d130c8..43c8290d21 100644
--- a/src/components/entity/ha-entity-name-picker.ts
+++ b/src/components/entity/ha-entity-name-picker.ts
@@ -20,6 +20,7 @@ import "../chips/ha-chip-set";
import "../chips/ha-input-chip";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
+import "../ha-input-helper-text";
import "../ha-sortable";
interface EntityNameOption {
@@ -239,7 +240,6 @@ export class HaEntityNamePicker extends LitElement {
.autofocus=${this.autofocus}
.disabled=${this.disabled}
.required=${this.required && !value.length}
- .helper=${this.helper}
.items=${options}
allow-custom-value
item-id-path="value"
@@ -253,9 +253,20 @@ export class HaEntityNamePicker extends LitElement {
+ ${this._renderHelper()}
`;
}
+ private _renderHelper() {
+ return this.helper
+ ? html`
+
+ ${this.helper}
+
+ `
+ : nothing;
+ }
+
private _onClosed(ev) {
ev.stopPropagation();
this._opened = false;
@@ -510,6 +521,11 @@ export class HaEntityNamePicker extends LitElement {
.sortable-drag {
cursor: grabbing;
}
+
+ ha-input-helper-text {
+ display: block;
+ margin: var(--ha-space-2) 0 0;
+ }
`;
}
diff --git a/src/components/ha-generic-picker.ts b/src/components/ha-generic-picker.ts
index 154e8cb869..0e2e8d5f25 100644
--- a/src/components/ha-generic-picker.ts
+++ b/src/components/ha-generic-picker.ts
@@ -179,7 +179,7 @@ export class HaGenericPicker extends LitElement {
}
ha-input-helper-text {
display: block;
- margin: 8px 0 0;
+ margin: var(--ha-space-2) 0 0;
}
`,
];
diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts
index ee32262d04..90804ba3aa 100644
--- a/src/panels/lovelace/cards/hui-button-card.ts
+++ b/src/panels/lovelace/cards/hui-button-card.ts
@@ -9,13 +9,14 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
+import { computeCssColor } from "../../../common/color/compute-color";
import { DOMAINS_TOGGLE } from "../../../common/const";
import { transform } from "../../../common/decorators/transform";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
-import { computeStateName } from "../../../common/entity/compute_state_name";
+import { stateActive } from "../../../common/entity/state_active";
import {
stateColorBrightness,
stateColorCss,
@@ -40,6 +41,7 @@ import type { FrontendLocaleData } from "../../../data/translation";
import type { Themes } from "../../../data/ws-themes";
import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
+import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
import { findEntities } from "../common/find-entities";
import { hasAction } from "../common/has-action";
import { createEntityNotFoundWarning } from "../components/hui-warning";
@@ -49,8 +51,6 @@ import type {
LovelaceGridOptions,
} from "../types";
import type { ButtonCardConfig } from "./types";
-import { computeCssColor } from "../../../common/color/compute-color";
-import { stateActive } from "../../../common/entity/state_active";
export const getEntityDefaultButtonAction = (entityId?: string) =>
entityId && DOMAINS_TOGGLE.has(computeDomain(entityId))
@@ -183,9 +183,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
`;
}
- const name = this._config.show_name
- ? this._config.name || (stateObj ? computeStateName(stateObj) : "")
- : "";
+ const name = computeLovelaceEntityName(
+ this.hass,
+ stateObj,
+ this._config.name
+ );
return html`
`;
diff --git a/src/panels/lovelace/common/entity/compute-lovelace-entity-name.ts b/src/panels/lovelace/common/entity/compute-lovelace-entity-name.ts
index 4a336ee2ea..53ebd2df05 100644
--- a/src/panels/lovelace/common/entity/compute-lovelace-entity-name.ts
+++ b/src/panels/lovelace/common/entity/compute-lovelace-entity-name.ts
@@ -4,6 +4,7 @@ import {
type EntityNameItem,
} from "../../../../common/entity/compute_entity_name_display";
import type { HomeAssistant } from "../../../../types";
+import { ensureArray } from "../../../../common/array/ensure-array";
/**
* Computes the display name for an entity in Lovelace (cards and badges).
@@ -15,9 +16,24 @@ import type { HomeAssistant } from "../../../../types";
*/
export const computeLovelaceEntityName = (
hass: HomeAssistant,
- stateObj: HassEntity,
+ stateObj: HassEntity | undefined,
nameConfig: string | EntityNameItem | EntityNameItem[] | undefined
-): string =>
- typeof nameConfig === "string"
- ? nameConfig
- : hass.formatEntityName(stateObj, nameConfig || DEFAULT_ENTITY_NAME);
+): string => {
+ if (typeof nameConfig === "string") {
+ return nameConfig;
+ }
+ const config = nameConfig || DEFAULT_ENTITY_NAME;
+ if (stateObj) {
+ return hass.formatEntityName(stateObj, config);
+ }
+ // If entity is not found, fall back to text parts in config
+ // This allows for static names even when the entity is missing
+ // e.g. for a card that doesn't require an entity
+ const textParts = ensureArray(config)
+ .filter((item) => item.type === "text")
+ .map((item) => ("text" in item ? item.text : ""));
+ if (textParts.length) {
+ return textParts.join(" ");
+ }
+ return "";
+};
diff --git a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts
index b77ad77e50..40b6a15797 100644
--- a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts
@@ -5,6 +5,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
+import { DEFAULT_ENTITY_NAME } from "../../../../common/entity/compute_entity_name_display";
import "../../../../components/ha-form/ha-form";
import type {
HaFormSchema,
@@ -16,13 +17,14 @@ import type { ButtonCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import { actionConfigStruct } from "../structs/action-struct";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
+import { entityNameStruct } from "../structs/entity-name-struct";
import { configElementStyle } from "./config-elements-style";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(string()),
- name: optional(string()),
+ name: optional(entityNameStruct),
show_name: optional(boolean()),
icon: optional(string()),
show_icon: optional(boolean()),
@@ -68,7 +70,13 @@ export class HuiButtonCardEditor
(entityId: string | undefined) =>
[
{ name: "entity", selector: { entity: {} } },
- { name: "name", selector: { text: {} } },
+ {
+ name: "name",
+ selector: {
+ entity_name: { default_name: DEFAULT_ENTITY_NAME },
+ },
+ context: { entity: "entity" },
+ },
{
name: "",
type: "grid",
diff --git a/src/panels/lovelace/editor/heading-badge-editor/hui-entity-heading-badge-editor.ts b/src/panels/lovelace/editor/heading-badge-editor/hui-entity-heading-badge-editor.ts
index 19c607d6f1..dfc733c588 100644
--- a/src/panels/lovelace/editor/heading-badge-editor/hui-entity-heading-badge-editor.ts
+++ b/src/panels/lovelace/editor/heading-badge-editor/hui-entity-heading-badge-editor.ts
@@ -13,6 +13,7 @@ import {
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
+import { DEFAULT_ENTITY_NAME } from "../../../../common/entity/compute_entity_name_display";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-form/ha-form";
@@ -27,6 +28,7 @@ import type { LovelaceGenericElementEditor } from "../../types";
import "../conditions/ha-card-conditions-editor";
import { configElementStyle } from "../config-elements/config-elements-style";
import { actionConfigStruct } from "../structs/action-struct";
+import { entityNameStruct } from "../structs/entity-name-struct";
export const DEFAULT_CONFIG: Partial = {
type: "entity",
@@ -37,7 +39,7 @@ export const DEFAULT_CONFIG: Partial = {
const entityConfigStruct = object({
type: optional(string()),
entity: optional(string()),
- name: optional(string()),
+ name: optional(entityNameStruct),
icon: optional(string()),
state_content: optional(union([string(), array(string())])),
show_state: optional(boolean()),
@@ -92,8 +94,11 @@ export class HuiHeadingEntityEditor
{
name: "name",
selector: {
- text: {},
+ entity_name: {
+ default_name: DEFAULT_ENTITY_NAME,
+ },
},
+ context: { entity: "entity" },
},
{
name: "icon",
diff --git a/src/panels/lovelace/heading-badges/hui-entity-heading-badge.ts b/src/panels/lovelace/heading-badges/hui-entity-heading-badge.ts
index 7779e3a6e2..34b67982e8 100644
--- a/src/panels/lovelace/heading-badges/hui-entity-heading-badge.ts
+++ b/src/panels/lovelace/heading-badges/hui-entity-heading-badge.ts
@@ -7,7 +7,6 @@ import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
import { computeDomain } from "../../../common/entity/compute_domain";
-import { computeStateName } from "../../../common/entity/compute_state_name";
import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-heading-badge";
@@ -16,6 +15,7 @@ import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import "../../../state-display/state-display";
import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
+import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { DEFAULT_CONFIG } from "../editor/heading-badge-editor/hui-entity-heading-badge-editor";
@@ -137,7 +137,11 @@ export class HuiEntityHeadingBadge
"--icon-color": color,
};
- const name = config.name || computeStateName(stateObj);
+ const name = computeLovelaceEntityName(
+ this.hass,
+ stateObj,
+ this._config.name
+ );
return html`
`
diff --git a/src/state-display/state-display.ts b/src/state-display/state-display.ts
index 0a2fbbe685..a112024d0a 100644
--- a/src/state-display/state-display.ts
+++ b/src/state-display/state-display.ts
@@ -5,7 +5,6 @@ import { customElement, property } from "lit/decorators";
import { join } from "lit/directives/join";
import { ensureArray } from "../common/array/ensure-array";
import { computeStateDomain } from "../common/entity/compute_state_domain";
-import { computeStateName } from "../common/entity/compute_state_name";
import "../components/ha-relative-time";
import { isUnavailableState } from "../data/entity";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor";
@@ -100,8 +99,8 @@ class StateDisplay extends LitElement {
return this.hass!.formatEntityState(stateObj);
}
- if (content === "name") {
- return html`${this.name || computeStateName(stateObj)}`;
+ if (content === "name" && this.name) {
+ return html`${this.name}`;
}
let relativeDateTime: string | Date | undefined;
diff --git a/test/panels/lovelace/common/entity/compute-lovelace-entity-name.test.ts b/test/panels/lovelace/common/entity/compute-lovelace-entity-name.test.ts
index 3e9028bef3..77e4291a8a 100644
--- a/test/panels/lovelace/common/entity/compute-lovelace-entity-name.test.ts
+++ b/test/panels/lovelace/common/entity/compute-lovelace-entity-name.test.ts
@@ -77,4 +77,71 @@ describe("computeLovelaceEntityName", () => {
expect(mockFormatEntityName).toHaveBeenCalledTimes(1);
expect(mockFormatEntityName).toHaveBeenCalledWith(stateObj, nameConfig);
});
+
+ describe("when stateObj is undefined", () => {
+ it("returns empty string when nameConfig is undefined", () => {
+ const mockFormatEntityName = vi.fn();
+ const hass = createMockHass(mockFormatEntityName);
+
+ const result = computeLovelaceEntityName(hass, undefined, undefined);
+
+ expect(result).toBe("");
+ expect(mockFormatEntityName).not.toHaveBeenCalled();
+ });
+
+ it("returns text from single text EntityNameItem", () => {
+ const mockFormatEntityName = vi.fn();
+ const hass = createMockHass(mockFormatEntityName);
+ const nameConfig = { type: "text" as const, text: "Custom Text" };
+
+ const result = computeLovelaceEntityName(hass, undefined, nameConfig);
+
+ expect(result).toBe("Custom Text");
+ expect(mockFormatEntityName).not.toHaveBeenCalled();
+ });
+
+ it("returns joined text from multiple text EntityNameItems", () => {
+ const mockFormatEntityName = vi.fn();
+ const hass = createMockHass(mockFormatEntityName);
+ const nameConfig = [
+ { type: "text" as const, text: "First" },
+ { type: "text" as const, text: "Second" },
+ ];
+
+ const result = computeLovelaceEntityName(hass, undefined, nameConfig);
+
+ expect(result).toBe("First Second");
+ expect(mockFormatEntityName).not.toHaveBeenCalled();
+ });
+
+ it("returns only text items when mixed with non-text items", () => {
+ const mockFormatEntityName = vi.fn();
+ const hass = createMockHass(mockFormatEntityName);
+ const nameConfig = [
+ { type: "text" as const, text: "Prefix" },
+ { type: "device" as const },
+ { type: "text" as const, text: "Suffix" },
+ { type: "entity" as const },
+ ];
+
+ const result = computeLovelaceEntityName(hass, undefined, nameConfig);
+
+ expect(result).toBe("Prefix Suffix");
+ expect(mockFormatEntityName).not.toHaveBeenCalled();
+ });
+
+ it("returns empty string when no text items in config", () => {
+ const mockFormatEntityName = vi.fn();
+ const hass = createMockHass(mockFormatEntityName);
+ const nameConfig = [
+ { type: "device" as const },
+ { type: "entity" as const },
+ ];
+
+ const result = computeLovelaceEntityName(hass, undefined, nameConfig);
+
+ expect(result).toBe("");
+ expect(mockFormatEntityName).not.toHaveBeenCalled();
+ });
+ });
});