diff --git a/README.md b/README.md index 08e9d43..28b638a 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ layout: | ----------------- | ------------ | ------------ | --------------------------------------------------------------------------------------------------------- | ------------------------ | | type | string | **Required** | `custom:time-picker-card` | | | entity | string | **Required** | [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity with `has_time: true` | | -| name | string | **Optional** | Card name | Entity's `friendly_name` | +| name | string / list | **Optional** | Card name. Accepts a [structured name](#structured-names) on Home Assistant 2026.4 and later. | Entity's name | | link_values | boolean | **Optional** | If enabled, will change hour when minutes overflow. E.g. will go from 11:55 to 12:00, instead of 11:00 | `false` | | hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` | | hour_step | number | **Optional** | Hour change when clicking arrows | `1` | @@ -156,6 +156,30 @@ you can use the following variables in your theme's config file: | time-picker-border-radius | `var(--ha-card-border-radius)` | Border radius of the card | | time-picker-control-padding | `8px` | Padding for interactive elements. Increase for larger hitboxes of the controls. Example: `10px 12px` | +### Structured names + +*Requires Home Assistant 2026.4 or later. On earlier versions a structured `name` falls back to the entity's friendly name.* + +Home Assistant composes an entity's display name out of its registry context +(entity, device, area, floor) rather than one `friendly_name` string. `name` can +be a list of those parts instead of a plain string, so it keeps following renames +and matches what the built-in cards show: + +```yaml +type: custom:time-picker-card +entity: input_datetime.alarm +name: + - type: area + - type: entity +``` + +Available part types are `entity`, `device`, `parent_device`, `area`, `floor`, and +`text` (a literal, written as `{type: text, text: 'Alarm'}`). Parts that resolve to +nothing are dropped. A plain string `name` keeps working exactly as before, and the +visual editor offers both modes on Home Assistant 2025.11 and later. + +See the [Home Assistant developer documentation](https://developers.home-assistant.io/docs/frontend/data#hassformatentitynamestateobj-name-options) for details. + ## Meta **Georgi Gardev** diff --git a/src/editor.ts b/src/editor.ts index 05062e5..07c9e9f 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1,4 +1,5 @@ import { HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers'; +import { supportsEntityNameSelector } from './entity-name'; import { html, LitElement, TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { TimePickerCardConfig } from './types'; @@ -17,12 +18,16 @@ const NAME_TO_LABEL_MAP = { seconds: 'Seconds', }; -const SCHEMA = [ +// The entity_name selector, which lets users compose a name out of registry +// parts, was added in HA 2025.11. Older versions fall back to a plain text field. +const nameSchema = (hass: HomeAssistant) => + supportsEntityNameSelector(hass) + ? { name: 'name', selector: { entity_name: {} }, context: { entity: 'entity' } } + : { name: 'name', selector: { text: {} } }; + +const schema = (hass: HomeAssistant) => [ { name: 'entity', selector: { entity: { domain: 'input_datetime' } } }, - { - name: 'name', - selector: { text: {} }, - }, + nameSchema(hass), { type: 'grid', schema: [ @@ -135,7 +140,7 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit diff --git a/src/entity-name.ts b/src/entity-name.ts new file mode 100644 index 0000000..5302403 --- /dev/null +++ b/src/entity-name.ts @@ -0,0 +1,49 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { HassEntity } from 'home-assistant-js-websocket'; +import { EntityName } from './types'; + +// hass.formatEntityName only accepts a card's `name` option (a user string, a +// structured name, or undefined) from HA 2026.4. Earlier versions expose the +// same helper with an incompatible signature, so feature detection is not +// enough - the version has to be checked. +function atLeastVersion(hass: HomeAssistant, major: number, minor: number): boolean { + const [haMajor, haMinor] = (hass.config?.version ?? '').split('.', 2); + return Number(haMajor) > major || (Number(haMajor) === major && Number(haMinor) >= minor); +} + +function supportsEntityNames(hass: HomeAssistant): boolean { + return atLeastVersion(hass, 2026, 4); +} + +/** + * The `entity_name` selector, which lets users compose a name out of registry + * parts in the visual editor, was added in HA 2025.11. + */ +export function supportsEntityNameSelector(hass: HomeAssistant): boolean { + return atLeastVersion(hass, 2025, 11); +} + +type HassWithEntityNames = HomeAssistant & { + formatEntityName: (stateObj: HassEntity, name: EntityName | undefined) => string; +}; + +/** + * Resolves a `name` option against the entity's registry context (entity, + * device, area, floor). Falls back to the friendly name on Home Assistant + * versions that cannot resolve a structured name. + */ +export function computeEntityName( + hass: HomeAssistant, + stateObj: HassEntity | undefined, + name: EntityName | undefined +): string | undefined { + const configuredName = typeof name === 'string' ? name : undefined; + + if (!stateObj) { + return configuredName; + } + if (supportsEntityNames(hass)) { + return (hass as HassWithEntityNames).formatEntityName(stateObj, name); + } + return configuredName ?? stateObj.attributes.friendly_name; +} diff --git a/src/time-picker-card.ts b/src/time-picker-card.ts index e687a36..7267d48 100644 --- a/src/time-picker-card.ts +++ b/src/time-picker-card.ts @@ -11,6 +11,7 @@ import { css, CSSResult, html, LitElement, TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { ClassInfo, classMap } from 'lit/directives/class-map.js'; import { actionHandler } from './action-handler-directive'; +import { computeEntityName } from './entity-name'; import './components/time-period.component'; import './components/time-unit.component'; import { @@ -79,7 +80,7 @@ export class TimePickerCard extends LitElement implements LovelaceCard { } private get name(): string | undefined { - return this.config.name || this.entity?.attributes.friendly_name; + return computeEntityName(this.hass, this.entity, this.config.name); } private get shouldShowPeriod(): boolean { diff --git a/src/types.ts b/src/types.ts index eaa7f6f..fb25078 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,7 @@ import { ActionConfig, LovelaceCardConfig } from 'custom-card-helpers'; export interface TimePickerCardConfig extends LovelaceCardConfig { entity: string; - name?: string; + name?: EntityName; link_values?: boolean; hour_mode?: HourMode; hour_step?: number; @@ -16,6 +16,13 @@ export interface TimePickerCardConfig extends LovelaceCardConfig { hold_action?: ActionConfig; } +/** A `name` option: a plain string, or name parts resolved from the registry. */ +export type EntityName = string | EntityNameItem | EntityNameItem[]; + +export type EntityNameItem = + | { type: 'entity' | 'device' | 'parent_device' | 'area' | 'floor' } + | { type: 'text'; text: string }; + export type HourMode = 12 | 24 | undefined; export interface TimePickerLayoutConfig {