diff --git a/src/components/entity/ha-entity-combo-box.ts b/src/components/entity/ha-entity-combo-box.ts index f0f3ede62b..db18168c3a 100644 --- a/src/components/entity/ha-entity-combo-box.ts +++ b/src/components/entity/ha-entity-combo-box.ts @@ -5,7 +5,6 @@ import type { HassEntity } from "home-assistant-js-websocket"; import type { PropertyValues, TemplateResult } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import { computeAreaName } from "../../common/entity/compute_area_name"; @@ -30,28 +29,17 @@ import "../ha-icon-button"; import "../ha-svg-icon"; import "./state-badge"; -const FAKE_ENTITY: HassEntity = { - entity_id: "", - state: "", - last_changed: "", - last_updated: "", - context: { id: "", user_id: null, parent_id: null }, - attributes: {}, -}; - -interface EntityComboBoxItem extends HassEntity { +interface EntityComboBoxItem { // Force empty label to always display empty value by default in the search field + id: string; label: ""; primary: string; secondary?: string; - translated_domain?: string; - show_entity_id?: boolean; - entity_name?: string; - area_name?: string; - device_name?: string; - friendly_name?: string; + domain_name?: string; + search_labels?: string[]; sorting_label?: string; icon_path?: string; + stateObj?: HassEntity; } export type HaEntityComboBoxEntityFilterFunc = (entity: HassEntity) => boolean; @@ -59,22 +47,6 @@ export type HaEntityComboBoxEntityFilterFunc = (entity: HassEntity) => boolean; const CREATE_ID = "___create-new-entity___"; const NO_ENTITIES_ID = "___no-entities___"; -const DOMAIN_STYLE = styleMap({ - fontSize: "var(--ha-font-size-s)", - fontWeight: "var(--ha-font-weight-normal)", - lineHeight: "var(--ha-line-height-normal)", - alignSelf: "flex-end", - maxWidth: "30%", - textOverflow: "ellipsis", - overflow: "hidden", - whiteSpace: "nowrap", -}); - -const ENTITY_ID_STYLE = styleMap({ - fontFamily: "var(--ha-font-family-code)", - fontSize: "var(--ha-font-size-xs)", -}); - @customElement("ha-entity-combo-box") export class HaEntityComboBox extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -177,33 +149,41 @@ export class HaEntityComboBox extends LitElement { private _rowRenderer: ComboBoxLitRenderer = ( item, { index } - ) => html` - - ${item.icon_path - ? html`` - : html` - - `} - ${item.primary} - ${item.secondary - ? html`${item.secondary}` - : nothing} - ${item.entity_id && item.show_entity_id - ? html`${item.entity_id}` - : nothing} - ${item.translated_domain && !item.show_entity_id - ? html`
- ${item.translated_domain} -
` - : nothing} -
- `; + ) => { + const showEntityId = this.hass.userData?.showEntityIdPicker; + + return html` + + ${item.icon_path + ? html` + + ` + : html` + + `} + ${item.primary} + ${item.secondary + ? html`${item.secondary}` + : nothing} + ${item.stateObj && showEntityId + ? html` + + ${item.stateObj.entity_id} + + ` + : nothing} + ${item.domain_name && !showEntityId + ? html` +
${item.domain_name}
+ ` + : nothing} +
+ `; + }; private _getItems = memoizeOne( ( @@ -218,7 +198,7 @@ export class HaEntityComboBox extends LitElement { excludeEntities: this["excludeEntities"], createDomains: this["createDomains"] ): EntityComboBoxItem[] => { - let states: EntityComboBoxItem[] = []; + let items: EntityComboBoxItem[] = []; let entityIds = Object.keys(hass.states); @@ -236,9 +216,8 @@ export class HaEntityComboBox extends LitElement { ); return { - ...FAKE_ENTITY, + id: CREATE_ID + domain, label: "", - entity_id: CREATE_ID + domain, primary: primary, secondary: this.hass.localize( "ui.components.entity.entity-picker.new_entity" @@ -251,9 +230,8 @@ export class HaEntityComboBox extends LitElement { if (!entityIds.length) { return [ { - ...FAKE_ENTITY, + id: NO_ENTITIES_ID, label: "", - entity_id: NO_ENTITIES_ID, primary: this.hass!.localize( "ui.components.entity.entity-picker.no_entities" ), @@ -289,7 +267,7 @@ export class HaEntityComboBox extends LitElement { const isRTL = computeRTL(this.hass); - states = entityIds + items = entityIds .map((entityId) => { const stateObj = hass!.states[entityId]; @@ -300,28 +278,32 @@ export class HaEntityComboBox extends LitElement { const deviceName = device ? computeDeviceName(device) : undefined; const areaName = area ? computeAreaName(area) : undefined; + const domainName = domainToName( + this.hass.localize, + computeDomain(entityId) + ); + const primary = entityName || deviceName || entityId; const secondary = [areaName, entityName ? deviceName : undefined] .filter(Boolean) .join(isRTL ? " ◂ " : " ▸ "); - const translatedDomain = domainToName( - this.hass.localize, - computeDomain(entityId) - ); - return { - ...hass!.states[entityId], + id: entityId, label: "", primary: primary, secondary: secondary, - translated_domain: translatedDomain, - sorting_label: [deviceName, entityName].filter(Boolean).join("-"), - entity_name: entityName || deviceName, - area_name: areaName, - device_name: deviceName, - friendly_name: friendlyName, - show_entity_id: hass.userData?.showEntityIdPicker, + domain_name: domainName, + sorting_label: [deviceName, entityName].filter(Boolean).join("_"), + search_labels: [ + entityName, + deviceName, + areaName, + domainName, + friendlyName, + entityId, + ].filter(Boolean) as string[], + stateObj: stateObj, }; }) .sort((entityA, entityB) => @@ -333,41 +315,43 @@ export class HaEntityComboBox extends LitElement { ); if (includeDeviceClasses) { - states = states.filter( - (stateObj) => + items = items.filter( + (item) => // We always want to include the entity of the current value - stateObj.entity_id === this.value || - (stateObj.attributes.device_class && - includeDeviceClasses.includes(stateObj.attributes.device_class)) + item.id === this.value || + (item.stateObj?.attributes.device_class && + includeDeviceClasses.includes( + item.stateObj.attributes.device_class + )) ); } if (includeUnitOfMeasurement) { - states = states.filter( - (stateObj) => + items = items.filter( + (item) => // We always want to include the entity of the current value - stateObj.entity_id === this.value || - (stateObj.attributes.unit_of_measurement && + item.id === this.value || + (item.stateObj?.attributes.unit_of_measurement && includeUnitOfMeasurement.includes( - stateObj.attributes.unit_of_measurement + item.stateObj.attributes.unit_of_measurement )) ); } if (entityFilter) { - states = states.filter( - (stateObj) => + items = items.filter( + (item) => // We always want to include the entity of the current value - stateObj.entity_id === this.value || entityFilter!(stateObj) + item.id === this.value || + (item.stateObj && entityFilter!(item.stateObj)) ); } - if (!states.length) { + if (!items.length) { return [ { - ...FAKE_ENTITY, + id: NO_ENTITIES_ID, label: "", - entity_id: NO_ENTITIES_ID, primary: this.hass!.localize( "ui.components.entity.entity-picker.no_match" ), @@ -378,10 +362,10 @@ export class HaEntityComboBox extends LitElement { } if (createItems?.length) { - states.push(...createItems); + items.push(...createItems); } - return states; + return items; } ); @@ -424,7 +408,7 @@ export class HaEntityComboBox extends LitElement { protected render(): TemplateResult { return html` - Fuse.createIndex( - [ - "entity_name", - "device_name", - "area_name", - "translated_domain", - "friendly_name", // for backwards compatibility - "entity_id", // for technical search - ], - states - ) + Fuse.createIndex(["search_labels"], states) ); private _filterChanged(ev: CustomEvent): void { @@ -503,9 +477,8 @@ export class HaEntityComboBox extends LitElement { if (results.length === 0) { target.filteredItems = [ { - ...FAKE_ENTITY, + id: NO_ENTITIES_ID, label: "", - entity_id: NO_ENTITIES_ID, primary: this.hass!.localize( "ui.components.entity.entity-picker.no_match" ), diff --git a/src/components/entity/ha-statistic-combo-box.ts b/src/components/entity/ha-statistic-combo-box.ts index 313bececcb..30749279ae 100644 --- a/src/components/entity/ha-statistic-combo-box.ts +++ b/src/components/entity/ha-statistic-combo-box.ts @@ -1,11 +1,10 @@ -import { mdiChartLine, mdiShape } from "@mdi/js"; +import { mdiChartLine, mdiHelpCircle, mdiShape } from "@mdi/js"; import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import Fuse from "fuse.js"; import type { HassEntity } from "home-assistant-js-websocket"; import type { PropertyValues, TemplateResult } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; @@ -26,31 +25,27 @@ import type { HaComboBox } from "../ha-combo-box"; import "../ha-combo-box-item"; import "../ha-svg-icon"; import "./state-badge"; +import { documentationUrl } from "../../util/documentation-url"; type StatisticItemType = "entity" | "external" | "no_state"; interface StatisticItem { + // Force empty label to always display empty value by default in the search field id: string; + statistic_id?: string; label: ""; primary: string; secondary?: string; - show_entity_id?: boolean; - entity_name?: string; - area_name?: string; - device_name?: string; - friendly_name?: string; + search_labels?: string[]; sorting_label?: string; - state?: HassEntity; + icon_path?: string; type?: StatisticItemType; - iconPath?: string; + stateObj?: HassEntity; } -const TYPE_ORDER = ["entity", "external", "no_state"] as StatisticItemType[]; +const MISSING_ID = "___missing-entity___"; -const ENTITY_ID_STYLE = styleMap({ - fontFamily: "var(--ha-font-family-code)", - fontSize: "11px", -}); +const TYPE_ORDER = ["entity", "external", "no_state"] as StatisticItemType[]; @customElement("ha-statistic-combo-box") export class HaStatisticComboBox extends LitElement { @@ -131,37 +126,39 @@ export class HaStatisticComboBox extends LitElement { private _rowRenderer: ComboBoxLitRenderer = ( item, { index } - ) => html` - - ${!item.state - ? html` - - ` - : html` - - `} - - ${item.primary} - ${item.secondary - ? html`${item.secondary}` - : nothing} - ${item.id && item.show_entity_id - ? html` - - ${item.id} - - ` - : nothing} - - `; + ) => { + const showEntityId = this.hass.userData?.showEntityIdPicker; + return html` + + ${item.icon_path + ? html` + + ` + : item.stateObj + ? html` + + ` + : nothing} + ${item.primary} + ${item.secondary + ? html`${item.secondary}` + : nothing} + ${item.id && showEntityId + ? html` + ${item.statistic_id} + ` + : nothing} + + `; + }; private _getItems = memoizeOne( ( @@ -249,19 +246,22 @@ export class HaStatisticComboBox extends LitElement { label: "", type, sorting_label: label, - iconPath: mdiShape, + search_labels: [label, id], + icon_path: mdiShape, }); } else if (type === "external") { const domain = id.split(":")[0]; const domainName = domainToName(this.hass.localize, domain); output.push({ id, + statistic_id: id, primary: label, secondary: domainName, label: "", type, sorting_label: label, - iconPath: mdiChartLine, + search_labels: [label, domainName, id], + icon_path: mdiChartLine, }); } } @@ -283,17 +283,20 @@ export class HaStatisticComboBox extends LitElement { output.push({ id, + statistic_id: id, + label: "", primary, secondary, - label: "", - state: stateObj, + stateObj: stateObj, type: "entity", sorting_label: [deviceName, entityName].join("_"), - entity_name: entityName || deviceName, - area_name: areaName, - device_name: deviceName, - friendly_name: friendlyName, - show_entity_id: hass.userData?.showEntityIdPicker, + search_labels: [ + entityName, + deviceName, + areaName, + friendlyName, + id, + ].filter(Boolean) as string[], }); }); @@ -323,11 +326,12 @@ export class HaStatisticComboBox extends LitElement { } output.push({ - id: "__missing", + id: MISSING_ID, primary: this.hass.localize( "ui.components.statistic-picker.missing_entity" ), label: "", + icon_path: mdiHelpCircle, }); return output; @@ -422,8 +426,12 @@ export class HaStatisticComboBox extends LitElement { private _statisticChanged(ev: ValueChangedEvent) { ev.stopPropagation(); let newValue = ev.detail.value; - if (newValue === "__missing") { + if (newValue === MISSING_ID) { newValue = ""; + window.open( + documentationUrl(this.hass, this.helpMissingEntityUrl), + "_blank" + ); } if (newValue !== this._value) { @@ -436,16 +444,7 @@ export class HaStatisticComboBox extends LitElement { } private _fuseIndex = memoizeOne((states: StatisticItem[]) => - Fuse.createIndex( - [ - "entity_name", - "device_name", - "area_name", - "friendly_name", // for backwards compatibility - "id", // for technical search - ], - states - ) + Fuse.createIndex(["search_labels"], states) ); private _filterChanged(ev: CustomEvent): void { diff --git a/src/components/ha-combo-box-item.ts b/src/components/ha-combo-box-item.ts index ddbf56da26..1a061174fb 100644 --- a/src/components/ha-combo-box-item.ts +++ b/src/components/ha-combo-box-item.ts @@ -35,6 +35,20 @@ export class HaComboBoxItem extends HaMdListItem { width: 32px; height: 32px; } + ::slotted(.code) { + font-family: var(--ha-font-family-code); + font-size: var(--ha-font-size-xs); + } + [slot="trailing-supporting-text"] { + font-size: var(--ha-font-size-s); + font-weight: var(--ha-font-weight-normal); + line-height: var(--ha-line-height-normal); + align-self: flex-end; + max-width: 30%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } `, ]; } diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 3af88bb787..68833ad0bf 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -95,22 +95,6 @@ type BaseNavigationCommand = Pick< "primaryText" | "path" >; -const DOMAIN_STYLE = styleMap({ - fontSize: "var(--ha-font-size-s)", - fontWeight: "var(--ha-font-weight-normal)", - lineHeight: "var(--ha-line-height-normal)", - alignSelf: "flex-end", - maxWidth: "30%", - textOverflow: "ellipsis", - overflow: "hidden", - whiteSpace: "nowrap", -}); - -const ENTITY_ID_STYLE = styleMap({ - fontFamily: "var(--ha-font-family-code)", - fontSize: "var(--ha-font-size-xs)", -}); - @customElement("ha-quick-bar") export class QuickBar extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -397,12 +381,12 @@ export class QuickBar extends LitElement { ? html` ${item.altText} ` : nothing} ${item.entityId && showEntityId - ? html`${item.entityId}` + ? html` + ${item.entityId} + ` : nothing} ${item.translatedDomain && !showEntityId - ? html`
+ ? html`
${item.translatedDomain}
` : nothing} @@ -1038,6 +1022,22 @@ export class QuickBar extends LitElement { --md-list-item-bottom-space: 8px; } + ha-md-list-item .code { + font-family: var(--ha-font-family-code); + font-size: var(--ha-font-size-xs); + } + + ha-md-list-item [slot="trailing-supporting-text"] { + font-size: var(--ha-font-size-s); + font-weight: var(--ha-font-weight-normal); + line-height: var(--ha-line-height-normal); + align-self: flex-end; + max-width: 30%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + ha-tip { padding: 20px; }