From 48a3e1fd63811a6809a0d1ec90f4f32b25900cfc Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 28 May 2025 22:33:42 +0200 Subject: [PATCH] Put item at the top of picker result if there is an exact match with entity id (#25625) --- src/components/entity/ha-entity-picker.ts | 23 ++++++++++++++++++- src/components/entity/ha-statistic-picker.ts | 24 +++++++++++++++++++- src/components/ha-generic-picker.ts | 5 ++++ src/components/ha-picker-combo-box.ts | 20 +++++++++++++--- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 7803f079f7..ae7aa71088 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -23,7 +23,10 @@ import type { HomeAssistant } from "../../types"; import "../ha-combo-box-item"; import "../ha-generic-picker"; import type { HaGenericPicker } from "../ha-generic-picker"; -import type { PickerComboBoxItem } from "../ha-picker-combo-box"; +import type { + PickerComboBoxItem, + PickerComboBoxSearchFn, +} from "../ha-picker-combo-box"; import type { PickerValueRenderer } from "../ha-picker-field"; import "../ha-svg-icon"; import "./state-badge"; @@ -406,6 +409,7 @@ export class HaEntityPicker extends LitElement { .getItems=${this._getItems} .getAdditionalItems=${this._getAdditionalItems} .hideClearIcon=${this.hideClearIcon} + .searchFn=${this._searchFn} .valueRenderer=${this._valueRenderer} @value-changed=${this._valueChanged} > @@ -413,6 +417,23 @@ export class HaEntityPicker extends LitElement { `; } + private _searchFn: PickerComboBoxSearchFn = ( + search, + filteredItems + ) => { + // If there is exact match for entity id, put it first + const index = filteredItems.findIndex( + (item) => item.stateObj?.entity_id === search + ); + if (index === -1) { + return filteredItems; + } + + const [exactMatch] = filteredItems.splice(index, 1); + filteredItems.unshift(exactMatch); + return filteredItems; + }; + public async open() { await this.updateComplete; await this._picker?.open(); diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 955071a8f2..3d20ab7a43 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -25,7 +25,10 @@ import "../ha-generic-picker"; import type { HaGenericPicker } from "../ha-generic-picker"; import "../ha-icon-button"; import "../ha-input-helper-text"; -import type { PickerComboBoxItem } from "../ha-picker-combo-box"; +import type { + PickerComboBoxItem, + PickerComboBoxSearchFn, +} from "../ha-picker-combo-box"; import type { PickerValueRenderer } from "../ha-picker-field"; import "../ha-svg-icon"; import "./state-badge"; @@ -470,6 +473,7 @@ export class HaStatisticPicker extends LitElement { .getItems=${this._getItems} .getAdditionalItems=${this._getAdditionalItems} .hideClearIcon=${this.hideClearIcon} + .searchFn=${this._searchFn} .valueRenderer=${this._valueRenderer} @value-changed=${this._valueChanged} > @@ -477,6 +481,24 @@ export class HaStatisticPicker extends LitElement { `; } + private _searchFn: PickerComboBoxSearchFn = ( + search, + filteredItems + ) => { + // If there is exact match for entity id or statistic id, put it first + const index = filteredItems.findIndex( + (item) => + item.stateObj?.entity_id === search || item.statistic_id === search + ); + if (index === -1) { + return filteredItems; + } + + const [exactMatch] = filteredItems.splice(index, 1); + filteredItems.unshift(exactMatch); + return filteredItems; + }; + private _valueChanged(ev: ValueChangedEvent) { ev.stopPropagation(); const value = ev.detail.value; diff --git a/src/components/ha-generic-picker.ts b/src/components/ha-generic-picker.ts index 687a548ede..92631333cf 100644 --- a/src/components/ha-generic-picker.ts +++ b/src/components/ha-generic-picker.ts @@ -12,6 +12,7 @@ import "./ha-picker-combo-box"; import type { HaPickerComboBox, PickerComboBoxItem, + PickerComboBoxSearchFn, } from "./ha-picker-combo-box"; import "./ha-picker-field"; import type { HaPickerField, PickerValueRenderer } from "./ha-picker-field"; @@ -57,6 +58,9 @@ export class HaGenericPicker extends LitElement { @property({ attribute: false }) public valueRenderer?: PickerValueRenderer; + @property({ attribute: false }) + public searchFn?: PickerComboBoxSearchFn; + @property({ attribute: "not-found-label", type: String }) public notFoundLabel?: string; @@ -102,6 +106,7 @@ export class HaGenericPicker extends LitElement { .notFoundLabel=${this.notFoundLabel} .getItems=${this.getItems} .getAdditionalItems=${this.getAdditionalItems} + .searchFn=${this.searchFn} > `} diff --git a/src/components/ha-picker-combo-box.ts b/src/components/ha-picker-combo-box.ts index 5280909ef8..9ea33ee20c 100644 --- a/src/components/ha-picker-combo-box.ts +++ b/src/components/ha-picker-combo-box.ts @@ -49,6 +49,12 @@ const DEFAULT_ROW_RENDERER: ComboBoxLitRenderer = ( `; +export type PickerComboBoxSearchFn = ( + search: string, + filteredItems: T[], + allItems: T[] +) => T[]; + @customElement("ha-picker-combo-box") export class HaPickerComboBox extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -84,6 +90,9 @@ export class HaPickerComboBox extends LitElement { @property({ attribute: "not-found-label", type: String }) public notFoundLabel?: string; + @property({ attribute: false }) + public searchFn?: PickerComboBoxSearchFn; + @state() private _opened = false; @query("ha-combo-box", true) public comboBox!: HaComboBox; @@ -237,6 +246,7 @@ export class HaPickerComboBox extends LitElement { const fuse = new HaFuse(this._items, { shouldSort: false }, index); const results = fuse.multiTermsSearch(searchString); + let filteredItems = this._items as PickerComboBoxItem[]; if (results) { const items = results.map((result) => result.item); if (items.length === 0) { @@ -246,10 +256,14 @@ export class HaPickerComboBox extends LitElement { } const additionalItems = this._getAdditionalItems(searchString); items.push(...additionalItems); - target.filteredItems = items; - } else { - target.filteredItems = this._items; + filteredItems = items; } + + if (this.searchFn) { + filteredItems = this.searchFn(searchString, filteredItems, this._items); + } + + target.filteredItems = filteredItems; } private _setValue(value: string | undefined) {