From bf41b3f7e3a24e8db194f174463a601008049b32 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 27 Jan 2026 09:08:54 +0000 Subject: [PATCH] Migrate country picker to generic picker (#29190) * Migrate country picker to generic picker * Add country code as secondary text * Pass required prop * Remove --- src/components/ha-country-picker.ts | 154 +++++++++++------- src/onboarding/onboarding-core-config.ts | 5 +- .../config/core/ha-config-section-general.ts | 1 - src/translations/en.json | 4 +- 4 files changed, 101 insertions(+), 63 deletions(-) diff --git a/src/components/ha-country-picker.ts b/src/components/ha-country-picker.ts index b2f4b8a6e3..9719da5415 100644 --- a/src/components/ha-country-picker.ts +++ b/src/components/ha-country-picker.ts @@ -2,11 +2,17 @@ import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; -import { stopPropagation } from "../common/dom/stop_propagation"; import { caseInsensitiveStringCompare } from "../common/string/compare"; -import "./ha-list-item"; -import "./ha-select"; -import type { HaSelect } from "./ha-select"; +import type { FrontendLocaleData } from "../data/translation"; +import type { HomeAssistant, ValueChangedEvent } from "../types"; +import "./ha-generic-picker"; +import type { PickerComboBoxItem } from "./ha-picker-combo-box"; + +const SEARCH_KEYS = [ + { name: "primary", weight: 10 }, + { name: "secondary", weight: 8 }, + { name: "search_labels.english", weight: 5 }, +]; export const COUNTRIES = [ "AD", @@ -260,9 +266,45 @@ export const COUNTRIES = [ "ZW", ]; +export const getCountryOptions = ( + countries: string[], + noSort: boolean, + locale?: FrontendLocaleData +): PickerComboBoxItem[] => { + const language = locale?.language ?? "en"; + const countryDisplayNames = new Intl.DisplayNames(language, { + type: "region", + fallback: "code", + }); + const englishDisplayNames = new Intl.DisplayNames("en", { + type: "region", + fallback: "code", + }); + + const options: PickerComboBoxItem[] = countries.map((country) => { + const primary = countryDisplayNames.of(country) ?? country; + const englishName = englishDisplayNames.of(country) ?? country; + return { + id: country, + primary, + secondary: country, + search_labels: { + english: englishName !== primary ? englishName : null, + }, + }; + }); + + if (!noSort && locale) { + options.sort((a, b) => + caseInsensitiveStringCompare(a.primary, b.primary, locale.language) + ); + } + return options; +}; + @customElement("ha-country-picker") export class HaCountryPicker extends LitElement { - @property() public language = "en"; + @property({ attribute: false }) public hass?: HomeAssistant; @property() public value?: string; @@ -278,76 +320,72 @@ export class HaCountryPicker extends LitElement { @property({ attribute: "no-sort", type: Boolean }) public noSort = false; - private _getOptions = memoizeOne( - (language?: string, countries?: string[]) => { - let options: { label: string; value: string }[] = []; - const countryDisplayNames = new Intl.DisplayNames(language, { - type: "region", - fallback: "code", - }); - if (countries) { - options = countries.map((country) => ({ - value: country, - label: countryDisplayNames - ? countryDisplayNames.of(country)! - : country, - })); - } else { - options = COUNTRIES.map((country) => ({ - value: country, - label: countryDisplayNames - ? countryDisplayNames.of(country)! - : country, - })); - } + private _getCountryOptions = memoizeOne(getCountryOptions); - if (!this.noSort) { - options.sort((a, b) => - caseInsensitiveStringCompare(a.label, b.label, language) - ); - } - return options; - } - ); + private _getItems = () => + this._getCountryOptions( + this.countries ?? COUNTRIES, + this.noSort, + this.hass?.locale + ); + + private _getCountryName = (country?: string) => + this._getItems().find((c) => c.id === country)?.primary; + + private _valueRenderer = (value: string) => + html`${this._getCountryName(value) ?? value}`; protected render() { - const options = this._getOptions(this.language, this.countries); + const label = + this.label ?? + (this.hass?.localize("ui.components.country-picker.country") || + "Country"); + + const value = + this.value ?? + (this.required && !this.disabled ? this._getItems()[0]?.id : this.value); return html` - - ${options.map( - (option) => html` - ${option.label} - ` - )} - + .getItems=${this._getItems} + .searchKeys=${SEARCH_KEYS} + @value-changed=${this._changed} + hide-clear-icon + > `; } static styles = css` - ha-select { + ha-generic-picker { width: 100%; + min-width: 200px; + display: block; } `; - private _changed(ev): void { - const target = ev.target as HaSelect; - if (target.value === "" || target.value === this.value) { - return; - } - this.value = target.value; + private _changed(ev: ValueChangedEvent): void { + ev.stopPropagation(); + this.value = ev.detail.value; fireEvent(this, "value-changed", { value: this.value }); } + + private _notFoundLabel = (search: string) => { + const term = html`'${search}'`; + return this.hass + ? this.hass.localize("ui.components.country-picker.no_match", { term }) + : html`No countries found for ${term}`; + }; } declare global { diff --git a/src/onboarding/onboarding-core-config.ts b/src/onboarding/onboarding-core-config.ts index 27fd4cf43e..bf3cae1ebf 100644 --- a/src/onboarding/onboarding-core-config.ts +++ b/src/onboarding/onboarding-core-config.ts @@ -68,7 +68,7 @@ class OnboardingCoreConfig extends LitElement { - + >