mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-09 02:49:51 +00:00
* WIP new combo box * Use new combo box for generic picker * Fix esc close and clean up * Fix empty search list item * Fix picker usages * Improve labels picker * Patch WA to make esc on popover work correctly * Apply suggestion from @piitaya Co-authored-by: Paul Bottein <paul.bottein@gmail.com> * Fix NO_MATCHING_ITEMS_FOUND_ID * Fix possible undefined boolean props --------- Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
216 lines
6.0 KiB
TypeScript
216 lines
6.0 KiB
TypeScript
import { mdiRoomService } from "@mdi/js";
|
|
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
|
import { html, LitElement, nothing, type TemplateResult } from "lit";
|
|
import { customElement, property, query } from "lit/decorators";
|
|
import memoizeOne from "memoize-one";
|
|
import { fireEvent } from "../common/dom/fire_event";
|
|
import { isValidServiceId } from "../common/entity/valid_service_id";
|
|
import type { LocalizeFunc } from "../common/translations/localize";
|
|
import { getServiceIcons } from "../data/icons";
|
|
import { domainToName } from "../data/integration";
|
|
import type { HomeAssistant, ValueChangedEvent } 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 { PickerValueRenderer } from "./ha-picker-field";
|
|
import "./ha-service-icon";
|
|
|
|
interface ServiceComboBoxItem extends PickerComboBoxItem {
|
|
domain_name?: string;
|
|
service_id?: string;
|
|
}
|
|
|
|
@customElement("ha-service-picker")
|
|
class HaServicePicker extends LitElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ type: Boolean }) public disabled = false;
|
|
|
|
@property() public label?: string;
|
|
|
|
@property() public placeholder?: string;
|
|
|
|
@property() public value?: string;
|
|
|
|
@property({ attribute: "show-service-id", type: Boolean })
|
|
public showServiceId = false;
|
|
|
|
@query("ha-generic-picker") private _picker?: HaGenericPicker;
|
|
|
|
public async open() {
|
|
await this.updateComplete;
|
|
await this._picker?.open();
|
|
}
|
|
|
|
protected firstUpdated(props) {
|
|
super.firstUpdated(props);
|
|
this.hass.loadBackendTranslation("services");
|
|
getServiceIcons(this.hass);
|
|
}
|
|
|
|
private _rowRenderer: ComboBoxLitRenderer<ServiceComboBoxItem> = (
|
|
item,
|
|
{ index }
|
|
) => html`
|
|
<ha-combo-box-item type="button" .borderTop=${index !== 0}>
|
|
<ha-service-icon
|
|
slot="start"
|
|
.hass=${this.hass}
|
|
.service=${item.id}
|
|
></ha-service-icon>
|
|
<span slot="headline">${item.primary}</span>
|
|
<span slot="supporting-text">${item.secondary}</span>
|
|
${item.service_id && this.showServiceId
|
|
? html`<span slot="supporting-text" class="code">
|
|
${item.service_id}
|
|
</span>`
|
|
: nothing}
|
|
${item.domain_name
|
|
? html`
|
|
<div slot="trailing-supporting-text" class="domain">
|
|
${item.domain_name}
|
|
</div>
|
|
`
|
|
: nothing}
|
|
</ha-combo-box-item>
|
|
`;
|
|
|
|
private _valueRenderer: PickerValueRenderer = (value) => {
|
|
const serviceId = value;
|
|
const [domain, service] = serviceId.split(".");
|
|
|
|
if (!this.hass.services[domain]?.[service]) {
|
|
return html`
|
|
<ha-svg-icon slot="start" .path=${mdiRoomService}></ha-svg-icon>
|
|
<span slot="headline">${value}</span>
|
|
`;
|
|
}
|
|
|
|
const serviceName =
|
|
this.hass.localize(`component.${domain}.services.${service}.name`) ||
|
|
this.hass.services[domain][service].name ||
|
|
service;
|
|
|
|
return html`
|
|
<ha-service-icon
|
|
slot="start"
|
|
.hass=${this.hass}
|
|
.service=${serviceId}
|
|
></ha-service-icon>
|
|
<span slot="headline">${serviceName}</span>
|
|
${this.showServiceId
|
|
? html`<span slot="supporting-text" class="code">${serviceId}</span>`
|
|
: nothing}
|
|
`;
|
|
};
|
|
|
|
protected render(): TemplateResult {
|
|
const placeholder =
|
|
this.placeholder ??
|
|
this.hass.localize("ui.components.service-picker.action");
|
|
|
|
return html`
|
|
<ha-generic-picker
|
|
.hass=${this.hass}
|
|
.autofocus=${this.autofocus}
|
|
allow-custom-value
|
|
.notFoundLabel=${this.hass.localize(
|
|
"ui.components.service-picker.no_match"
|
|
)}
|
|
.label=${this.label}
|
|
.placeholder=${placeholder}
|
|
.value=${this.value}
|
|
.getItems=${this._getItems}
|
|
.rowRenderer=${this._rowRenderer}
|
|
.valueRenderer=${this._valueRenderer}
|
|
@value-changed=${this._valueChanged}
|
|
>
|
|
</ha-generic-picker>
|
|
`;
|
|
}
|
|
|
|
private _getItems = () =>
|
|
this._services(this.hass.localize, this.hass.services);
|
|
|
|
private _services = memoizeOne(
|
|
(
|
|
localize: LocalizeFunc,
|
|
services: HomeAssistant["services"]
|
|
): ServiceComboBoxItem[] => {
|
|
if (!services) {
|
|
return [];
|
|
}
|
|
const items: ServiceComboBoxItem[] = [];
|
|
|
|
Object.keys(services)
|
|
.sort()
|
|
.forEach((domain) => {
|
|
const services_keys = Object.keys(services[domain]).sort();
|
|
|
|
for (const service of services_keys) {
|
|
const serviceId = `${domain}.${service}`;
|
|
const domainName = domainToName(localize, domain);
|
|
|
|
const name =
|
|
this.hass.localize(
|
|
`component.${domain}.services.${service}.name`
|
|
) ||
|
|
services[domain][service].name ||
|
|
service;
|
|
|
|
const description =
|
|
this.hass.localize(
|
|
`component.${domain}.services.${service}.description`
|
|
) ||
|
|
services[domain][service].description ||
|
|
"";
|
|
|
|
items.push({
|
|
id: serviceId,
|
|
primary: name,
|
|
secondary: description,
|
|
domain_name: domainName,
|
|
service_id: serviceId,
|
|
search_labels: [serviceId, domainName, name, description].filter(
|
|
Boolean
|
|
),
|
|
sorting_label: serviceId,
|
|
});
|
|
}
|
|
});
|
|
|
|
return items;
|
|
}
|
|
);
|
|
|
|
private _valueChanged(ev: ValueChangedEvent<string>) {
|
|
ev.stopPropagation();
|
|
const value = ev.detail.value;
|
|
|
|
if (!value) {
|
|
this._setValue(undefined);
|
|
return;
|
|
}
|
|
|
|
if (!isValidServiceId(value)) {
|
|
return;
|
|
}
|
|
|
|
this._setValue(value);
|
|
}
|
|
|
|
private _setValue(value: string | undefined) {
|
|
this.value = value;
|
|
|
|
fireEvent(this, "value-changed", { value });
|
|
fireEvent(this, "change");
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-service-picker": HaServicePicker;
|
|
}
|
|
}
|