1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 00:27:49 +01:00

migrate ha-select to ha-dropdown (#29392)

* migrate ha-select to ha-dropdown

* remove ha-menu

* review

* Fix eslint error

---------

Co-authored-by: Aidan Timson <aidan@timmo.dev>
This commit is contained in:
Wendelin
2026-02-04 14:47:15 +01:00
committed by GitHub
parent 917f2b4434
commit aec4a06156
64 changed files with 1339 additions and 1602 deletions

View File

@@ -100,7 +100,6 @@ class HaLandingPage extends LandingPageBaseElement {
button-style
native-name
@value-changed=${this._languageChanged}
inline-arrow
></ha-language-picker>
<ha-button
appearance="plain"

View File

@@ -71,7 +71,6 @@
"@material/mwc-icon-button": "0.27.0",
"@material/mwc-linear-progress": "0.27.0",
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"@material/mwc-menu": "0.27.0",
"@material/mwc-radio": "0.27.0",
"@material/mwc-select": "0.27.0",
"@material/mwc-snackbar": "0.27.0",

View File

@@ -194,7 +194,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
button-style
native-name
@value-changed=${this._languageChanged}
inline-arrow
></ha-language-picker>
<ha-button
appearance="plain"

View File

@@ -2,14 +2,12 @@ import type { PropertyValueMap } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { formatLanguageCode } from "../common/language/format_language";
import type { AssistPipeline } from "../data/assist_pipeline";
import { listAssistPipelines } from "../data/assist_pipeline";
import type { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import type { HaSelectOption } from "./ha-select";
const PREFERRED = "preferred";
const LAST_USED = "last_used";
@@ -41,6 +39,31 @@ export class HaAssistPipelinePicker extends LitElement {
return nothing;
}
const value = this.value ?? this._default;
const options: HaSelectOption[] = [
{
value: PREFERRED,
label: this.hass.localize("ui.components.pipeline-picker.preferred", {
preferred: this._pipelines.find(
(pipeline) => pipeline.id === this._preferredPipeline
)?.name,
}),
},
];
if (this.includeLastUsed) {
options.unshift({
value: LAST_USED,
label: this.hass.localize("ui.components.pipeline-picker.last_used"),
});
}
options.push(
...this._pipelines.map((pipeline) => ({
value: pipeline.id,
label: `${pipeline.name} (${formatLanguageCode(pipeline.language, this.hass.locale)})`,
}))
);
return html`
<ha-select
.label=${this.label ||
@@ -49,33 +72,8 @@ export class HaAssistPipelinePicker extends LitElement {
.required=${this.required}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${options}
>
${this.includeLastUsed
? html`
<ha-list-item .value=${LAST_USED}>
${this.hass!.localize(
"ui.components.pipeline-picker.last_used"
)}
</ha-list-item>
`
: null}
<ha-list-item .value=${PREFERRED}>
${this.hass!.localize("ui.components.pipeline-picker.preferred", {
preferred: this._pipelines.find(
(pipeline) => pipeline.id === this._preferredPipeline
)?.name,
})}
</ha-list-item>
${this._pipelines.map(
(pipeline) =>
html`<ha-list-item .value=${pipeline.id}>
${pipeline.name}
(${formatLanguageCode(pipeline.language, this.hass.locale)})
</ha-list-item>`
)}
</ha-select>
`;
}
@@ -96,17 +94,17 @@ export class HaAssistPipelinePicker extends LitElement {
}
`;
private _changed(ev): void {
const target = ev.target as HaSelect;
private _changed(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (
!this.hass ||
target.value === "" ||
target.value === this.value ||
(this.value === undefined && target.value === this._default)
value === "" ||
value === this.value ||
(this.value === undefined && value === this._default)
) {
return;
}
this.value = target.value === this._default ? undefined : target.value;
this.value = value === this._default ? undefined : value;
fireEvent(this, "value-changed", { value: this.value });
}
}

View File

@@ -4,10 +4,8 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-list-item";
import "./ha-select";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@@ -260,14 +258,10 @@ export class HaBaseTimeInput extends LitElement {
.required=${this.required}
.value=${this.amPm}
.disabled=${this.disabled}
name="amPm"
naturalMenuWidth
fixedMenuPosition
.name=${"amPm"}
@selected=${this._valueChanged}
@closed=${stopPropagation}
.options=${["AM", "PM"]}
>
<ha-list-item value="AM">AM</ha-list-item>
<ha-list-item value="PM">PM</ha-list-item>
</ha-select>`}
</div>
${this.helper
@@ -282,10 +276,12 @@ export class HaBaseTimeInput extends LitElement {
fireEvent(this, "value-changed");
}
private _valueChanged(ev: InputEvent) {
private _valueChanged(ev: InputEvent | CustomEvent<{ value: string }>): void {
const textField = ev.currentTarget as HaTextField;
this[textField.name] =
textField.name === "amPm" ? textField.value : Number(textField.value);
textField.name === "amPm"
? (ev as CustomEvent<{ value: string }>).detail.value
: Number(textField.value);
const value: TimeChangedEvent = {
hours: this.hours,
minutes: this.minutes,
@@ -366,10 +362,6 @@ export class HaBaseTimeInput extends LitElement {
ha-textfield:last-child {
--text-field-border-top-right-radius: var(--mdc-shape-medium);
}
ha-select {
--mdc-shape-small: 0;
width: 85px;
}
:host([clearable]) .mdc-select__anchor {
padding-inline-end: var(--select-selected-text-padding-end, 12px);
}

View File

@@ -2,12 +2,10 @@ import { css, html, LitElement, nothing } 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 { stringCompare } from "../common/string/compare";
import type { Blueprint, BlueprintDomain, Blueprints } from "../data/blueprint";
import { fetchBlueprints } from "../data/blueprint";
import type { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
@customElement("ha-blueprint-picker")
@@ -55,20 +53,16 @@ class HaBluePrintPicker extends LitElement {
<ha-select
.label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.select_blueprint")}
fixedMenuPosition
naturalMenuWidth
.value=${this.value}
.disabled=${this.disabled}
@selected=${this._blueprintChanged}
@closed=${stopPropagation}
>
${this._processedBlueprints(this.blueprints).map(
(blueprint) => html`
<ha-list-item .value=${blueprint.path}>
${blueprint.name}
</ha-list-item>
`
.options=${this._processedBlueprints(this.blueprints).map(
(blueprint) => ({
value: blueprint.path,
label: blueprint.name,
})
)}
>
</ha-select>
`;
}
@@ -82,8 +76,8 @@ class HaBluePrintPicker extends LitElement {
}
}
private _blueprintChanged(ev) {
const newValue = ev.target.value;
private _blueprintChanged(ev: CustomEvent<{ value: string }>) {
const newValue = ev.detail.value;
if (newValue !== this.value) {
this.value = newValue;

View File

@@ -3,7 +3,6 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { debounce } from "../common/util/debounce";
import type { ConfigEntry, SubEntry } from "../data/config_entries";
import { getConfigEntry, getSubEntries } from "../data/config_entries";
@@ -14,9 +13,8 @@ import { fetchIntegrationManifest } from "../data/integration";
import { showOptionsFlowDialog } from "../dialogs/config-flow/show-dialog-options-flow";
import { showSubConfigFlowDialog } from "../dialogs/config-flow/show-dialog-sub-config-flow";
import type { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import type { HaSelectOption } from "./ha-select";
const NONE = "__NONE_OPTION__";
@@ -73,6 +71,23 @@ export class HaConversationAgentPicker extends LitElement {
value = NONE;
}
const options: HaSelectOption[] = this._agents.map((agent) => ({
value: agent.id,
label: agent.name,
disabled:
agent.supported_languages !== "*" &&
agent.supported_languages.length === 0,
}));
if (!this.required) {
options.unshift({
value: NONE,
label: this.hass.localize(
"ui.components.conversation-agent-picker.none"
),
});
}
return html`
<ha-select
.label=${this.label ||
@@ -83,27 +98,8 @@ export class HaConversationAgentPicker extends LitElement {
.required=${this.required}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${!this.required
? html`<ha-list-item .value=${NONE}>
${this.hass!.localize(
"ui.components.conversation-agent-picker.none"
)}
</ha-list-item>`
: nothing}
${this._agents.map(
(agent) =>
html`<ha-list-item
.value=${agent.id}
.disabled=${agent.supported_languages !== "*" &&
agent.supported_languages.length === 0}
>
${agent.name}
</ha-list-item>`
)}</ha-select
.options=${options}
></ha-select
>${(this._subConfigEntry &&
this._configEntry?.supported_subentry_types[
this._subConfigEntry.subentry_type
@@ -238,17 +234,17 @@ export class HaConversationAgentPicker extends LitElement {
}
`;
private _changed(ev): void {
const target = ev.target as HaSelect;
private _changed(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (
!this.hass ||
target.value === "" ||
target.value === this.value ||
(this.value === undefined && target.value === NONE)
value === "" ||
value === this.value ||
(this.value === undefined && value === NONE)
) {
return;
}
this.value = target.value === NONE ? undefined : target.value;
this.value = value === NONE ? undefined : value;
fireEvent(this, "value-changed", { value: this.value });
fireEvent(this, "supported-languages-changed", {
value: this._agents!.find((agent) => agent.id === this.value)

View File

@@ -107,9 +107,6 @@ export class HaLanguagePicker extends LitElement {
@property({ attribute: "no-sort", type: Boolean }) public noSort = false;
@property({ attribute: "inline-arrow", type: Boolean })
public inlineArrow = false;
@state() _defaultLanguages: string[] = [];
@query("ha-generic-picker", true) public genericPicker!: HaGenericPicker;

View File

@@ -1,45 +0,0 @@
import { MenuBase } from "@material/mwc-menu/mwc-menu-base";
import { styles } from "@material/mwc-menu/mwc-menu.css";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import "./ha-list";
@customElement("ha-menu")
export class HaMenu extends MenuBase {
protected get listElement() {
if (!this.listElement_) {
this.listElement_ = this.renderRoot.querySelector("ha-list");
return this.listElement_;
}
return this.listElement_;
}
protected renderList() {
const itemRoles = this.innerRole === "menu" ? "menuitem" : "option";
const classes = this.renderListClasses();
return html`<ha-list
rootTabbable
.innerAriaLabel=${this.innerAriaLabel}
.innerRole=${this.innerRole}
.multi=${this.multi}
class=${classMap(classes)}
.itemRoles=${itemRoles}
.wrapFocus=${this.wrapFocus}
.activatable=${this.activatable}
@action=${this.onAction}
>
<slot></slot>
</ha-list>`;
}
static styles = styles;
}
declare global {
interface HTMLElementTagNameMap {
"ha-menu": HaMenu;
}
}

View File

@@ -5,7 +5,6 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import type { SupervisorMounts } from "../data/supervisor/mounts";
import {
@@ -17,7 +16,7 @@ import type { HomeAssistant } from "../types";
import "./ha-alert";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import type { HaSelectOption } from "./ha-select";
const _BACKUP_DATA_DISK_ = "/backup";
@@ -52,60 +51,54 @@ class HaMountPicker extends LitElement {
if (!this._mounts) {
return nothing;
}
const dataDiskOption = html`<ha-list-item
graphic="icon"
.value=${_BACKUP_DATA_DISK_}
>
<span>
${this.hass.localize("ui.components.mount-picker.use_datadisk") ||
"Use data disk for backup"}
</span>
<ha-svg-icon slot="graphic" .path=${mdiHarddisk}></ha-svg-icon>
</ha-list-item>`;
const options: HaSelectOption[] = this._filterMounts(
this._mounts,
this.usage
).map((mount) => ({
value: mount.name,
label: mount.name,
secondary: `${mount.server}${mount.port ? `:${mount.port}` : ""}${
mount.type === SupervisorMountType.NFS ? mount.path : `:${mount.share}`
}`,
iconPath:
mount.usage === SupervisorMountUsage.MEDIA
? mdiPlayBox
: mount.usage === SupervisorMountUsage.SHARE
? mdiFolder
: mdiBackupRestore,
}));
if (this.usage === SupervisorMountUsage.BACKUP) {
const dataDiskOption = {
value: _BACKUP_DATA_DISK_,
iconPath: mdiHarddisk,
label:
this.hass.localize("ui.components.mount-picker.use_datadisk") ||
"Use data disk for backup",
};
if (
!this._mounts.default_backup_mount ||
this._mounts.default_backup_mount === _BACKUP_DATA_DISK_
) {
options.unshift(dataDiskOption);
} else {
options.push(dataDiskOption);
}
}
return html`
<ha-select
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.mount-picker.mount")
: this.label}
.value=${this._value}
.value=${this.value}
.required=${this.required}
.disabled=${this.disabled}
.helper=${this.helper}
@selected=${this._mountChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${options}
>
${this.usage === SupervisorMountUsage.BACKUP &&
(!this._mounts.default_backup_mount ||
this._mounts.default_backup_mount === _BACKUP_DATA_DISK_)
? dataDiskOption
: nothing}
${this._filterMounts(this._mounts, this.usage).map(
(mount) =>
html`<ha-list-item twoline graphic="icon" .value=${mount.name}>
<span>${mount.name}</span>
<span slot="secondary"
>${mount.server}${mount.port
? `:${mount.port}`
: nothing}${mount.type === SupervisorMountType.NFS
? mount.path
: `:${mount.share}`}</span
>
<ha-svg-icon
slot="graphic"
.path=${mount.usage === SupervisorMountUsage.MEDIA
? mdiPlayBox
: mount.usage === SupervisorMountUsage.SHARE
? mdiFolder
: mdiBackupRestore}
></ha-svg-icon>
</ha-list-item>`
)}
${this.usage === SupervisorMountUsage.BACKUP &&
this._mounts.default_backup_mount
? dataDiskOption
: nothing}
</ha-select>
`;
}
@@ -153,16 +146,10 @@ class HaMountPicker extends LitElement {
}
}
private get _value() {
return this.value || "";
}
private _mountChanged(ev: CustomEvent<{ value: string }>) {
const newValue = ev.detail.value;
private _mountChanged(ev: Event) {
ev.stopPropagation();
const target = ev.target as HaSelect;
const newValue = target.value;
if (newValue !== this._value) {
if (newValue !== this.value) {
this._setValue(newValue);
}
}

View File

@@ -1,187 +1,209 @@
import { SelectBase } from "@material/mwc-select/mwc-select-base";
import { styles } from "@material/mwc-select/mwc-select.css";
import { mdiClose } from "@mdi/js";
import { css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { debounce } from "../common/util/debounce";
import { nextRender } from "../common/util/render-status";
import "./ha-icon-button";
import "./ha-menu";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-dropdown";
import "./ha-dropdown-item";
import "./ha-picker-field";
import type { HaPickerField } from "./ha-picker-field";
import "./ha-svg-icon";
export interface HaSelectOption {
value: string;
label?: string;
secondary?: string;
iconPath?: string;
disabled?: boolean;
}
@customElement("ha-select")
export class HaSelect extends SelectBase {
// @ts-ignore
@property({ type: Boolean }) public icon = false;
export class HaSelect extends LitElement {
@property({ type: Boolean }) public clearable = false;
@property({ type: Boolean, reflect: true }) public clearable = false;
@property({ attribute: false }) public options?: HaSelectOption[] | string[];
@property({ attribute: "inline-arrow", type: Boolean })
public inlineArrow = false;
@property() public label?: string;
@property() public options;
@property() public helper?: string;
@property() public value?: string;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean }) public disabled = false;
@state() private _opened = false;
@query("ha-picker-field") private _triggerField!: HaPickerField;
private _getValueLabel = memoizeOne(
(
options: HaSelectOption[] | string[] | undefined,
value: string | undefined
) => {
if (!options || !value) {
return value;
}
for (const option of options) {
if (
(typeof option === "string" && option === value) ||
(typeof option !== "string" && option.value === value)
) {
return typeof option === "string"
? option
: option.label || option.value;
}
}
return value;
}
);
protected override render() {
if (this.disabled) {
return this._renderField();
}
return html`
${super.render()}
${this.clearable && !this.required && !this.disabled && this.value
? html`<ha-icon-button
label="clear"
@click=${this._clearValue}
.path=${mdiClose}
></ha-icon-button>`
: nothing}
<ha-dropdown
placement="bottom"
@wa-select=${this._handleSelect}
@wa-show=${this._handleShow}
@wa-hide=${this._handleHide}
>
${this._renderField()}
${this.options
? this.options.map(
(option) => html`
<ha-dropdown-item
.value=${typeof option === "string" ? option : option.value}
.disabled=${typeof option === "string"
? false
: (option.disabled ?? false)}
class=${this.value ===
(typeof option === "string" ? option : option.value)
? "selected"
: ""}
>
${option.iconPath
? html`<ha-svg-icon
slot="icon"
.path=${option.iconPath}
></ha-svg-icon>`
: nothing}
<div class="content">
${typeof option === "string"
? option
: option.label || option.value}
${option.secondary
? html`<div class="secondary">${option.secondary}</div>`
: nothing}
</div>
</ha-dropdown-item>
`
)
: html`<slot></slot>`}
</ha-dropdown>
`;
}
protected override renderMenu() {
const classes = this.getMenuClasses();
return html`<ha-menu
innerRole="listbox"
wrapFocus
class=${classMap(classes)}
activatable
.fullwidth=${this.fixedMenuPosition ? false : !this.naturalMenuWidth}
.open=${this.menuOpen}
.anchor=${this.anchorElement}
.fixed=${this.fixedMenuPosition}
@selected=${this.onSelected}
@opened=${this.onOpened}
@closed=${this.onClosed}
@items-updated=${this.onItemsUpdated}
@keydown=${this.handleTypeahead}
>
${this.renderMenuContent()}
</ha-menu>`;
private _renderField() {
const valueLabel = this._getValueLabel(this.options, this.value);
return html`
<ha-picker-field
slot="trigger"
type="button"
class=${this._opened ? "opened" : ""}
compact
aria-label=${ifDefined(this.label)}
@clear=${this._clearValue}
.label=${this.label}
.helper=${this.helper}
.value=${valueLabel}
.required=${this.required}
.disabled=${this.disabled}
.hideClearIcon=${!this.clearable ||
this.required ||
this.disabled ||
!this.value}
>
</ha-picker-field>
`;
}
protected override renderLeadingIcon() {
if (!this.icon) {
return nothing;
private _handleSelect(ev: CustomEvent<{ item: { value: string } }>) {
ev.stopPropagation();
const value = ev.detail.item.value;
if (value === this.value) {
return;
}
return html`<span class="mdc-select__icon"
><slot name="icon"></slot
></span>`;
}
connectedCallback() {
super.connectedCallback();
window.addEventListener("translations-updated", this._translationsUpdated);
}
protected async firstUpdated() {
super.firstUpdated();
if (this.inlineArrow) {
this.shadowRoot
?.querySelector(".mdc-select__selected-text-container")
?.classList.add("inline-arrow");
}
}
protected updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has("inlineArrow")) {
const textContainerElement = this.shadowRoot?.querySelector(
".mdc-select__selected-text-container"
);
if (this.inlineArrow) {
textContainerElement?.classList.add("inline-arrow");
} else {
textContainerElement?.classList.remove("inline-arrow");
}
}
if (changedProperties.get("options")) {
this.layoutOptions();
this.selectByValue(this.value);
}
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener(
"translations-updated",
this._translationsUpdated
);
fireEvent(this, "selected", { value });
}
private _clearValue(): void {
if (this.disabled || !this.value) {
return;
}
this.valueSetDirectly = true;
this.select(-1);
this.mdcFoundation.handleChange();
fireEvent(this, "selected", { value: undefined });
}
private _translationsUpdated = debounce(async () => {
await nextRender();
this.layoutOptions();
}, 500);
private _handleShow() {
this.style.setProperty(
"--select-menu-width",
`${this._triggerField.offsetWidth}px`
);
this._opened = true;
}
static override styles = [
styles,
css`
:host([clearable]) {
position: relative;
}
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
color: var(--secondary-text-color);
}
.mdc-select__anchor {
width: var(--ha-select-min-width, 200px);
}
.mdc-select--filled .mdc-select__anchor {
height: var(--ha-select-height, 56px);
}
.mdc-select--filled .mdc-floating-label {
inset-inline-start: var(--ha-space-4);
inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label {
inset-inline-start: 48px;
inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select .mdc-select__anchor {
padding-inline-start: var(--ha-space-4);
padding-inline-end: 0px;
direction: var(--direction);
}
.mdc-select__anchor .mdc-floating-label--float-above {
transform-origin: var(--float-start);
}
.mdc-select__selected-text-container {
padding-inline-end: var(--select-selected-text-padding-end, 0px);
}
:host([clearable]) .mdc-select__selected-text-container {
padding-inline-end: var(
--select-selected-text-padding-end,
var(--ha-space-4)
);
}
ha-icon-button {
position: absolute;
top: 10px;
right: 28px;
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
inset-inline-start: initial;
inset-inline-end: 28px;
direction: var(--direction);
}
.inline-arrow {
flex-grow: 0;
}
`,
];
private _handleHide() {
this._opened = false;
}
static styles = css`
:host {
position: relative;
}
ha-picker-field.opened {
--mdc-text-field-idle-line-color: var(--primary-color);
}
ha-dropdown-item.selected:hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
ha-dropdown-item .content {
display: flex;
gap: var(--ha-space-1);
flex-direction: column;
}
ha-dropdown-item .secondary {
font-size: var(--ha-font-size-s);
color: var(--ha-color-text-secondary);
}
ha-dropdown::part(menu) {
min-width: var(--select-menu-width);
}
:host ::slotted(ha-dropdown-item.selected),
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-medium);
color: var(--primary-color);
background-color: var(--ha-color-fill-primary-quiet-resting);
--icon-primary-color: var(--primary-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-select": HaSelect;
}
interface HASSDomEvents {
selected: { value: string | undefined };
}
}

View File

@@ -5,17 +5,16 @@ import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import { fireEvent } from "../../common/dom/fire_event";
import { stopPropagation } from "../../common/dom/stop_propagation";
import { caseInsensitiveStringCompare } from "../../common/string/compare";
import type { SelectOption, SelectSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../chips/ha-chip-set";
import "../chips/ha-input-chip";
import "../ha-checkbox";
import "../ha-dropdown-item";
import "../ha-formfield";
import "../ha-generic-picker";
import "../ha-input-helper-text";
import "../ha-list-item";
import "../ha-radio";
import "../ha-select";
import "../ha-select-box";
@@ -231,24 +230,15 @@ export class HaSelectSelector extends LitElement {
return html`
<ha-select
fixedMenuPosition
naturalMenuWidth
.label=${this.label ?? ""}
.value=${this.value ?? ""}
.value=${(this.value as string) ?? ""}
.helper=${this.helper ?? ""}
.disabled=${this.disabled}
.required=${this.required}
clearable
@closed=${stopPropagation}
@selected=${this._valueChanged}
.options=${options}
>
${options.map(
(item: SelectOption) => html`
<ha-list-item .value=${item.value} .disabled=${!!item.disabled}
>${item.label}</ha-list-item
>
`
)}
</ha-select>
`;
}
@@ -295,7 +285,7 @@ export class HaSelectSelector extends LitElement {
private _valueChanged(ev) {
ev.stopPropagation();
if (ev.detail?.index === -1 && this.value !== undefined) {
if (ev.detail?.value === undefined && this.value !== undefined) {
fireEvent(this, "value-changed", {
value: undefined,
});
@@ -385,7 +375,7 @@ export class HaSelectSelector extends LitElement {
ha-formfield {
display: block;
}
ha-list-item[disabled] {
ha-dropdown-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
ha-chip-set {

View File

@@ -108,6 +108,7 @@ export class HaSettingsRow extends LitElement {
white-space: normal;
}
.prefix-wrap {
flex: 1;
display: var(--settings-row-prefix-display);
}
:host([narrow]) .prefix-wrap {

View File

@@ -2,16 +2,14 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { debounce } from "../common/util/debounce";
import type { STTEngine } from "../data/stt";
import { listSTTEngines } from "../data/stt";
import type { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import { computeDomain } from "../common/entity/compute_domain";
import type { HaSelectOption } from "./ha-select";
const NONE = "__NONE_OPTION__";
@@ -61,6 +59,30 @@ export class HaSTTPicker extends LitElement {
value = NONE;
}
const options: HaSelectOption[] = this._engines
.filter((engine) => !engine.deprecated || engine.engine_id !== value)
.map((engine) => {
let label: string;
if (engine.engine_id.includes(".")) {
const stateObj = this.hass.states[engine.engine_id];
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
} else {
label = engine.name || engine.engine_id;
}
return {
value: engine.engine_id,
label,
disabled: engine.supported_languages?.length === 0,
};
});
if (this.required || value === NONE) {
options.unshift({
value: NONE,
label: this.hass.localize("ui.components.stt-picker.none") || "None",
});
}
return html`
<ha-select
.label=${this.label ||
@@ -69,33 +91,8 @@ export class HaSTTPicker extends LitElement {
.required=${this.required}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${options}
>
${!this.required
? html`<ha-list-item .value=${NONE}>
${this.hass!.localize("ui.components.stt-picker.none")}
</ha-list-item>`
: nothing}
${this._engines.map((engine) => {
if (engine.deprecated && engine.engine_id !== value) {
return nothing;
}
let label: string;
if (engine.engine_id.includes(".")) {
const stateObj = this.hass!.states[engine.engine_id];
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
} else {
label = engine.name || engine.engine_id;
}
return html`<ha-list-item
.value=${engine.engine_id}
.disabled=${engine.supported_languages?.length === 0}
>
${label}
</ha-list-item>`;
})}
</ha-select>
`;
}
@@ -144,17 +141,17 @@ export class HaSTTPicker extends LitElement {
}
`;
private _changed(ev): void {
const target = ev.target as HaSelect;
private _changed(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (
!this.hass ||
target.value === "" ||
target.value === this.value ||
(this.value === undefined && target.value === NONE)
value === "" ||
value === this.value ||
(this.value === undefined && value === NONE)
) {
return;
}
this.value = target.value === NONE ? undefined : target.value;
this.value = value === NONE ? undefined : value;
fireEvent(this, "value-changed", { value: this.value });
fireEvent(this, "supported-languages-changed", {
value: this._engines!.find((engine) => engine.engine_id === this.value)

View File

@@ -1,11 +1,10 @@
import type { TemplateResult } from "lit";
import { css, html, nothing, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import type { HomeAssistant } from "../types";
import "./ha-select";
import "./ha-list-item";
import type { HaSelectOption } from "./ha-select";
const DEFAULT_THEME = "default";
@@ -25,6 +24,26 @@ export class HaThemePicker extends LitElement {
@property({ type: Boolean }) public required = false;
protected render(): TemplateResult {
const options: HaSelectOption[] = Object.keys(
this.hass?.themes.themes || {}
).map((theme) => ({
value: theme,
}));
if (this.includeDefault) {
options.unshift({
value: DEFAULT_THEME,
label: "Home Assistant",
});
}
if (!this.required) {
options.unshift({
value: "remove",
label: this.hass!.localize("ui.components.theme-picker.no_theme"),
});
}
return html`
<ha-select
.label=${this.label ||
@@ -33,31 +52,8 @@ export class HaThemePicker extends LitElement {
.required=${this.required}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${!this.required
? html`
<ha-list-item value="remove">
${this.hass!.localize("ui.components.theme-picker.no_theme")}
</ha-list-item>
`
: nothing}
${this.includeDefault
? html`
<ha-list-item .value=${DEFAULT_THEME}>
Home Assistant
</ha-list-item>
`
: nothing}
${Object.keys(this.hass!.themes.themes)
.sort()
.map(
(theme) =>
html`<ha-list-item .value=${theme}>${theme}</ha-list-item>`
)}
</ha-select>
.options=${options}
></ha-select>
`;
}
@@ -67,11 +63,11 @@ export class HaThemePicker extends LitElement {
}
`;
private _changed(ev): void {
if (!this.hass || ev.target.value === "") {
private _changed(ev: CustomEvent<{ value: string }>): void {
if (!this.hass || ev.detail.value === "") {
return;
}
this.value = ev.target.value === "remove" ? undefined : ev.target.value;
this.value = ev.detail.value === "remove" ? undefined : ev.detail.value;
fireEvent(this, "value-changed", { value: this.value });
}
}

View File

@@ -2,16 +2,14 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { debounce } from "../common/util/debounce";
import type { TTSEngine } from "../data/tts";
import { listTTSEngines } from "../data/tts";
import type { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import { computeDomain } from "../common/entity/compute_domain";
import type { HaSelectOption } from "./ha-select";
const NONE = "__NONE_OPTION__";
@@ -61,6 +59,30 @@ export class HaTTSPicker extends LitElement {
value = NONE;
}
const options: HaSelectOption[] = this._engines
.filter((engine) => !engine.deprecated || engine.engine_id === value)
.map((engine) => {
let label: string;
if (engine.engine_id.includes(".")) {
const stateObj = this.hass.states[engine.engine_id];
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
} else {
label = engine.name || engine.engine_id;
}
return {
value: engine.engine_id,
label,
disabled: engine.supported_languages?.length === 0,
};
});
if (!this.required || value === NONE) {
options.unshift({
value: NONE,
label: this.hass.localize("ui.components.tts-picker.none"),
});
}
return html`
<ha-select
.label=${this.label ||
@@ -69,33 +91,8 @@ export class HaTTSPicker extends LitElement {
.required=${this.required}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${options}
>
${!this.required
? html`<ha-list-item .value=${NONE}>
${this.hass!.localize("ui.components.tts-picker.none")}
</ha-list-item>`
: nothing}
${this._engines.map((engine) => {
if (engine.deprecated && engine.engine_id !== value) {
return nothing;
}
let label: string;
if (engine.engine_id.includes(".")) {
const stateObj = this.hass!.states[engine.engine_id];
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
} else {
label = engine.name || engine.engine_id;
}
return html`<ha-list-item
.value=${engine.engine_id}
.disabled=${engine.supported_languages?.length === 0}
>
${label}
</ha-list-item>`;
})}
</ha-select>
`;
}
@@ -144,17 +141,17 @@ export class HaTTSPicker extends LitElement {
}
`;
private _changed(ev): void {
const target = ev.target as HaSelect;
private _changed(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (
!this.hass ||
target.value === "" ||
target.value === this.value ||
(this.value === undefined && target.value === NONE)
value === "" ||
value === this.value ||
(this.value === undefined && value === NONE)
) {
return;
}
this.value = target.value === NONE ? undefined : target.value;
this.value = value === NONE ? undefined : value;
fireEvent(this, "value-changed", { value: this.value });
fireEvent(this, "supported-languages-changed", {
value: this._engines!.find((engine) => engine.engine_id === this.value)

View File

@@ -1,15 +1,13 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { debounce } from "../common/util/debounce";
import type { TTSVoice } from "../data/tts";
import { listTTSVoices } from "../data/tts";
import type { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import type { HaSelectOption } from "./ha-select";
const NONE = "__NONE_OPTION__";
@@ -31,14 +29,25 @@ export class HaTTSVoicePicker extends LitElement {
@state() _voices?: TTSVoice[] | null;
@query("ha-select") private _select?: HaSelect;
protected render() {
if (!this._voices) {
return nothing;
}
const value =
this.value ?? (this.required ? this._voices[0]?.voice_id : NONE);
const options: HaSelectOption[] = (this._voices || []).map((voice) => ({
value: voice.voice_id,
label: voice.name,
}));
if (!this.required || !this.value) {
options.unshift({
value: NONE,
label: this.hass!.localize("ui.components.tts-voice-picker.none"),
});
}
return html`
<ha-select
.label=${this.label ||
@@ -47,21 +56,8 @@ export class HaTTSVoicePicker extends LitElement {
.required=${this.required}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${options}
>
${!this.required
? html`<ha-list-item .value=${NONE}>
${this.hass!.localize("ui.components.tts-voice-picker.none")}
</ha-list-item>`
: nothing}
${this._voices.map(
(voice) =>
html`<ha-list-item .value=${voice.voice_id}>
${voice.name}
</ha-list-item>`
)}
</ha-select>
`;
}
@@ -102,34 +98,25 @@ export class HaTTSVoicePicker extends LitElement {
}
}
protected updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
if (
changedProperties.has("_voices") &&
this._select?.value !== this.value
) {
this._select?.layoutOptions();
fireEvent(this, "value-changed", { value: this._select?.value });
}
}
static styles = css`
ha-select {
width: 100%;
text-align: start;
display: block;
}
`;
private _changed(ev): void {
const target = ev.target as HaSelect;
private _changed(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (
!this.hass ||
target.value === "" ||
target.value === this.value ||
(this.value === undefined && target.value === NONE)
value === "" ||
value === this.value ||
(this.value === undefined && value === NONE)
) {
return;
}
this.value = target.value === NONE ? undefined : target.value;
this.value = value === NONE ? undefined : value;
fireEvent(this, "value-changed", { value: this.value });
}
}

View File

@@ -13,7 +13,6 @@ import "../../../components/ha-cover-controls";
import "../../../components/ha-cover-tilt-controls";
import "../../../components/ha-date-input";
import "../../../components/ha-humidifier-state";
import "../../../components/ha-list-item";
import "../../../components/ha-select";
import "../../../components/ha-slider";
import "../../../components/ha-time-input";
@@ -296,17 +295,11 @@ class EntityPreviewRow extends LitElement {
.label=${computeStateName(stateObj)}
.value=${stateObj.state}
.disabled=${isUnavailableState(stateObj.state)}
naturalMenuWidth
.options=${stateObj.attributes.options?.map((option) => ({
value: option,
label: this.hass!.formatEntityState(stateObj, option),
})) || []}
>
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) => html`
<ha-list-item .value=${option}>
${this.hass!.formatEntityState(stateObj, option)}
</ha-list-item>
`
)
: ""}
</ha-select>
`;
}

View File

@@ -4,16 +4,15 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import "../../../../components/ha-button";
import "../../../../components/ha-control-button";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-list-item";
import "../../../../components/ha-wa-dialog";
import "../../../../components/ha-select";
import "../../../../components/ha-textfield";
import "../../../../components/ha-wa-dialog";
import { SirenEntityFeature } from "../../../../data/siren";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
@@ -77,24 +76,19 @@ class MoreInfoSirenAdvancedControls extends LitElement {
? html`
<ha-select
.label=${this.hass.localize("ui.components.siren.tone")}
@closed=${stopPropagation}
@change=${this._handleToneChange}
@selected=${this._handleToneChange}
.value=${this._tone}
.options=${Object.entries(
this._stateObj!.attributes.available_tones
).map(([toneId, toneName]) => ({
value: Array.isArray(
this._stateObj!.attributes.available_tones
)
? toneName
: toneId,
label: toneName,
}))}
>
${Object.entries(
this._stateObj.attributes.available_tones
).map(
([toneId, toneName]) => html`
<ha-list-item
.value=${Array.isArray(
this._stateObj!.attributes.available_tones
)
? toneName
: toneId}
>${toneName}</ha-list-item
>
`
)}
</ha-select>
`
: nothing}
@@ -152,8 +146,8 @@ class MoreInfoSirenAdvancedControls extends LitElement {
`;
}
private _handleToneChange(ev) {
this._tone = ev.target.value;
private _handleToneChange(ev: CustomEvent<{ value: string }>) {
this._tone = ev.detail.value;
}
private _handleVolumeChange(ev) {

View File

@@ -1,12 +1,10 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-select";
import type { RemoteEntity } from "../../../data/remote";
import { REMOTE_SUPPORT_ACTIVITY } from "../../../data/remote";
import type { HomeAssistant } from "../../../types";
import "../../../components/ha-select";
import "../../../components/ha-list-item";
@customElement("more-info-remote")
class MoreInfoRemote extends LitElement {
@@ -30,30 +28,24 @@ class MoreInfoRemote extends LitElement {
)}
.value=${stateObj.attributes.current_activity || ""}
@selected=${this._handleActivityChanged}
fixedMenuPosition
naturalMenuWidth
@closed=${stopPropagation}
.options=${stateObj.attributes.activity_list?.map((activity) => ({
value: activity,
label: this.hass!.formatEntityAttributeValue(
stateObj,
"activity",
activity
),
}))}
>
${stateObj.attributes.activity_list?.map(
(activity) => html`
<ha-list-item .value=${activity}>
${this.hass.formatEntityAttributeValue(
stateObj,
"activity",
activity
)}
</ha-list-item>
`
)}
</ha-select>
`
: nothing}
`;
}
private _handleActivityChanged(ev) {
private _handleActivityChanged(ev: CustomEvent<{ value: string }>) {
const oldVal = this.stateObj!.attributes.current_activity;
const newVal = ev.target.value;
const newVal = ev.detail.value;
if (!newVal || oldVal === newVal) {
return;

View File

@@ -11,13 +11,11 @@ import {
import { LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { EntityRegistryDisplayEntry } from "../../../data/entity/entity_registry";
@@ -172,21 +170,17 @@ class MoreInfoVacuum extends LitElement {
.disabled=${stateObj.state === UNAVAILABLE}
.value=${stateObj.attributes.fan_speed}
@selected=${this._handleFanSpeedChanged}
fixedMenuPosition
naturalMenuWidth
@closed=${stopPropagation}
>
${stateObj.attributes.fan_speed_list!.map(
(mode) => html`
<ha-list-item .value=${mode}>
${this.hass.formatEntityAttributeValue(
stateObj,
"fan_speed",
mode
)}
</ha-list-item>
`
.options=${stateObj.attributes.fan_speed_list!.map(
(mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
stateObj,
"fan_speed",
mode
),
})
)}
>
</ha-select>
<div
style="justify-content: center; align-self: center; padding-top: 1.3em"
@@ -291,9 +285,9 @@ class MoreInfoVacuum extends LitElement {
});
}
private _handleFanSpeedChanged(ev) {
private _handleFanSpeedChanged(ev: CustomEvent<{ value: string }>) {
const oldVal = this.stateObj!.attributes.fan_speed;
const newVal = ev.target.value;
const newVal = ev.detail.value;
if (!newVal || oldVal === newVal) {
return;

View File

@@ -5,8 +5,8 @@ import "../../components/ha-button";
import "../../components/ha-spinner";
import { testAssistSatelliteConnection } from "../../data/assist_satellite";
import type { HomeAssistant } from "../../types";
import { AssistantSetupStyles } from "./styles";
import { documentationUrl } from "../../util/documentation-url";
import { AssistantSetupStyles } from "./styles";
@customElement("ha-voice-assistant-setup-step-check")
export class HaVoiceAssistantSetupStepCheck extends LitElement {
@@ -58,7 +58,7 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
"/voice_control/troubleshooting/#i-dont-get-a-voice-response"
)}
>
>${this.hass.localize(
${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.check.help"
)}</ha-button
>

View File

@@ -8,7 +8,6 @@ import {
computeDeviceName,
computeDeviceNameDisplay,
} from "../../common/entity/compute_device_name";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-tts-voice-picker";
import type { AssistPipeline } from "../../data/assist_pipeline";
@@ -115,19 +114,15 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.form.wake_word_id"
)}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.value=${this.assistConfiguration.active_wake_words[0]}
@selected=${this._wakeWordPicked}
>
${this.assistConfiguration.available_wake_words.map(
(wakeword) =>
html`<ha-list-item .value=${wakeword.id}>
${wakeword.wake_word}
</ha-list-item>`
.options=${this.assistConfiguration.available_wake_words.map(
(wakeword) => ({
value: wakeword.id,
label: wakeword.wake_word,
})
)}
</ha-select>
></ha-select>
<ha-button
appearance="plain"
size="small"
@@ -151,16 +146,17 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
)}
@closed=${stopPropagation}
.value=${pipelineEntity?.state}
fixedMenuPosition
naturalMenuWidth
@selected=${this._pipelinePicked}
>
${pipelineEntity?.attributes.options.map(
(pipeline) =>
html`<ha-list-item .value=${pipeline}>
${this.hass.formatEntityState(pipelineEntity, pipeline)}
</ha-list-item>`
.options=${pipelineEntity?.attributes.options.map(
(pipeline) => ({
value: pipeline,
label: this.hass.formatEntityState(
pipelineEntity,
pipeline
),
})
)}
>
</ha-select>
<ha-button
appearance="plain"
@@ -235,16 +231,19 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
this._deviceName = ev.target.value;
}
private async _wakeWordPicked(ev) {
const option = ev.target.value;
private async _wakeWordPicked(ev: CustomEvent<{ value: string }>) {
const option = ev.detail.value;
if (this.assistConfiguration) {
this.assistConfiguration.active_wake_words = [option];
}
await setWakeWords(this.hass, this.assistEntityId!, [option]);
}
private _pipelinePicked(ev) {
private _pipelinePicked(ev: CustomEvent<{ value: string }>) {
const stateObj = this.hass!.states[
this.assistConfiguration!.pipeline_entity_id
] as InputSelectEntity;
const option = ev.target.value;
const option = ev.detail.value;
if (
option === stateObj.state ||
!stateObj.attributes.options.includes(option)
@@ -384,6 +383,11 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
.row ha-button {
width: 82px;
}
ha-select {
display: block;
text-align: start;
}
`,
];
}

View File

@@ -144,7 +144,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
.label=${""}
native-name
@value-changed=${this._languageChanged}
inline-arrow
></ha-language-picker>
<a
href="https://www.home-assistant.io/getting-started/onboarding/"

View File

@@ -1,19 +1,16 @@
import type { SelectedDetail } from "@material/mwc-list";
import { TZDate } from "@date-fns/tz";
import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type { Options, WeekdayStr, ByWeekday } from "rrule";
import { customElement, property, state } from "lit/decorators";
import type { ByWeekday, Options, WeekdayStr } from "rrule";
import { RRule, Weekday } from "rrule";
import { formatDate, formatTime } from "../../common/datetime/calc_date";
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
import { stopPropagation } from "../../common/dom/stop_propagation";
import type { LocalizeKeys } from "../../common/translations/localize";
import "../../components/chips/ha-chip-set";
import "../../components/chips/ha-filter-chip";
import "../../components/ha-date-input";
import "../../components/ha-list-item";
import "../../components/ha-select";
import type { HaSelect } from "../../components/ha-select";
import "../../components/ha-textfield";
import type { HomeAssistant } from "../../types";
import type {
@@ -33,7 +30,6 @@ import {
ruleByWeekDay,
untilValue,
} from "./recurrence";
import { formatDate, formatTime } from "../../common/datetime/calc_date";
@customElement("ha-recurrence-rule-editor")
export class RecurrenceRuleEditor extends LitElement {
@@ -71,8 +67,6 @@ export class RecurrenceRuleEditor extends LitElement {
@state() private _untilDay?: Date;
@query("#monthly") private _monthlyRepeatSelect!: HaSelect;
private _allWeekdays?: WeekdayStr[];
private _monthlyRepeatItems: MonthlyRepeatItem[] = [];
@@ -91,14 +85,6 @@ export class RecurrenceRuleEditor extends LitElement {
? getMonthlyRepeatItems(this.hass, this._interval, this.dtstart)
: [];
this._computeWeekday();
const selectElement = this._monthlyRepeatSelect;
if (selectElement) {
const oldSelected = selectElement.index;
selectElement.select(-1);
this.updateComplete.then(() => {
selectElement.select(changedProps.has("dtstart") ? 0 : oldSelected);
});
}
}
if (
@@ -184,35 +170,16 @@ export class RecurrenceRuleEditor extends LitElement {
id="freq"
label=${this.hass.localize("ui.components.calendar.event.repeat.label")}
@selected=${this._onRepeatSelected}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.value=${this._freq}
>
<ha-list-item value="none">
${this.hass.localize("ui.components.calendar.event.repeat.freq.none")}
</ha-list-item>
<ha-list-item value="yearly">
${this.hass.localize(
"ui.components.calendar.event.repeat.freq.yearly"
)}
</ha-list-item>
<ha-list-item value="monthly">
${this.hass.localize(
"ui.components.calendar.event.repeat.freq.monthly"
)}
</ha-list-item>
<ha-list-item value="weekly">
${this.hass.localize(
"ui.components.calendar.event.repeat.freq.weekly"
)}
</ha-list-item>
<ha-list-item value="daily">
${this.hass.localize(
"ui.components.calendar.event.repeat.freq.daily"
)}
</ha-list-item>
</ha-select>
.options=${["none", "yearly", "monthly", "weekly", "daily"].map(
(freq) => ({
value: freq,
label: this.hass.localize(
`ui.components.calendar.event.repeat.freq.${freq}` as LocalizeKeys
),
})
)}
></ha-select>
`;
}
@@ -227,18 +194,8 @@ export class RecurrenceRuleEditor extends LitElement {
)}
@selected=${this._onMonthlyDetailSelected}
.value=${this._monthlyRepeat || this._monthlyRepeatItems[0]?.value}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this._monthlyRepeatItems!.map(
(item) => html`
<ha-list-item .value=${item.value} .item=${item}>
${item.label}
</ha-list-item>
`
)}
</ha-select>`
.options=${this._monthlyRepeatItems}
></ha-select>`
: nothing}
`;
}
@@ -299,19 +256,13 @@ export class RecurrenceRuleEditor extends LitElement {
)}
.value=${this._end}
@selected=${this._onEndSelected}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${["never", "after", "on"].map((end) => ({
value: end,
label: this.hass.localize(
`ui.components.calendar.event.repeat.end.${end as RepeatEnd}`
),
}))}
>
<ha-list-item value="never">
${this.hass.localize("ui.components.calendar.event.repeat.end.never")}
</ha-list-item>
<ha-list-item value="after">
${this.hass.localize("ui.components.calendar.event.repeat.end.after")}
</ha-list-item>
<ha-list-item value="on">
${this.hass.localize("ui.components.calendar.event.repeat.end.on")}
</ha-list-item>
</ha-select>
${this._end === "after"
? html`
@@ -360,8 +311,8 @@ export class RecurrenceRuleEditor extends LitElement {
this._interval = (e.target! as any).value;
}
private _onRepeatSelected(e: CustomEvent<SelectedDetail<number>>) {
this._freq = (e.target as HaSelect).value as RepeatFrequency;
private _onRepeatSelected(e: CustomEvent<{ value: string }>) {
this._freq = e.detail.value as RepeatFrequency;
if (this._freq === "yearly") {
this._interval = 1;
@@ -370,12 +321,12 @@ export class RecurrenceRuleEditor extends LitElement {
this._weekday.clear();
this._computeWeekday();
}
e.stopPropagation();
}
private _onMonthlyDetailSelected(e: CustomEvent<SelectedDetail<number>>) {
e.stopPropagation();
const selectedItem = this._monthlyRepeatItems[e.detail.index];
private _onMonthlyDetailSelected(e: CustomEvent<{ value: string }>) {
const selectedItem = this._monthlyRepeatItems.find(
(item) => item.value === e.detail.value
);
if (!selectedItem) {
return;
}
@@ -395,8 +346,8 @@ export class RecurrenceRuleEditor extends LitElement {
this.requestUpdate("_weekday");
}
private _onEndSelected(e: CustomEvent<SelectedDetail<number>>) {
const end = (e.target as HaSelect).value as RepeatEnd;
private _onEndSelected(e: CustomEvent<{ value: string }>) {
const end = e.detail.value as RepeatEnd;
if (end === this._end) {
return;
}

View File

@@ -1,11 +1,9 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-card";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import type {
HassioAddonDetails,
@@ -16,8 +14,8 @@ import type { HassioHardwareAudioDevice } from "../../../../../data/hassio/hardw
import { fetchHassioHardwareAudio } from "../../../../../data/hassio/hardware";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
@customElement("supervisor-app-audio")
class SupervisorAppAudio extends LitElement {
@@ -55,19 +53,13 @@ class SupervisorAppAudio extends LitElement {
"ui.panel.config.apps.configuration.audio.input"
)}
@selected=${this._setInputDevice}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.value=${this._selectedInput!}
.disabled=${this.disabled}
.options=${this._inputDevices.map((item) => ({
value: item.device || "",
label: item.name,
}))}
>
${this._inputDevices.map(
(item) => html`
<ha-list-item .value=${item.device || ""}>
${item.name}
</ha-list-item>
`
)}
</ha-select>`}
${this._outputDevices &&
html`<ha-select
@@ -75,19 +67,13 @@ class SupervisorAppAudio extends LitElement {
"ui.panel.config.apps.configuration.audio.output"
)}
@selected=${this._setOutputDevice}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.value=${this._selectedOutput!}
.disabled=${this.disabled}
.options=${this._outputDevices.map((item) => ({
value: item.device || "",
label: item.name,
}))}
>
${this._outputDevices.map(
(item) => html`
<ha-list-item .value=${item.device || ""}
>${item.name}</ha-list-item
>
`
)}
</ha-select>`}
</div>
<div class="card-actions">
@@ -116,6 +102,7 @@ class SupervisorAppAudio extends LitElement {
}
ha-select {
width: 100%;
display: block;
}
ha-select:last-child {
margin-top: var(--ha-space-2);
@@ -131,13 +118,13 @@ class SupervisorAppAudio extends LitElement {
}
}
private _setInputDevice(ev): void {
const device = ev.target.value;
private _setInputDevice(ev: CustomEvent<{ value: string }>): void {
const device = ev.detail.value;
this._selectedInput = device;
}
private _setOutputDevice(ev): void {
const device = ev.target.value;
private _setOutputDevice(ev: CustomEvent<{ value: string }>): void {
const device = ev.detail.value;
this._selectedOutput = device;
}

View File

@@ -2,13 +2,11 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import { stringCompare } from "../../../../../common/string/compare";
import type { LocalizeFunc } from "../../../../../common/translations/localize";
import { CONDITION_ICONS } from "../../../../../components/ha-condition-icon";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-dropdown-item";
import "../../../../../components/ha-select";
import type { HaSelect } from "../../../../../components/ha-select";
import {
DYNAMIC_PREFIX,
getValueFromDynamic,
@@ -85,37 +83,47 @@ export class HaConditionAction
this.action.condition
);
const value =
this.action.condition in this._conditionDescriptions
? `${DYNAMIC_PREFIX}${this.action.condition}`
: this.action.condition;
let valueLabel = value;
const items = html`${this._processedTypes(
this._conditionDescriptions,
this.hass.localize
).map(([opt, label, condition]) => {
const selected = value === opt;
if (selected) {
valueLabel = label;
}
return html`
<ha-dropdown-item .value=${opt} class=${selected ? "selected" : ""}>
<ha-condition-icon
.hass=${this.hass}
slot="icon"
.condition=${condition}
></ha-condition-icon>
${label}
</ha-dropdown-item>
`;
})}`;
return html`
${this.inSidebar || (!this.inSidebar && !this.indent)
? html`
<ha-select
fixedMenuPosition
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type_select"
)}
.disabled=${this.disabled}
.value=${this.action.condition in this._conditionDescriptions
? `${DYNAMIC_PREFIX}${this.action.condition}`
: this.action.condition}
naturalMenuWidth
.value=${valueLabel}
@selected=${this._typeChanged}
@closed=${stopPropagation}
>
${this._processedTypes(
this._conditionDescriptions,
this.hass.localize
).map(
([opt, label, condition]) => html`
<ha-list-item .value=${opt} graphic="icon">
${label}
<ha-condition-icon
.hass=${this.hass}
slot="graphic"
.condition=${condition}
></ha-condition-icon>
</ha-list-item>
`
)}
${items}
</ha-select>
`
: nothing}
@@ -192,8 +200,8 @@ export class HaConditionAction
});
}
private _typeChanged(ev: CustomEvent) {
const type = (ev.target as HaSelect).value;
private _typeChanged(ev: CustomEvent<{ value: string }>) {
const type = ev.detail.value;
if (!type) {
return;
@@ -242,6 +250,7 @@ export class HaConditionAction
static styles = css`
ha-select {
margin-bottom: 24px;
display: block;
}
`;
}

View File

@@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../../../../../common/string/compare";
import "../../../../../components/ha-select";
import "../../../../../components/ha-list-item";
import type { TagTrigger } from "../../../../../data/automation";
import type { Tag } from "../../../../../data/tag";
import { fetchTags } from "../../../../../data/tag";
@@ -42,16 +41,11 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
.disabled=${this.disabled || this._tags.length === 0}
.value=${this.trigger.tag_id}
@selected=${this._tagChanged}
fixedMenuPosition
naturalMenuWidth
.options=${this._tags.map((tag) => ({
value: tag.id,
label: tag.name || tag.id,
}))}
>
${this._tags.map(
(tag) => html`
<ha-list-item .value=${tag.id}>
${tag.name || tag.id}
</ha-list-item>
`
)}
</ha-select>
`;
}
@@ -66,18 +60,18 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
);
}
private _tagChanged(ev) {
private _tagChanged(ev: CustomEvent<{ value: string }>) {
if (
!ev.target.value ||
!ev.detail.value ||
!this._tags ||
this.trigger.tag_id === ev.target.value
this.trigger.tag_id === ev.detail.value
) {
return;
}
fireEvent(this, "value-changed", {
value: {
...this.trigger,
tag_id: ev.target.value,
tag_id: ev.detail.value,
},
});
}

View File

@@ -1,12 +1,12 @@
import { css, html, LitElement, nothing } from "lit";
import { mdiContentCopy } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import "../../../../components/ha-button";
import "../../../../components/ha-card";
import "../../../../components/ha-language-picker";
import "../../../../components/ha-list-item";
import "../../../../components/ha-select";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-switch";
@@ -19,9 +19,8 @@ import {
} from "../../../../data/cloud/tts";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import { showTryTtsDialog } from "./show-dialog-cloud-tts-try";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import { showToast } from "../../../../util/toast";
import { showTryTtsDialog } from "./show-dialog-cloud-tts-try";
export const getCloudTtsSupportedVoices = (
language: string,
@@ -96,13 +95,11 @@ export class CloudTTSPref extends LitElement {
.disabled=${this.savingPreferences}
.value=${defaultVoice[1]}
@selected=${this._handleVoiceChange}
.options=${voices.map((voice) => ({
value: voice.voiceId,
label: voice.voiceName,
}))}
>
${voices.map(
(voice) =>
html`<ha-list-item .value=${voice.voiceId}>
${voice.voiceName}
</ha-list-item>`
)}
</ha-select>
</div>
</div>
@@ -134,16 +131,6 @@ export class CloudTTSPref extends LitElement {
`;
}
protected updated(changedProps) {
if (
changedProps.has("cloudStatus") &&
this.cloudStatus?.prefs.tts_default_voice?.[0] !==
changedProps.get("cloudStatus")?.prefs.tts_default_voice?.[0]
) {
this.renderRoot.querySelector("ha-select")?.layoutOptions();
}
}
protected willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
@@ -195,13 +182,13 @@ export class CloudTTSPref extends LitElement {
}
}
private async _handleVoiceChange(ev) {
if (ev.target.value === this.cloudStatus!.prefs.tts_default_voice[1]) {
private async _handleVoiceChange(ev: CustomEvent<{ value: string }>) {
if (ev.detail.value === this.cloudStatus!.prefs.tts_default_voice[1]) {
return;
}
this.savingPreferences = true;
const language = this.cloudStatus!.prefs.tts_default_voice[0];
const voice = ev.target.value;
const voice = ev.detail.value;
try {
await updateCloudPref(this.hass, {

View File

@@ -4,14 +4,13 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-list-item";
import "../../../../components/ha-select";
import "../../../../components/ha-button";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-select";
import type { HaSelectOption } from "../../../../components/ha-select";
import "../../../../components/ha-textarea";
import type { HaTextArea } from "../../../../components/ha-textarea";
import { showAutomationEditor } from "../../../../data/automation";
@@ -60,6 +59,25 @@ export class DialogTryTts extends LitElement {
return nothing;
}
const target = this._target || "browser";
const targetOptions: HaSelectOption[] = Object.values(this.hass.states)
.filter(
(entity) =>
computeStateDomain(entity) === "media_player" &&
supportsFeature(entity, MediaPlayerEntityFeature.PLAY_MEDIA)
)
.map((entity) => ({
value: entity.entity_id,
label: computeStateName(entity),
}));
targetOptions.unshift({
value: "browser",
label: this.hass.localize(
"ui.panel.config.cloud.account.tts.dialog.target_browser"
),
});
return html`
<ha-dialog
open
@@ -93,28 +111,8 @@ export class DialogTryTts extends LitElement {
id="target"
.value=${target}
@selected=${this._handleTargetChanged}
fixedMenuPosition
naturalMenuWidth
@closed=${stopPropagation}
.options=${targetOptions}
>
<ha-list-item value="browser">
${this.hass.localize(
"ui.panel.config.cloud.account.tts.dialog.target_browser"
)}
</ha-list-item>
${Object.values(this.hass.states)
.filter(
(entity) =>
computeStateDomain(entity) === "media_player" &&
supportsFeature(entity, MediaPlayerEntityFeature.PLAY_MEDIA)
)
.map(
(entity) => html`
<ha-list-item .value=${entity.entity_id}>
${computeStateName(entity)}
</ha-list-item>
`
)}
</ha-select>
</div>
<ha-button
@@ -140,8 +138,8 @@ export class DialogTryTts extends LitElement {
`;
}
private _handleTargetChanged(ev) {
this._target = ev.target.value;
private _handleTargetChanged(ev: CustomEvent<{ value: string }>) {
this._target = ev.detail.value;
this.requestUpdate("_target");
}
@@ -227,10 +225,9 @@ export class DialogTryTts extends LitElement {
}
ha-textarea,
ha-select {
width: 100%;
}
ha-select {
display: block;
margin-top: 8px;
width: 100%;
}
`,
];

View File

@@ -3,14 +3,12 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog";
import "../../../../components/ha-radio";
import "../../../../components/ha-button";
import "../../../../components/ha-select";
import "../../../../components/ha-list-item";
import type { DeviceConsumptionEnergyPreference } from "../../../../data/energy";
import { energyStatisticHelpUrl } from "../../../../data/energy";
import { getStatisticLabel } from "../../../../data/recorder";
@@ -95,6 +93,27 @@ export class DialogEnergyDeviceSettingsWater
const pickableUnit = this._volume_units?.join(", ") || "";
const includedInDeviceOptions = !this._possibleParents.length
? [
{
value: "-",
disabled: true,
label: this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.no_upstream_devices"
),
},
]
: this._possibleParents.map((stat) => ({
value: stat.stat_consumption,
label:
stat.name ||
getStatisticLabel(
this.hass,
stat.stat_consumption,
this._params?.statsMetadata?.[stat.stat_consumption]
),
}));
return html`
<ha-dialog
open
@@ -156,31 +175,9 @@ export class DialogEnergyDeviceSettingsWater
)}
.disabled=${!this._device}
@selected=${this._parentSelected}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
clearable
.options=${includedInDeviceOptions}
>
${!this._possibleParents.length
? html`
<ha-list-item disabled value="-"
>${this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.no_upstream_devices"
)}</ha-list-item
>
`
: this._possibleParents.map(
(stat) => html`
<ha-list-item .value=${stat.stat_consumption}
>${stat.name ||
getStatisticLabel(
this.hass,
stat.stat_consumption,
this._params?.statsMetadata?.[stat.stat_consumption]
)}</ha-list-item
>
`
)}
</ha-select>
<ha-button
@@ -221,10 +218,10 @@ export class DialogEnergyDeviceSettingsWater
this._device = newDevice;
}
private _parentSelected(ev) {
private _parentSelected(ev: CustomEvent<{ value: string }>) {
const newDevice = {
...this._device!,
included_in_stat: ev.target!.value,
included_in_stat: ev.detail.value,
} as DeviceConsumptionEnergyPreference;
if (!newDevice.included_in_stat) {
delete newDevice.included_in_stat;
@@ -249,6 +246,7 @@ export class DialogEnergyDeviceSettingsWater
width: 100%;
}
ha-select {
display: block;
margin-top: 16px;
width: 100%;
}

View File

@@ -3,14 +3,13 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog";
import "../../../../components/ha-radio";
import "../../../../components/ha-button";
import "../../../../components/ha-select";
import "../../../../components/ha-list-item";
import type { HaSelectOption } from "../../../../components/ha-select";
import type { DeviceConsumptionEnergyPreference } from "../../../../data/energy";
import { energyStatisticHelpUrl } from "../../../../data/energy";
import { getStatisticLabel } from "../../../../data/recorder";
@@ -104,6 +103,28 @@ export class DialogEnergyDeviceSettings
return nothing;
}
const includedInDeviceOptions: HaSelectOption[] = this._possibleParents
.length
? this._possibleParents.map((stat) => ({
value: stat.stat_consumption,
label:
stat.name ||
getStatisticLabel(
this.hass,
stat.stat_consumption,
this._params?.statsMetadata?.[stat.stat_consumption]
),
}))
: [
{
value: "-",
disabled: true,
label: this.hass.localize(
"ui.panel.config.energy.device_consumption.dialog.no_upstream_devices"
),
},
];
return html`
<ha-dialog
open
@@ -178,31 +199,9 @@ export class DialogEnergyDeviceSettings
)}
.disabled=${!this._device}
@selected=${this._parentSelected}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
clearable
.options=${includedInDeviceOptions}
>
${!this._possibleParents.length
? html`
<ha-list-item disabled value="-"
>${this.hass.localize(
"ui.panel.config.energy.device_consumption.dialog.no_upstream_devices"
)}</ha-list-item
>
`
: this._possibleParents.map(
(stat) => html`
<ha-list-item .value=${stat.stat_consumption}
>${stat.name ||
getStatisticLabel(
this.hass,
stat.stat_consumption,
this._params?.statsMetadata?.[stat.stat_consumption]
)}</ha-list-item
>
`
)}
</ha-select>
<ha-button
@@ -257,10 +256,10 @@ export class DialogEnergyDeviceSettings
this._device = newDevice;
}
private _parentSelected(ev) {
private _parentSelected(ev: CustomEvent<{ value: string }>) {
const newDevice = {
...this._device!,
included_in_stat: ev.target!.value,
included_in_stat: ev.detail.value,
} as DeviceConsumptionEnergyPreference;
if (!newDevice.included_in_stat) {
delete newDevice.included_in_stat;
@@ -289,6 +288,7 @@ export class DialogEnergyDeviceSettings
width: 100%;
}
ha-select {
display: block;
margin-top: var(--ha-space-4);
width: 100%;
}

View File

@@ -1,3 +1,4 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { mdiContentCopy, mdiRestore } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
@@ -7,7 +8,6 @@ import { until } from "lit/directives/until";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeObjectId } from "../../../common/entity/compute_object_id";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -22,6 +22,7 @@ import { copyToClipboard } from "../../../common/util/copy-clipboard";
import "../../../components/ha-alert";
import "../../../components/ha-area-picker";
import "../../../components/ha-color-picker";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button-next";
import "../../../components/ha-icon-picker";
@@ -424,34 +425,46 @@ export class EntityRegistrySettingsEditor extends LitElement {
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_class"
)}
naturalMenuWidth
fixedMenuPosition
@selected=${this._switchAsDomainChanged}
@closed=${stopPropagation}
value=${this._switchAsLabel(
this._switchAsDomain,
this._deviceClass
)}
>
<ha-list-item
<ha-dropdown-item
value="switch"
.selected=${!this._deviceClass || this._deviceClass === "switch"}
class=${this._switchAsDomain === "switch" &&
(!this._deviceClass || this._deviceClass === "switch")
? "selected"
: ""}
>
${domainToName(this.hass.localize, "switch")}
</ha-list-item>
<ha-list-item
</ha-dropdown-item>
<ha-dropdown-item
value="outlet"
.selected=${this._deviceClass === "outlet"}
class=${this._switchAsDomain === "switch" &&
this._deviceClass === "outlet"
? "selected"
: ""}
>
${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_classes.switch.outlet"
)}
</ha-list-item>
<li divider role="separator"></li>
</ha-dropdown-item>
<wa-divider></wa-divider>
${this._switchAsDomainsSorted(
SWITCH_AS_DOMAINS,
this.hass.localize
).map(
(entry) => html`
<ha-list-item .value=${entry.domain}>
<ha-dropdown-item
.value=${entry.domain}
class=${this._switchAsDomain === entry.domain
? "selected"
: ""}
>
${entry.label}
</ha-list-item>
</ha-dropdown-item>
`
)}
</ha-select>`
@@ -460,19 +473,22 @@ export class EntityRegistrySettingsEditor extends LitElement {
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.switch_as_x"
)}
.value=${this._switchAsDomain}
naturalMenuWidth
fixedMenuPosition
.value=${this._switchAsLabel(this._switchAsDomain)}
@selected=${this._switchAsDomainChanged}
@closed=${stopPropagation}
>
<ha-list-item value="switch">
<ha-dropdown-item
value="switch"
class=${this._switchAsDomain === "switch" ? "selected" : ""}
>
${domainToName(this.hass.localize, "switch")}
</ha-list-item>
<ha-list-item .value=${domain}>
</ha-dropdown-item>
<ha-dropdown-item
.value=${domain}
class=${this._switchAsDomain === domain ? "selected" : ""}
>
${domainToName(this.hass.localize, domain)}
</ha-list-item>
<li divider role="separator"></li>
</ha-dropdown-item>
<wa-divider></wa-divider>
${this._switchAsDomainsSorted(
SWITCH_AS_DOMAINS,
this.hass.localize
@@ -480,9 +496,14 @@ export class EntityRegistrySettingsEditor extends LitElement {
domain === entry.domain
? nothing
: html`
<ha-list-item .value=${entry.domain}>
<ha-dropdown-item
.value=${entry.domain}
class=${this._switchAsDomain === entry.domain
? "selected"
: ""}
>
${entry.label}
</ha-list-item>
</ha-dropdown-item>
`
)}
</ha-select>
@@ -513,12 +534,13 @@ export class EntityRegistrySettingsEditor extends LitElement {
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.device_class"
)}
.value=${this._deviceClass}
naturalMenuWidth
fixedMenuPosition
.value=${this._deviceClass
? this.hass.localize(
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${this._deviceClass}`
)
: undefined}
clearable
@selected=${this._deviceClassChanged}
@closed=${stopPropagation}
>
${this._deviceClassesSorted(
domain,
@@ -526,29 +548,39 @@ export class EntityRegistrySettingsEditor extends LitElement {
this.hass.localize
).map(
(entry) => html`
<ha-list-item .value=${entry.deviceClass}>
<ha-dropdown-item
.value=${entry.deviceClass}
class=${entry.deviceClass === this._deviceClass
? "selected"
: ""}
>
${entry.label}
</ha-list-item>
</ha-dropdown-item>
`
)}
${this._deviceClassOptions[0].length &&
this._deviceClassOptions[1].length
? html`<li divider role="separator"></li>`
: ""}
? html`<wa-divider></wa-divider>`
: nothing}
${this._deviceClassesSorted(
domain,
this._deviceClassOptions[1],
this.hass.localize
).map(
(entry) => html`
<ha-list-item .value=${entry.deviceClass}>
<ha-dropdown-item
.value=${entry.deviceClass}
class=${entry.deviceClass === this._deviceClass
? "selected"
: ""}
>
${entry.label}
</ha-list-item>
</ha-dropdown-item>
`
)}
</ha-select>
`
: ""}
: nothing}
${domain === "number" &&
this._deviceClass &&
stateObj?.attributes.unit_of_measurement &&
@@ -560,20 +592,14 @@ export class EntityRegistrySettingsEditor extends LitElement {
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.unit_of_measurement"
)}
.value=${stateObj.attributes.unit_of_measurement}
naturalMenuWidth
fixedMenuPosition
.value=${this._unit_of_measurement ||
stateObj.attributes.unit_of_measurement}
@selected=${this._unitChanged}
@closed=${stopPropagation}
.options=${this._numberDeviceClassConvertibleUnits}
>
${this._numberDeviceClassConvertibleUnits.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
`
: ""}
: nothing}
${domain === "lock"
? html`
<ha-textfield
@@ -590,7 +616,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
@input=${this._defaultcodeChanged}
></ha-textfield>
`
: ""}
: nothing}
${domain === "alarm_control_panel"
? html`
<ha-textfield
@@ -603,7 +629,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
@input=${this._defaultcodeChanged}
></ha-textfield>
`
: ""}
: nothing}
${domain === "calendar"
? html`
<ha-color-picker
@@ -616,7 +642,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
@value-changed=${this._calendarColorChanged}
></ha-color-picker>
`
: ""}
: nothing}
${domain === "sensor" &&
this._deviceClass &&
stateObj?.attributes.unit_of_measurement &&
@@ -628,20 +654,14 @@ export class EntityRegistrySettingsEditor extends LitElement {
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.unit_of_measurement"
)}
.value=${stateObj.attributes.unit_of_measurement}
naturalMenuWidth
fixedMenuPosition
.value=${this._unit_of_measurement ||
stateObj.attributes.unit_of_measurement}
@selected=${this._unitChanged}
@closed=${stopPropagation}
.options=${this._sensorDeviceClassConvertibleUnits}
>
${this._sensorDeviceClassConvertibleUnits.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
`
: ""}
: nothing}
${domain === "sensor" &&
// Allow customizing the precision for a sensor with numerical device class,
// a unit of measurement or state class
@@ -657,116 +677,78 @@ export class EntityRegistrySettingsEditor extends LitElement {
.value=${this._precision == null
? "default"
: this._precision.toString()}
naturalMenuWidth
fixedMenuPosition
@selected=${this._precisionChanged}
@closed=${stopPropagation}
.options=${[
{
value: "default",
label: this.hass.localize(
"ui.dialogs.entity_registry.editor.precision_default",
{
value: this._precisionLabel(
defaultPrecision,
stateObj?.state
),
}
),
},
...PRECISIONS.map((precision) => ({
value: precision.toString(),
label: this._precisionLabel(precision, stateObj?.state),
})),
]}
>
<ha-list-item value="default"
>${this.hass.localize(
"ui.dialogs.entity_registry.editor.precision_default",
{
value: this._precisionLabel(
defaultPrecision,
stateObj?.state
),
}
)}</ha-list-item
>
${PRECISIONS.map(
(precision) => html`
<ha-list-item .value=${precision.toString()}>
${this._precisionLabel(precision, stateObj?.state)}
</ha-list-item>
`
)}
</ha-select>
`
: ""}
: nothing}
${domain === "weather"
? html`
<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.precipitation_unit"
)}
.value=${this._precipitation_unit}
naturalMenuWidth
fixedMenuPosition
.value=${this._precipitation_unit || undefined}
@selected=${this._precipitationUnitChanged}
@closed=${stopPropagation}
.options=${this._weatherConvertibleUnits?.precipitation_unit}
>
${this._weatherConvertibleUnits?.precipitation_unit.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.pressure_unit"
)}
.value=${this._pressure_unit}
naturalMenuWidth
fixedMenuPosition
.value=${this._pressure_unit || undefined}
@selected=${this._pressureUnitChanged}
@closed=${stopPropagation}
.options=${this._weatherConvertibleUnits?.pressure_unit}
>
${this._weatherConvertibleUnits?.pressure_unit.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.temperature_unit"
)}
.value=${this._temperature_unit}
naturalMenuWidth
fixedMenuPosition
.value=${this._temperature_unit || undefined}
@selected=${this._temperatureUnitChanged}
@closed=${stopPropagation}
.options=${this._weatherConvertibleUnits?.temperature_unit}
>
${this._weatherConvertibleUnits?.temperature_unit.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.visibility_unit"
)}
.value=${this._visibility_unit}
naturalMenuWidth
fixedMenuPosition
.value=${this._visibility_unit || undefined}
@selected=${this._visibilityUnitChanged}
@closed=${stopPropagation}
.options=${this._weatherConvertibleUnits?.visibility_unit}
>
${this._weatherConvertibleUnits?.visibility_unit.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.wind_speed_unit"
)}
.value=${this._wind_speed_unit}
naturalMenuWidth
fixedMenuPosition
.value=${this._wind_speed_unit || undefined}
@selected=${this._windSpeedUnitChanged}
@closed=${stopPropagation}
.options=${this._weatherConvertibleUnits?.wind_speed_unit}
>
${this._weatherConvertibleUnits?.wind_speed_unit.map(
(unit: string) => html`
<ha-list-item .value=${unit}>${unit}</ha-list-item>
`
)}
</ha-select>
`
: ""}
: nothing}
<ha-textfield
class="entityId"
.value=${computeObjectId(this._entityId)}
@@ -801,7 +783,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
.disabled=${this.disabled}
@value-changed=${this._areaPicked}
></ha-area-picker>`
: ""}
: nothing}
<ha-labels-picker
.hass=${this.hass}
.value=${this._labels}
@@ -843,26 +825,21 @@ export class EntityRegistrySettingsEditor extends LitElement {
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.stream.stream_orientation"
)}
naturalMenuWidth
fixedMenuPosition
.disabled=${this.disabled}
@selected=${this._handleCameraOrientationChanged}
@closed=${stopPropagation}
.options=${CAMERA_ORIENTATIONS.map((num) => ({
value: num.toString(),
label: this.hass.localize(
("ui.dialogs.entity_registry.editor.stream.stream_orientation_" +
num.toString()) as LocalizeKeys
),
}))}
.value=${this._cameraPrefs.orientation.toString()}
>
${CAMERA_ORIENTATIONS.map((num) => {
const localizeStr =
"ui.dialogs.entity_registry.editor.stream.stream_orientation_" +
num.toString();
return html`
<ha-list-item value=${num}>
${this.hass.localize(localizeStr as LocalizeKeys)}
</ha-list-item>
`;
})}
</ha-select>
</ha-settings-row>
`
: ""}
: nothing}
${this.helperConfigEntry &&
this.helperConfigEntry.supports_options &&
this.helperConfigEntry.domain !== "switch_as_x"
@@ -899,7 +876,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
`
: ""}
: nothing}
<ha-list-item
class="menu-item"
@@ -938,7 +915,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
}
)}</ha-alert
>`
: ""}
: nothing}
<ha-settings-row>
<span slot="heading"
@@ -973,7 +950,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
}
)}</ha-alert
>`
: ""}
: nothing}
<ha-settings-row>
<span slot="heading"
@@ -1005,7 +982,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
this.hass.devices[this.entry.device_id].area_id!
]?.name
})`
: ""}</span
: nothing}</span
>
<span slot="description"
>${this.hass.localize(
@@ -1036,8 +1013,8 @@ export class EntityRegistrySettingsEditor extends LitElement {
.disabled=${this.disabled}
@value-changed=${this._areaPicked}
></ha-area-picker>`
: ""} `
: ""}
: nothing} `
: nothing}
`;
}
@@ -1343,14 +1320,14 @@ export class EntityRegistrySettingsEditor extends LitElement {
this._entityId = `${computeDomain(this._origEntityId)}.${ev.target.value}`;
}
private _deviceClassChanged(ev): void {
private _deviceClassChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._deviceClass = ev.target.value;
this._deviceClass = ev.detail.value;
}
private _unitChanged(ev): void {
private _unitChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._unit_of_measurement = ev.target.value;
this._unit_of_measurement = ev.detail.value;
}
private _defaultcodeChanged(ev): void {
@@ -1363,53 +1340,53 @@ export class EntityRegistrySettingsEditor extends LitElement {
this._calendarColor = ev.detail.value || null;
}
private _precipitationUnitChanged(ev): void {
private _precipitationUnitChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._precipitation_unit = ev.target.value;
this._precipitation_unit = ev.detail.value;
}
private _precisionChanged(ev): void {
private _precisionChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._precision =
ev.target.value === "default" ? null : Number(ev.target.value);
ev.detail.value === "default" ? null : Number(ev.detail.value);
}
private _pressureUnitChanged(ev): void {
private _pressureUnitChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._pressure_unit = ev.target.value;
this._pressure_unit = ev.detail.value;
}
private _temperatureUnitChanged(ev): void {
private _temperatureUnitChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._temperature_unit = ev.target.value;
this._temperature_unit = ev.detail.value;
}
private _visibilityUnitChanged(ev): void {
private _visibilityUnitChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._visibility_unit = ev.target.value;
this._visibility_unit = ev.detail.value;
}
private _windSpeedUnitChanged(ev): void {
private _windSpeedUnitChanged(ev: CustomEvent<{ value: string }>): void {
fireEvent(this, "change");
this._wind_speed_unit = ev.target.value;
this._wind_speed_unit = ev.detail.value;
}
private _switchAsDomainChanged(ev): void {
if (ev.target.value === "") {
private _switchAsDomainChanged(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (value === "") {
return;
}
// If value is "outlet" that means the user kept the "switch" domain, but actually changed
// the device_class of the switch to "outlet".
const switchAs = ev.target.value === "outlet" ? "switch" : ev.target.value;
this._switchAsDomain = switchAs;
this._switchAsDomain = value === "outlet" ? "switch" : value;
if (
(computeDomain(this.entry.entity_id) === "switch" &&
ev.target.value === "outlet") ||
ev.target.value === "switch"
value === "outlet") ||
value === "switch"
) {
this._deviceClass = ev.target.value;
this._deviceClass = value;
}
}
@@ -1466,13 +1443,15 @@ export class EntityRegistrySettingsEditor extends LitElement {
}
}
private async _handleCameraOrientationChanged(ev) {
private async _handleCameraOrientationChanged(
ev: CustomEvent<{ value: string }>
) {
try {
this._cameraPrefs = await updateCameraPrefs(
this.hass,
this.entry.entity_id,
{
orientation: ev.currentTarget.value,
orientation: Number(ev.detail.value),
}
);
} catch (err: any) {
@@ -1547,6 +1526,35 @@ export class EntityRegistrySettingsEditor extends LitElement {
)
);
private _switchAsLabel = memoizeOne(
(switchAsDomain: string, deviceClass?: string) => {
if (switchAsDomain !== "switch") {
const switchAsDomains = this._switchAsDomainsSorted(
SWITCH_AS_DOMAINS,
this.hass.localize
);
for (const entry of switchAsDomains) {
if (entry.domain === switchAsDomain) {
return entry.label;
}
}
}
if (!deviceClass || deviceClass === "switch") {
return domainToName(this.hass.localize, "switch");
}
if (deviceClass === "outlet") {
return this.hass.localize(
"ui.dialogs.entity_registry.editor.device_classes.switch.outlet"
);
}
return switchAsDomain;
}
);
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -1592,9 +1600,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
margin: var(--ha-space-2) 0;
width: 100%;
}
li[divider] {
border-bottom-color: var(--divider-color);
}
.menu-item {
border-radius: var(--ha-border-radius-sm);
margin-top: 3px;

View File

@@ -2,21 +2,20 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { storage } from "../../../../../common/decorators/storage";
import "../../../../../components/ha-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-code-editor";
import "../../../../../components/ha-formfield";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-switch";
import "../../../../../components/ha-button";
import { getConfigEntries } from "../../../../../data/config_entries";
import type { Action } from "../../../../../data/script";
import { callExecuteScript } from "../../../../../data/service";
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
import "../../../../../layouts/hass-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import "./mqtt-subscribe-card";
import type { Action } from "../../../../../data/script";
import { callExecuteScript } from "../../../../../data/service";
import { showToast } from "../../../../../util/toast";
import "./mqtt-subscribe-card";
const qosLevel = ["0", "1", "2"];
@@ -89,10 +88,8 @@ export class MQTTConfigPanel extends LitElement {
.label=${this.hass.localize("ui.panel.config.mqtt.qos")}
.value=${this._qos}
@selected=${this._handleQos}
>${qosLevel.map(
(qos) =>
html`<ha-list-item .value=${qos}>${qos}</ha-list-item>`
)}
.options=${qosLevel}
>
</ha-select>
<ha-formfield
label=${this.hass!.localize("ui.panel.config.mqtt.retain")}
@@ -137,9 +134,9 @@ export class MQTTConfigPanel extends LitElement {
this._payload = ev.detail.value;
}
private _handleQos(ev: CustomEvent) {
const newValue = (ev.target! as any).value;
if (newValue >= 0 && newValue !== this._qos) {
private _handleQos(ev: CustomEvent<{ value: string }>) {
const newValue = ev.detail.value;
if (Number(newValue) >= 0 && newValue !== this._qos) {
this._qos = newValue;
}
}

View File

@@ -2,8 +2,8 @@ import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatTime } from "../../../../../common/datetime/format_time";
import "../../../../../components/ha-card";
import "../../../../../components/ha-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-select";
import "../../../../../components/ha-textfield";
import type { MQTTMessage } from "../../../../../data/mqtt";
@@ -12,7 +12,6 @@ import type { HomeAssistant } from "../../../../../types";
import { storage } from "../../../../../common/decorators/storage";
import "../../../../../components/ha-formfield";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-switch";
const qosLevel = ["0", "1", "2"];
@@ -96,9 +95,8 @@ class MqttSubscribeCard extends LitElement {
.disabled=${this._subscribed !== undefined}
.value=${this._qos}
@selected=${this._handleQos}
>${qosLevel.map(
(qos) => html`<ha-list-item .value=${qos}>${qos}</ha-list-item>`
)}
.options=${qosLevel}
>
</ha-select>
<ha-button
appearance="plain"
@@ -142,9 +140,9 @@ class MqttSubscribeCard extends LitElement {
this._topic = ev.target.value;
}
private _handleQos(ev: CustomEvent): void {
const newValue = (ev.target! as any).value;
if (newValue >= 0 && newValue !== this._qos) {
private _handleQos(ev: CustomEvent<{ value: string }>): void {
const newValue = ev.detail.value;
if (Number(newValue) >= 0 && newValue !== this._qos) {
this._qos = newValue;
}
}
@@ -193,8 +191,8 @@ class MqttSubscribeCard extends LitElement {
static styles = css`
form {
display: block;
padding: 16px;
padding: var(--ha-space-4);
padding-bottom: var(--ha-space-8);
}
.events {
margin: -16px 0;
@@ -229,6 +227,7 @@ class MqttSubscribeCard extends LitElement {
}
@media screen and (max-width: 600px) {
ha-select {
display: block;
margin-left: 0px;
margin-top: 8px;
margin-inline-start: 0px;

View File

@@ -2,12 +2,10 @@ import type { TemplateResult } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-button";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import { changeZHANetworkChannel } from "../../../../../data/zha";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
@@ -96,22 +94,18 @@ class DialogZHAChangeChannel extends LitElement implements HassDialog {
.label=${this.hass.localize(
"ui.panel.config.zha.change_channel_dialog.new_channel"
)}
fixedMenuPosition
naturalMenuWidth
@selected=${this._newChannelChosen}
@closed=${stopPropagation}
.value=${String(this._newChannel)}
.options=${VALID_CHANNELS.map((channel) => ({
value: String(channel),
label:
channel === "auto"
? this.hass.localize(
"ui.panel.config.zha.change_channel_dialog.channel_auto"
)
: String(channel),
}))}
>
${VALID_CHANNELS.map(
(newChannel) =>
html`<ha-list-item .value=${String(newChannel)}
>${newChannel === "auto"
? this.hass.localize(
"ui.panel.config.zha.change_channel_dialog.channel_auto"
)
: newChannel}</ha-list-item
>`
)}
</ha-select>
</p>
@@ -137,8 +131,8 @@ class DialogZHAChangeChannel extends LitElement implements HassDialog {
`;
}
private _newChannelChosen(evt: Event): void {
const value: string = (evt.target! as HTMLSelectElement).value;
private _newChannelChosen(ev: CustomEvent<{ value: string }>): void {
const value: string = ev.detail.value;
this._newChannel = value === "auto" ? "auto" : parseInt(value, 10);
}

View File

@@ -1,11 +1,9 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import "../../../../../components/ha-textfield";
import { forwardHaptic } from "../../../../../data/haptics";
@@ -22,7 +20,7 @@ import {
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import { formatAsPaddedHex } from "./functions";
import type { ItemSelectedEvent, SetAttributeServiceData } from "./types";
import type { SetAttributeServiceData } from "./types";
@customElement("zha-cluster-attributes")
export class ZHAClusterAttributes extends LitElement {
@@ -70,22 +68,16 @@ export class ZHAClusterAttributes extends LitElement {
class="menu"
.value=${String(this._selectedAttributeId)}
@selected=${this._selectedAttributeChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${this._attributes.map((entry) => ({
value: String(entry.id),
label: `${entry.name} (id: ${formatAsPaddedHex(entry.id)})`,
}))}
>
${this._attributes.map(
(entry) => html`
<ha-list-item .value=${String(entry.id)}>
${`${entry.name} (id: ${formatAsPaddedHex(entry.id)})`}
</ha-list-item>
`
)}
</ha-select>
</div>
${this._selectedAttributeId !== undefined
? this._renderAttributeInteractions()
: ""}
: nothing}
</ha-card>
`;
}
@@ -221,8 +213,10 @@ export class ZHAClusterAttributes extends LitElement {
}
}
private _selectedAttributeChanged(event: ItemSelectedEvent): void {
this._selectedAttributeId = Number(event.target!.value);
private _selectedAttributeChanged(
event: CustomEvent<{ value: string }>
): void {
this._selectedAttributeId = Number(event.detail.value);
this._attributeValue = "";
}

View File

@@ -1,11 +1,9 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-form/ha-form";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import "../../../../../components/ha-textfield";
import type { Cluster, Command, ZHADevice } from "../../../../../data/zha";
@@ -64,17 +62,11 @@ export class ZHAClusterCommands extends LitElement {
class="menu"
.value=${String(this._selectedCommandId)}
@selected=${this._selectedCommandChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${this._commands.map((entry) => ({
value: String(entry.id),
label: `${entry.name} (id: ${formatAsPaddedHex(entry.id)})`,
}))}
>
${this._commands.map(
(entry) => html`
<ha-list-item .value=${String(entry.id)}>
${entry.name} (id: ${formatAsPaddedHex(entry.id)})
</ha-list-item>
`
)}
</ha-select>
</div>
${this._selectedCommandId !== undefined
@@ -179,8 +171,8 @@ export class ZHAClusterCommands extends LitElement {
this._computeIssueClusterCommandServiceData();
}
private _selectedCommandChanged(event): void {
this._selectedCommandId = Number(event.target.value);
private _selectedCommandChanged(event: CustomEvent<{ value: string }>): void {
this._selectedCommandId = Number(event.detail.value);
this._issueClusterCommandServiceData =
this._computeIssueClusterCommandServiceData();
}

View File

@@ -1,16 +1,13 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-select";
import "../../../../../components/ha-list-item";
import type { ZHADevice } from "../../../../../data/zha";
import { bindDevices, unbindDevices } from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import type { ItemSelectedEvent } from "./types";
@customElement("zha-device-binding-control")
export class ZHADeviceBindingControl extends LitElement {
@@ -44,19 +41,13 @@ export class ZHADeviceBindingControl extends LitElement {
class="menu"
.value=${String(this._bindTargetIndex)}
@selected=${this._bindTargetIndexChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${this.bindableDevices.map((device, idx) => ({
value: String(idx),
label: device.user_given_name
? device.user_given_name
: device.name,
}))}
>
${this.bindableDevices.map(
(device, idx) => html`
<ha-list-item .value=${String(idx)}>
${device.user_given_name
? device.user_given_name
: device.name}
</ha-list-item>
`
)}
</ha-select>
</div>
<div class="card-actions">
@@ -81,8 +72,8 @@ export class ZHADeviceBindingControl extends LitElement {
`;
}
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
this._bindTargetIndex = Number(event.target!.value);
private _bindTargetIndexChanged(event: CustomEvent<{ value: string }>): void {
this._bindTargetIndex = Number(event.detail.value);
this._deviceToBind =
this._bindTargetIndex === -1
? undefined

View File

@@ -1,13 +1,11 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-progress-button";
import type { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-card";
import "../../../../../components/ha-select";
import "../../../../../components/ha-list-item";
import type { Cluster, ZHADevice, ZHAGroup } from "../../../../../data/zha";
import {
bindDeviceToGroup,
@@ -16,7 +14,6 @@ import {
} from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import type { ItemSelectedEvent } from "./types";
import "./zha-clusters-data-table";
import type { ZHAClustersDataTable } from "./zha-clusters-data-table";
@@ -64,16 +61,11 @@ export class ZHAGroupBindingControl extends LitElement {
class="menu"
.value=${String(this._bindTargetIndex)}
@selected=${this._bindTargetIndexChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${this.groups.map((group, idx) => ({
value: String(idx),
label: group.name,
}))}
>
${this.groups.map(
(group, idx) =>
html`<ha-list-item .value=${String(idx)}
>${group.name}</ha-list-item
> `
)}
</ha-select>
</div>
<div class="command-picker">
@@ -109,8 +101,8 @@ export class ZHAGroupBindingControl extends LitElement {
`;
}
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
this._bindTargetIndex = Number(event.target!.value);
private _bindTargetIndexChanged(event: CustomEvent<{ value: string }>): void {
this._bindTargetIndex = Number(event.detail.value);
this._groupToBind =
this._bindTargetIndex === -1
? undefined

View File

@@ -2,9 +2,7 @@ import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/ha-card";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import "../../../../../components/ha-tab-group";
import "../../../../../components/ha-tab-group-tab";
@@ -77,17 +75,11 @@ export class ZHAManageClusters extends LitElement {
class="menu"
.value=${String(this._selectedClusterIndex)}
@selected=${this._selectedClusterChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${this._clusters.map((entry, idx) => ({
value: String(idx),
label: computeClusterKey(entry),
}))}
>
${this._clusters.map(
(entry, idx) => html`
<ha-list-item .value=${String(idx)}
>${computeClusterKey(entry)}</ha-list-item
>
`
)}
</ha-select>
</div>
${this._selectedCluster
@@ -155,8 +147,8 @@ export class ZHAManageClusters extends LitElement {
this._currTab = newTab;
}
private _selectedClusterChanged(event): void {
this._selectedClusterIndex = Number(event.target!.value);
private _selectedClusterChanged(event: CustomEvent<{ value: string }>): void {
this._selectedClusterIndex = Number(event.detail.value);
this._selectedCluster = this._clusters[this._selectedClusterIndex];
}

View File

@@ -5,7 +5,6 @@ import type { HaProgressButton } from "../../../../../../components/buttons/ha-p
import "../../../../../../components/ha-alert";
import "../../../../../../components/ha-button";
import "../../../../../../components/ha-formfield";
import "../../../../../../components/ha-list-item";
import "../../../../../../components/ha-select";
import "../../../../../../components/ha-spinner";
import "../../../../../../components/ha-switch";
@@ -126,16 +125,13 @@ class ZWaveJSCapabilityDoorLock extends LitElement {
)}
.value=${this._currentDoorLockMode?.toString() ?? ""}
@selected=${this._doorLockModeChanged}
.options=${supportedDoorLockModes.map((mode) => ({
value: mode.toString(),
label: this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.door_lock.modes.${mode}`
),
}))}
>
${supportedDoorLockModes.map(
(mode) => html`
<ha-list-item .value=${mode.toString()}>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.door_lock.modes.${mode}`
)}
</ha-list-item>
`
)}
</ha-select>
</div>
<div class="row">
@@ -145,16 +141,13 @@ class ZWaveJSCapabilityDoorLock extends LitElement {
)}
.value=${this._configuration.operationType.toString()}
@selected=${this._operationTypeChanged}
.options=${this._capabilities.supportedOperationTypes.map((type) => ({
value: type.toString(),
label: this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.door_lock.operation_types.${type}`
),
}))}
>
${this._capabilities.supportedOperationTypes.map(
(type) => html`
<ha-list-item .value=${type.toString()}>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.door_lock.operation_types.${type}`
)}
</ha-list-item>
`
)}
</ha-select>
</div>
@@ -346,9 +339,8 @@ class ZWaveJSCapabilityDoorLock extends LitElement {
);
}
private _operationTypeChanged(ev: CustomEvent) {
const target = ev.target as HTMLSelectElement;
const newType = parseInt(target.value);
private _operationTypeChanged(ev: CustomEvent<{ value: string }>) {
const newType = parseInt(ev.detail.value);
if (this._configuration) {
this._configuration = {
...this._configuration,
@@ -393,9 +385,8 @@ class ZWaveJSCapabilityDoorLock extends LitElement {
}
}
private _doorLockModeChanged(ev: CustomEvent) {
const target = ev.target as HTMLSelectElement;
this._currentDoorLockMode = parseInt(target.value) as DoorLockMode;
private _doorLockModeChanged(ev: CustomEvent<{ value: string }>) {
this._currentDoorLockMode = parseInt(ev.detail.value) as DoorLockMode;
}
private async _saveConfig(ev: CustomEvent) {

View File

@@ -1,12 +1,10 @@
import { LitElement, css, html } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../../components/buttons/ha-progress-button";
import type { HaProgressButton } from "../../../../../../components/buttons/ha-progress-button";
import "../../../../../../components/ha-alert";
import "../../../../../../components/ha-formfield";
import "../../../../../../components/ha-list-item";
import "../../../../../../components/ha-select";
import type { HaSelect } from "../../../../../../components/ha-select";
import "../../../../../../components/ha-switch";
import type { HaSwitch } from "../../../../../../components/ha-switch";
import "../../../../../../components/ha-textfield";
@@ -35,6 +33,8 @@ class ZWaveJSCapabilityMultiLevelSwitch extends LitElement {
@state() private _error?: string;
@state() private _direction = "up";
protected render() {
return html`
<h3>
@@ -44,23 +44,28 @@ class ZWaveJSCapabilityMultiLevelSwitch extends LitElement {
</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
: nothing}
<ha-select
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.direction"
)}
id="direction"
.value=${this._direction}
.options=${[
{
value: "up",
label: this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.up"
),
},
{
value: "down",
label: this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.down"
),
},
]}
@selected=${this._directionChanged}
>
<ha-list-item .value=${"up"} selected
>${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.up"
)}</ha-list-item
>
<ha-list-item .value=${"down"}
>${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.multilevel_switch.down"
)}</ha-list-item
>
</ha-select>
<ha-formfield
.label=${this.hass.localize(
@@ -103,9 +108,6 @@ class ZWaveJSCapabilityMultiLevelSwitch extends LitElement {
const button = ev.currentTarget as HaProgressButton;
button.progress = true;
const direction = (this.shadowRoot!.getElementById("direction") as HaSelect)
.value;
const ignoreStartLevel = (
this.shadowRoot!.getElementById("ignore_start_level") as HaSwitch
).checked;
@@ -115,7 +117,7 @@ class ZWaveJSCapabilityMultiLevelSwitch extends LitElement {
);
const options = {
direction,
direction: this._direction,
ignoreStartLevel,
startLevel,
};
@@ -146,6 +148,10 @@ class ZWaveJSCapabilityMultiLevelSwitch extends LitElement {
button.progress = false;
}
private _directionChanged(ev: CustomEvent<{ value: string }>) {
this._direction = ev.detail.value;
}
static styles = css`
ha-select,
ha-formfield,

View File

@@ -1,12 +1,10 @@
import { LitElement, css, html } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import "../../../../../../components/buttons/ha-progress-button";
import type { HaProgressButton } from "../../../../../../components/buttons/ha-progress-button";
import "../../../../../../components/ha-alert";
import "../../../../../../components/ha-button";
import "../../../../../../components/ha-list-item";
import "../../../../../../components/ha-select";
import type { HaSelect } from "../../../../../../components/ha-select";
import "../../../../../../components/ha-textfield";
import type { HaTextField } from "../../../../../../components/ha-textfield";
import type { DeviceRegistryEntry } from "../../../../../../data/device/device_registry";
@@ -37,13 +35,12 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
@state() private _disableSetbackState = false;
@query("#setback_type") private _setbackTypeInput!: HaSelect;
@state() private _setbackType = "0";
@state() private _setbackSpecialType?: string;
@query("#setback_state") private _setbackStateInput!: HaTextField;
@query("#setback_special_state")
private _setbackSpecialStateSelect!: HaSelect;
@state() private _error?: string;
@state() private _loading = true;
@@ -57,23 +54,21 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
</h3>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
: nothing}
<ha-select
.label=${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_type.label`
)}
id="setback_type"
.value=${"0"}
@selected=${this._setbackTypeChanged}
.value=${this._setbackType}
.disabled=${this._loading}
.options=${SETBACK_TYPE_OPTIONS.map((translationKey, index) => ({
value: String(index),
label: this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_type.${translationKey}`
),
}))}
>
${SETBACK_TYPE_OPTIONS.map(
(translationKey, index) =>
html`<ha-list-item .value=${String(index)}>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_type.${translationKey}`
)}
</ha-list-item>`
)}
</ha-select>
<div class="setback-state">
<ha-textfield
@@ -98,16 +93,17 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
id="setback_special_state"
@change=${this._changeSpecialState}
.disabled=${this._loading}
>
<ha-list-item selected> </ha-list-item>
${Object.entries(SpecialState).map(
([translationKey, value]) =>
html`<ha-list-item .value=${value}>
${this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_special_state.${translationKey}`
)}
</ha-list-item>`
.value=${this._setbackSpecialType}
clearable
.options=${Object.entries(SpecialState).map(
([translationKey, value]) => ({
value: value,
label: this.hass.localize(
`ui.panel.config.zwave_js.node_installer.capability_controls.thermostat_setback.setback_special_state.${translationKey}`
),
})
)}
>
</ha-select>
</div>
<div class="actions">
@@ -144,12 +140,12 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
true
)) as { setbackType: number; setbackState: number | SpecialState };
this._setbackTypeInput.value = String(setbackType);
this._setbackType = String(setbackType);
if (typeof setbackState === "number") {
this._setbackStateInput.value = String(setbackState);
this._setbackSpecialStateSelect.value = "";
this._setbackSpecialType = undefined;
} else {
this._setbackSpecialStateSelect.value = setbackState;
this._setbackSpecialType = setbackState;
}
} catch (err) {
this._error = this.hass.localize(
@@ -161,8 +157,9 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
this._loading = false;
}
private _changeSpecialState() {
this._disableSetbackState = !!this._setbackSpecialStateSelect.value;
private _changeSpecialState(ev: CustomEvent<{ value: string }>) {
this._disableSetbackState = !ev.detail.value;
this._setbackSpecialType = ev.detail.value;
}
private async _saveSetback(ev: CustomEvent) {
@@ -170,11 +167,11 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
button.progress = true;
this._error = undefined;
const setbackType = this._setbackTypeInput.value;
const setbackType = this._setbackType;
let setbackState: number | string = Number(this._setbackStateInput.value);
if (this._setbackSpecialStateSelect.value) {
setbackState = this._setbackSpecialStateSelect.value;
if (this._setbackSpecialType) {
setbackState = this._setbackSpecialType;
}
try {
@@ -204,6 +201,10 @@ class ZWaveJSCapabilityThermostatSetback extends LitElement {
this._loadSetback();
}
private _setbackTypeChanged(ev: CustomEvent<{ value: string }>) {
this._setbackType = ev.detail.value;
}
static styles = css`
:host {
display: flex;

View File

@@ -1,17 +1,16 @@
import { LitElement, html, css, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mdiCloseCircle } from "@mdi/js";
import "../../../../../components/ha-textfield";
import "../../../../../components/ha-select";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-button";
import "../../../../../components/ha-select";
import "../../../../../components/ha-spinner";
import "../../../../../components/ha-list-item";
import type { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-textfield";
import {
getZwaveNodeRawConfigParameter,
setZwaveNodeRawConfigParameter,
} from "../../../../../data/zwave_js";
import { fireEvent } from "../../../../../common/dom/fire_event";
import type { HomeAssistant } from "../../../../../types";
@customElement("zwave_js-custom-param")
class ZWaveJSCustomParam extends LitElement {
@@ -48,10 +47,8 @@ class ZWaveJSCustomParam extends LitElement {
)}
.value=${String(this._valueSize)}
@selected=${this._customValueSizeChanged}
.options=${["1", "2", "4"]}
>
<ha-list-item value="1">1</ha-list-item>
<ha-list-item value="2">2</ha-list-item>
<ha-list-item value="4">4</ha-list-item>
</ha-select>
<ha-textfield
.label=${this.hass.localize(
@@ -67,27 +64,33 @@ class ZWaveJSCustomParam extends LitElement {
)}
.value=${String(this._valueFormat)}
@selected=${this._customValueFormatChanged}
.options=${[
{
value: "0",
label: this.hass.localize(
"ui.panel.config.zwave_js.node_config.signed"
),
},
{
value: "1",
label: this.hass.localize(
"ui.panel.config.zwave_js.node_config.unsigned"
),
},
{
value: "2",
label: this.hass.localize(
"ui.panel.config.zwave_js.node_config.enumerated"
),
},
{
value: "3",
label: this.hass.localize(
"ui.panel.config.zwave_js.node_config.bitfield"
),
},
]}
>
<ha-list-item value="0"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.signed"
)}</ha-list-item
>
<ha-list-item value="1"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.unsigned"
)}</ha-list-item
>
<ha-list-item value="2"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.enumerated"
)}</ha-list-item
>
<ha-list-item value="3"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.bitfield"
)}</ha-list-item
>
</ha-select>
</div>
<div class="custom-config-buttons">
@@ -129,18 +132,16 @@ class ZWaveJSCustomParam extends LitElement {
);
}
private _customValueSizeChanged(ev: Event) {
this._valueSize =
this._tryParseNumber((ev.target as HTMLSelectElement).value) ?? 1;
private _customValueSizeChanged(ev: CustomEvent<{ value: string }>) {
this._valueSize = this._tryParseNumber(ev.detail.value) ?? 1;
}
private _customValueChanged(ev: Event) {
this._value = this._tryParseNumber((ev.target as HTMLInputElement).value);
}
private _customValueFormatChanged(ev: Event) {
this._valueFormat =
this._tryParseNumber((ev.target as HTMLSelectElement).value) ?? 0;
private _customValueFormatChanged(ev: CustomEvent<{ value: string }>) {
this._valueFormat = this._tryParseNumber(ev.detail.value) ?? 0;
}
private async _getCustomConfigValue() {

View File

@@ -5,7 +5,6 @@ import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import type { ZWaveJSLogConfig } from "../../../../../data/zwave_js";
import {
@@ -84,13 +83,18 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
)}
.value=${this._logConfig.level}
@selected=${this._dropdownSelected}
.options=${[
"error",
"warn",
"info",
"verbose",
"debug",
"silly",
].map((level) => ({
value: level,
label: capitalizeFirstLetter(level),
}))}
>
<ha-list-item value="error">Error</ha-list-item>
<ha-list-item value="warn">Warn</ha-list-item>
<ha-list-item value="info">Info</ha-list-item>
<ha-list-item value="verbose">Verbose</ha-list-item>
<ha-list-item value="debug">Debug</ha-list-item>
<ha-list-item value="silly">Silly</ha-list-item>
</ha-select>
`
: ""}
@@ -133,11 +137,11 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
);
}
private _dropdownSelected(ev) {
private _dropdownSelected(ev: CustomEvent<{ value: string }>) {
if (ev.target === undefined || this._logConfig === undefined) {
return;
}
const selected = ev.target.value;
const selected = ev.detail.value;
if (this._logConfig.level === selected) {
return;
}

View File

@@ -17,7 +17,6 @@ import type { HaProgressButton } from "../../../../../components/buttons/ha-prog
import "../../../../../components/ha-alert";
import "../../../../../components/ha-card";
import "../../../../../components/ha-generic-picker";
import "../../../../../components/ha-list-item";
import type { PickerComboBoxItem } from "../../../../../components/ha-picker-combo-box";
import "../../../../../components/ha-select";
import "../../../../../components/ha-selector/ha-selector-boolean";
@@ -374,7 +373,6 @@ class ZWaveJSNodeConfig extends LitElement {
return html`
${labelAndDescription}
<ha-select
fixedMenuPosition
.disabled=${!item.metadata.writeable}
.value=${item.value?.toString()}
.key=${id}
@@ -383,12 +381,13 @@ class ZWaveJSNodeConfig extends LitElement {
.propertyKey=${item.property_key}
@selected=${this._dropdownSelected}
.helper=${defaultLabel}
>
${Object.entries(item.metadata.states).map(
([key, entityState]) => html`
<ha-list-item .value=${key}>${entityState}</ha-list-item>
`
.options=${Object.entries(item.metadata.states).map(
([key, entityState]) => ({
value: key,
label: entityState,
})
)}
>
</ha-select>
`;
}
@@ -457,8 +456,8 @@ class ZWaveJSNodeConfig extends LitElement {
this._updateConfigParameter(ev.target, ev.detail.value ? 1 : 0);
}
private _dropdownSelected(ev) {
this._handleEnumeratedPickerValueChanged(ev, ev.target.value);
private _dropdownSelected(ev: CustomEvent<{ value: string }>) {
this._handleEnumeratedPickerValueChanged(ev, ev.detail.value);
}
private _pickerValueChanged(ev) {
@@ -469,7 +468,7 @@ class ZWaveJSNodeConfig extends LitElement {
if (ev.target === undefined || this._config![ev.target.key] === undefined) {
return;
}
if (this._config![ev.target.key].value?.toString() === value) {
if (this._config![ev.target.key].value === value) {
return;
}
this._setResult(ev.target.key, undefined);

View File

@@ -3,10 +3,8 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-dialog";
import "../../../components/ha-button";
import "../../../components/ha-list-item";
import "../../../components/ha-dialog";
import "../../../components/ha-select";
import "../../../components/ha-spinner";
import {
@@ -132,26 +130,18 @@ class MoveDatadiskDialog extends LitElement {
"ui.panel.config.storage.datadisk.select_device"
)}
@selected=${this._selectDevice}
@closed=${stopPropagation}
dialogInitialFocus
fixedMenuPosition
>
${this._disks.map(
(disk) =>
html`<ha-list-item twoline .value=${disk.id}>
<span>${disk.vendor} ${disk.model}</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.storage.datadisk.extra_information",
{
size: bytesToString(disk.size),
serial: disk.serial,
}
)}
</span>
</ha-list-item>`
)}
</ha-select>
.options=${this._disks.map((disk) => ({
value: disk.id,
label: `${disk.vendor} ${disk.model}`,
secondary: this.hass.localize(
"ui.panel.config.storage.datadisk.extra_information",
{
size: bytesToString(disk.size),
serial: disk.serial,
}
),
}))}
></ha-select>
<ha-button
slot="primaryAction"
@@ -174,8 +164,8 @@ class MoveDatadiskDialog extends LitElement {
`;
}
private _selectDevice(ev) {
this._selectedDevice = ev.target.value;
private _selectDevice(ev: CustomEvent<{ value: string }>): void {
this._selectedDevice = ev.detail.value;
}
private async _moveDatadisk() {

View File

@@ -1,19 +1,15 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { refine } from "superstruct";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { refine } from "superstruct";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-assist-pipeline-picker";
import type {
HaFormSchema,
SchemaUnion,
} from "../../../components/ha-form/types";
import "../../../components/ha-help-tooltip";
import "../../../components/ha-list-item";
import "../../../components/ha-navigation-picker";
import type { HaSelect } from "../../../components/ha-select";
import "../../../components/ha-service-control";
import type {
ActionConfig,
@@ -92,8 +88,6 @@ export class HuiActionEditor extends LitElement {
@property({ attribute: false })
public context?: ActionRelatedContext;
@query("ha-select") private _select!: HaSelect;
get _navigation_path(): string {
const config = this.config as NavigateActionConfig | undefined;
return config?.navigation_path || "";
@@ -136,15 +130,6 @@ export class HuiActionEditor extends LitElement {
]
);
protected updated(changedProperties: PropertyValues<typeof this>) {
super.updated(changedProperties);
if (changedProperties.has("defaultAction")) {
if (changedProperties.get("defaultAction") !== this.defaultAction) {
this._select.layoutOptions();
}
}
}
protected render() {
if (!this.hass) {
return nothing;
@@ -165,29 +150,28 @@ export class HuiActionEditor extends LitElement {
.configValue=${"action"}
@selected=${this._actionPicked}
.value=${action}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${[
{
value: "default",
label: `${this.hass!.localize(
"ui.panel.lovelace.editor.action-editor.actions.default_action"
)}
${
this.defaultAction
? ` (${this.hass!.localize(
`ui.panel.lovelace.editor.action-editor.actions.${this.defaultAction}`
).toLowerCase()})`
: ""
}`,
},
...actions.map((actn) => ({
value: actn,
label: this.hass!.localize(
`ui.panel.lovelace.editor.action-editor.actions.${actn}`
),
})),
]}
>
<ha-list-item value="default">
${this.hass!.localize(
"ui.panel.lovelace.editor.action-editor.actions.default_action"
)}
${this.defaultAction
? ` (${this.hass!.localize(
`ui.panel.lovelace.editor.action-editor.actions.${this.defaultAction}`
).toLowerCase()})`
: nothing}
</ha-list-item>
${actions.map(
(actn) => html`
<ha-list-item .value=${actn}>
${this.hass!.localize(
`ui.panel.lovelace.editor.action-editor.actions.${actn}`
)}
</ha-list-item>
`
)}
</ha-select>
${this.tooltipText
? html`
@@ -249,7 +233,7 @@ export class HuiActionEditor extends LitElement {
`;
}
private _actionPicked(ev): void {
private _actionPicked(ev: CustomEvent<{ value: string }>): void {
ev.stopPropagation();
if (!this.hass) {
return;
@@ -260,7 +244,7 @@ export class HuiActionEditor extends LitElement {
action = "perform-action";
}
const value = ev.target.value;
const value = ev.detail.value;
if (action === value) {
return;

View File

@@ -1,13 +1,18 @@
import { mdiClose, mdiContentDuplicate, mdiPencil } from "@mdi/js";
import {
mdiClose,
mdiContentDuplicate,
mdiPencil,
mdiPlaylistPlus,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-select";
import type { HaSelect } from "../../../components/ha-select";
import "../../../components/ha-svg-icon";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
@@ -47,8 +52,6 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
@property({ attribute: false }) public elements?: LovelaceElementConfig[];
@query("ha-select") private _select!: HaSelect;
protected render() {
if (!this.elements || !this.hass) {
return nothing;
@@ -104,26 +107,23 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
</div>
`
)}
<ha-select
fixedMenuPosition
naturalMenuWidth
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.picture-elements.new_element"
)}
.value=${""}
@closed=${stopPropagation}
@selected=${this._addElement}
>
<ha-dropdown @wa-select=${this._addElement}>
<ha-button size="small" slot="trigger" appearance="filled">
<ha-svg-icon slot="start" .path=${mdiPlaylistPlus}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.card.picture-elements.new_element"
)}
</ha-button>
${elementTypes.map(
(element) => html`
<ha-list-item .value=${element}
>${this.hass?.localize(
<ha-dropdown-item .value=${element}>
${this.hass?.localize(
`ui.panel.lovelace.editor.card.picture-elements.element_types.${element}`
)}</ha-list-item
>
) || element}
</ha-dropdown-item>
`
)}
</ha-select>
</ha-dropdown>
</div>
`;
}
@@ -177,8 +177,8 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
return element.title ?? "Unknown type";
}
private async _addElement(ev): Promise<void> {
const value = ev.target!.value;
private async _addElement(ev: HaDropdownSelectEvent): Promise<void> {
const value = ev.detail.item.value;
if (value === "") {
return;
}
@@ -191,7 +191,6 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
)
);
fireEvent(this, "elements-changed", { elements: newElements });
this._select.select(-1);
}
private _removeRow(ev: CustomEvent): void {
@@ -269,10 +268,6 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
}
ha-select {
width: 100%;
}
`;
}

View File

@@ -2,13 +2,11 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-icon";
import "../../../../components/ha-list";
import "../../../../components/ha-list-item";
import "../../../../components/ha-radio-list-item";
import "../../../../components/ha-select";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
@@ -82,29 +80,23 @@ export class HuiDialogSelectView extends LitElement {
.disabled=${!this._dashboards.length}
.value=${this._urlPath || defaultPanel}
@selected=${this._dashboardChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.options=${this._dashboards
.map((dashboard) => ({
value: dashboard.url_path,
label: `${dashboard.title}${dashboard.id === "lovelace" ? ` (${this.hass.localize("ui.common.default")})` : ""}`,
disabled: dashboard.mode !== "storage",
}))
.sort((a, b) =>
a.value === "lovelace"
? -1
: b.value === "lovelace"
? 1
: a.label.localeCompare(b.label)
)}
dialogInitialFocus
>
<ha-list-item
value="lovelace"
.disabled=${(this.hass.panels.lovelace?.config as any)?.mode ===
"yaml"}
>
Default
</ha-list-item>
${this._dashboards.map(
(dashboard) => html`
<ha-list-item
.disabled=${dashboard.mode !== "storage"}
.value=${dashboard.url_path}
>${dashboard.title}</ha-list-item
>
`
)}
</ha-select>`
: ""}
: nothing}
${!this._config || (this._config.views || []).length < 1
? html`<ha-alert alert-type="error"
>${this.hass.localize(
@@ -142,7 +134,7 @@ export class HuiDialogSelectView extends LitElement {
})}
</ha-list>
`
: ""}
: nothing}
<ha-button
slot="primaryAction"
@click=${this.closeDialog}
@@ -167,8 +159,8 @@ export class HuiDialogSelectView extends LitElement {
this._params!.dashboards || (await fetchDashboards(this.hass));
}
private async _dashboardChanged(ev) {
let urlPath: string | null = ev.target.value;
private async _dashboardChanged(ev: CustomEvent<{ value: string }>) {
let urlPath: string | null = ev.detail.value;
if (urlPath === this._urlPath) {
return;
}

View File

@@ -1,8 +1,6 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-list-item";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { forwardHaptic } from "../../../data/haptics";
@@ -70,17 +68,8 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
.disabled=${
stateObj.state === UNAVAILABLE /* UNKNOWN state is allowed */
}
naturalMenuWidth
@selected=${this._selectedChanged}
@click=${stopPropagation}
@closed=${stopPropagation}
>
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) =>
html`<ha-list-item .value=${option}>${option}</ha-list-item>`
)
: ""}
</ha-select>
</hui-generic-entity-row>
`;
@@ -97,11 +86,11 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
}
`;
private _selectedChanged(ev): void {
private _selectedChanged(ev: CustomEvent<{ value: string }>): void {
const stateObj = this.hass!.states[
this._config!.entity
] as InputSelectEntity;
const option = ev.target.value;
const option = ev.detail.value;
if (
option === stateObj.state ||
!stateObj.attributes.options.includes(option)

View File

@@ -1,8 +1,6 @@
import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-list-item";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { forwardHaptic } from "../../../data/haptics";
@@ -22,6 +20,8 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
@state() private _config?: EntitiesCardEntityConfig;
@state() private _selectedEntityRow?: string;
public setConfig(config: EntitiesCardEntityConfig): void {
if (!config || !config.entity) {
throw new Error("Entity must be specified");
@@ -65,23 +65,14 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
>
<ha-select
.label=${name}
.value=${stateObj.state}
.options=${stateObj.attributes.options}
.value=${this._selectedEntityRow || stateObj.state}
.options=${stateObj.attributes.options?.map((option) => ({
value: option,
label: this.hass!.formatEntityState(stateObj, option),
}))}
.disabled=${stateObj.state === UNAVAILABLE}
naturalMenuWidth
@action=${this._handleAction}
@click=${stopPropagation}
@closed=${stopPropagation}
@selected=${this._handleAction}
>
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) => html`
<ha-list-item .value=${option}>
${this.hass!.formatEntityState(stateObj, option)}
</ha-list-item>
`
)
: ""}
</ha-select>
</hui-generic-entity-row>
`;
@@ -98,10 +89,10 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
}
`;
private _handleAction(ev): void {
private _handleAction(ev: CustomEvent<{ value: string }>): void {
const stateObj = this.hass!.states[this._config!.entity] as SelectEntity;
const option = ev.target.value;
const option = ev.detail.value;
if (
option === stateObj.state ||
@@ -120,9 +111,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
setTimeout(() => {
const newStateObj = this.hass!.states[this._config!.entity];
if (newStateObj === stateObj) {
const select = this.shadowRoot?.querySelector("ha-select");
const index = select?.options.indexOf(stateObj.state) ?? -1;
select?.select(index);
this._selectedEntityRow = stateObj.state;
}
}, 2000)
);

View File

@@ -1,9 +1,10 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import "../../components/ha-divider";
import "../../components/ha-dropdown-item";
import "../../components/ha-icon";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import "../../components/ha-spinner";
@@ -46,13 +47,15 @@ class HaPickDashboardRow extends LitElement {
.label=${this.hass.localize(
"ui.panel.profile.dashboard.dropdown_label"
)}
.value=${value}
.value=${this._valueLabel(value)}
@selected=${this._dashboardChanged}
naturalMenuWidth
>
<ha-list-item .value=${USE_SYSTEM_VALUE}>
<ha-dropdown-item
.value=${USE_SYSTEM_VALUE}
class=${value === USE_SYSTEM_VALUE ? "selected" : ""}
>
${this.hass.localize("ui.panel.profile.dashboard.system")}
</ha-list-item>
</ha-dropdown-item>
<ha-divider></ha-divider>
${PANEL_DASHBOARDS.map((panel) => {
const panelInfo = this.hass.panels[panel] as
@@ -62,13 +65,16 @@ class HaPickDashboardRow extends LitElement {
return nothing;
}
return html`
<ha-list-item value=${panelInfo.url_path} graphic="icon">
<ha-dropdown-item
value=${panelInfo.url_path}
class=${value === panelInfo.url_path ? "selected" : ""}
>
<ha-icon
slot="graphic"
slot="icon"
.icon=${getPanelIcon(panelInfo)}
></ha-icon>
${getPanelTitle(this.hass, panelInfo)}
</ha-list-item>
</ha-dropdown-item>
`;
})}
${this._dashboards.length
@@ -82,16 +88,18 @@ class HaPickDashboardRow extends LitElement {
return "";
}
return html`
<ha-list-item
<ha-dropdown-item
.value=${dashboard.url_path}
graphic="icon"
class=${value === dashboard.url_path
? "selected"
: ""}
>
<ha-icon
slot="graphic"
slot="icon"
.icon=${dashboard.icon || "mdi:view-dashboard"}
></ha-icon>
${dashboard.title}
</ha-list-item>
</ha-dropdown-item>
`;
})}
`
@@ -109,8 +117,8 @@ class HaPickDashboardRow extends LitElement {
this._dashboards = await fetchDashboards(this.hass);
}
private _dashboardChanged(ev) {
const value = ev.target.value as string;
private _dashboardChanged(ev: CustomEvent<{ value: string }>): void {
const value = ev.detail.value;
if (!value) {
return;
}
@@ -124,6 +132,24 @@ class HaPickDashboardRow extends LitElement {
});
}
private _valueLabel = memoizeOne((value: string) => {
if (value === USE_SYSTEM_VALUE) {
return this.hass.localize("ui.panel.profile.dashboard.system");
}
if (value === "lovelace") {
return this.hass.localize("ui.panel.profile.dashboard.lovelace");
}
const panelInfo = this.hass.panels[value] as PanelInfo | undefined;
if (panelInfo) {
return getPanelTitle(this.hass, panelInfo);
}
const dashboard = this._dashboards?.find((dash) => dash.url_path === value);
if (dashboard) {
return dashboard.title;
}
return value;
});
static get styles(): CSSResultGroup {
return [
css`
@@ -134,6 +160,11 @@ class HaPickDashboardRow extends LitElement {
height: 56px;
width: 200px;
}
ha-select {
display: block;
width: 100%;
}
`,
];
}

View File

@@ -1,10 +1,9 @@
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { formatDateNumeric } from "../../common/datetime/format_date";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-card";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import { DateFormat } from "../../data/translation";
@@ -33,33 +32,36 @@ class DateFormatRow extends LitElement {
.disabled=${this.hass.locale === undefined}
.value=${this.hass.locale.date_format}
@selected=${this._handleFormatSelection}
naturalMenuWidth
>
${Object.values(DateFormat).map((format) => {
const formattedDate = formatDateNumeric(
.options=${Object.values(DateFormat).map((format) => ({
value: format.toString(),
label: this.hass.localize(
`ui.panel.profile.date_format.formats.${format}`
),
secondary: formatDateNumeric(
date,
{
...this.hass.locale,
date_format: format,
},
this.hass.config
);
const value = this.hass.localize(
`ui.panel.profile.date_format.formats.${format}`
);
return html`<ha-list-item .value=${format} twoline>
<span>${value}</span>
<span slot="secondary">${formattedDate}</span>
</ha-list-item>`;
})}
),
}))}
>
</ha-select>
</ha-settings-row>
`;
}
private async _handleFormatSelection(ev) {
fireEvent(this, "hass-date-format-select", ev.target.value);
private async _handleFormatSelection(ev: CustomEvent<{ value: string }>) {
fireEvent(this, "hass-date-format-select", ev.detail.value as DateFormat);
}
static styles = css`
ha-select {
display: block;
width: 100%;
}
`;
}
declare global {

View File

@@ -1,9 +1,8 @@
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { firstWeekday } from "../../common/datetime/first_weekday";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import { FirstWeekday } from "../../data/translation";
@@ -31,43 +30,44 @@ class FirstWeekdayRow extends LitElement {
.disabled=${this.hass.locale === undefined}
.value=${this.hass.locale.first_weekday}
@selected=${this._handleFormatSelection}
naturalMenuWidth
>
${[
.options=${[
FirstWeekday.language,
FirstWeekday.monday,
FirstWeekday.saturday,
FirstWeekday.sunday,
].map((day) => {
const value = this.hass.localize(
].map((day) => ({
value: day.toString(),
label: this.hass.localize(
`ui.panel.profile.first_weekday.values.${day}`
);
const twoLine = day === FirstWeekday.language;
return html`
<ha-list-item .value=${day} .twoline=${twoLine}>
<span>${value}</span>
${twoLine
? html`
<span slot="secondary"
>${this.hass.localize(
`ui.panel.profile.first_weekday.values.${firstWeekday(
this.hass.locale
)}`
)}</span
>
`
: ""}
</ha-list-item>
`;
})}
</ha-select>
),
secondary:
day === FirstWeekday.language
? this.hass.localize(
`ui.panel.profile.first_weekday.values.${firstWeekday(
this.hass.locale
)}`
)
: undefined,
}))}
></ha-select>
</ha-settings-row>
`;
}
private async _handleFormatSelection(ev) {
fireEvent(this, "hass-first-weekday-select", ev.target.value);
private async _handleFormatSelection(ev: CustomEvent<{ value: string }>) {
fireEvent(
this,
"hass-first-weekday-select",
ev.detail.value as FirstWeekday
);
}
static styles = css`
ha-select {
display: block;
width: 100%;
}
`;
}
declare global {

View File

@@ -1,10 +1,9 @@
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { formatNumber } from "../../common/number/format_number";
import "../../components/ha-card";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import { NumberFormat } from "../../data/translation";
@@ -32,34 +31,42 @@ class NumberFormatRow extends LitElement {
.disabled=${this.hass.locale === undefined}
.value=${this.hass.locale.number_format}
@selected=${this._handleFormatSelection}
naturalMenuWidth
>
${Object.values(NumberFormat).map((format) => {
const formattedNumber = formatNumber(1234567.89, {
...this.hass.locale,
number_format: format,
});
const value = this.hass.localize(
.options=${Object.values(NumberFormat).map((format) => {
const label = this.hass.localize(
`ui.panel.profile.number_format.formats.${format}`
);
const twoLine = value.slice(value.length - 2) !== "89"; // Display explicit number formats on one line
return html`
<ha-list-item .value=${format} .twoline=${twoLine}>
<span>${value}</span>
${twoLine
? html`<span slot="secondary">${formattedNumber}</span>`
: ""}
</ha-list-item>
`;
return {
value: format,
label,
secondary:
label.slice(label.length - 2) !== "89"
? formatNumber(1234567.89, {
...this.hass.locale,
number_format: format,
})
: undefined,
};
})}
>
</ha-select>
</ha-settings-row>
`;
}
private async _handleFormatSelection(ev) {
fireEvent(this, "hass-number-format-select", ev.target.value);
private async _handleFormatSelection(ev: CustomEvent<{ value: string }>) {
fireEvent(
this,
"hass-number-format-select",
ev.detail.value as NumberFormat
);
}
static styles = css`
ha-select {
display: block;
width: 100%;
}
`;
}
declare global {

View File

@@ -3,23 +3,22 @@ import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { normalizeLuminance } from "../../common/color/palette";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-formfield";
import "../../components/ha-list-item";
import "../../components/ha-radio";
import "../../components/ha-button";
import "../../components/ha-formfield";
import "../../components/ha-radio";
import type { HaRadio } from "../../components/ha-radio";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import "../../components/ha-textfield";
import {
DefaultAccentColor,
DefaultPrimaryColor,
} from "../../resources/theme/color/color.globals";
import {
saveThemePreferences,
subscribeThemePreferences,
} from "../../data/theme";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import {
DefaultAccentColor,
DefaultPrimaryColor,
} from "../../resources/theme/color/color.globals";
import type { HomeAssistant, ThemeSettings } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { clearSelectedThemeState } from "../../util/ha-pref-storage";
@@ -93,19 +92,18 @@ export class HaPickThemeRow extends SubscribeMixin(LitElement) {
.disabled=${!hasThemes}
.value=${this.hass.selectedTheme?.theme || USE_DEFAULT_THEME}
@selected=${this._handleThemeSelection}
naturalMenuWidth
.options=${[
{
value: USE_DEFAULT_THEME,
label: this.hass.localize("ui.panel.profile.themes.use_default"),
},
{ value: HOME_ASSISTANT_THEME, label: "Home Assistant" },
...this._themeNames.map((theme) => ({
value: theme,
label: theme,
})),
]}
>
<ha-list-item .value=${USE_DEFAULT_THEME}>
${this.hass.localize("ui.panel.profile.themes.use_default")}
</ha-list-item>
<ha-list-item .value=${HOME_ASSISTANT_THEME}>
Home Assistant
</ha-list-item>
${this._themeNames.map(
(theme) => html`
<ha-list-item .value=${theme}>${theme}</ha-list-item>
`
)}
</ha-select>
</ha-settings-row>
${curTheme === HOME_ASSISTANT_THEME ||
@@ -261,8 +259,8 @@ export class HaPickThemeRow extends SubscribeMixin(LitElement) {
fireEvent(this, "settheme", { dark });
}
private _handleThemeSelection(ev) {
const theme = ev.target.value;
private _handleThemeSelection(ev: CustomEvent<{ value: string }>) {
const theme = ev.detail.value;
if (theme === this.hass.selectedTheme?.theme) {
return;
}
@@ -334,6 +332,11 @@ export class HaPickThemeRow extends SubscribeMixin(LitElement) {
flex-grow: 1;
margin: 0 4px;
}
ha-select {
display: block;
width: 100%;
}
`;
}

View File

@@ -1,10 +1,9 @@
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { formatTime } from "../../common/datetime/format_time";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-card";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import { TimeFormat } from "../../data/translation";
@@ -33,33 +32,36 @@ class TimeFormatRow extends LitElement {
.disabled=${this.hass.locale === undefined}
.value=${this.hass.locale.time_format}
@selected=${this._handleFormatSelection}
naturalMenuWidth
>
${Object.values(TimeFormat).map((format) => {
const formattedTime = formatTime(
.options=${Object.values(TimeFormat).map((format) => ({
value: format.toString(),
label: this.hass.localize(
`ui.panel.profile.time_format.formats.${format}`
),
secondary: formatTime(
date,
{
...this.hass.locale,
time_format: format,
},
this.hass.config
);
const value = this.hass.localize(
`ui.panel.profile.time_format.formats.${format}`
);
return html`<ha-list-item .value=${format} twoline>
<span>${value}</span>
<span slot="secondary">${formattedTime}</span>
</ha-list-item>`;
})}
),
}))}
>
</ha-select>
</ha-settings-row>
`;
}
private async _handleFormatSelection(ev) {
fireEvent(this, "hass-time-format-select", ev.target.value);
private async _handleFormatSelection(ev: CustomEvent<{ value: string }>) {
fireEvent(this, "hass-time-format-select", ev.detail.value as TimeFormat);
}
static styles = css`
ha-select {
display: block;
width: 100%;
}
`;
}
declare global {

View File

@@ -1,11 +1,10 @@
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { formatDateTimeNumeric } from "../../common/datetime/format_date_time";
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-card";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import { TimeZone } from "../../data/translation";
@@ -34,40 +33,42 @@ class TimeZoneRow extends LitElement {
.disabled=${this.hass.locale === undefined}
.value=${this.hass.locale.time_zone}
@selected=${this._handleFormatSelection}
naturalMenuWidth
>
${Object.values(TimeZone).map((format) => {
const formattedTime = formatDateTimeNumeric(
.options=${Object.values(TimeZone).map((format) => ({
value: format.toString(),
label: this.hass.localize(
`ui.panel.profile.time_zone.options.${format}`,
{
timezone: resolveTimeZone(
format,
this.hass.config.time_zone
).replace("_", " "),
}
),
secondary: formatDateTimeNumeric(
date,
{
...this.hass.locale,
time_zone: format,
},
this.hass.config
);
return html`<ha-list-item .value=${format} twoline>
<span
>${this.hass.localize(
`ui.panel.profile.time_zone.options.${format}`,
{
timezone: resolveTimeZone(
format,
this.hass.config.time_zone
).replace("_", " "),
}
)}</span
>
<span slot="secondary">${formattedTime}</span>
</ha-list-item>`;
})}
),
}))}
>
</ha-select>
</ha-settings-row>
`;
}
private async _handleFormatSelection(ev) {
fireEvent(this, "hass-time-zone-select", ev.target.value);
private async _handleFormatSelection(ev: CustomEvent<{ value: string }>) {
fireEvent(this, "hass-time-zone-select", ev.detail.value as TimeZone);
}
static styles = css`
ha-select {
display: block;
width: 100%;
}
`;
}
declare global {

View File

@@ -2,9 +2,9 @@ import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { computeStateName } from "../common/entity/compute_state_name";
import type { HaDropdownSelectEvent } from "../components/ha-dropdown";
import "../components/entity/state-badge";
import "../components/ha-control-select-menu";
import type { HaDropdownSelectEvent } from "../components/ha-dropdown";
import { UNAVAILABLE } from "../data/entity/entity";
import type { InputSelectEntity } from "../data/input_select";
import { setInputSelectOption } from "../data/input_select";

View File

@@ -2,9 +2,9 @@ import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { computeStateName } from "../common/entity/compute_state_name";
import type { HaDropdownSelectEvent } from "../components/ha-dropdown";
import "../components/entity/state-badge";
import "../components/ha-control-select-menu";
import type { HaDropdownSelectEvent } from "../components/ha-dropdown";
import { UNAVAILABLE } from "../data/entity/entity";
import type { SelectEntity } from "../data/select";
import { setSelectOption } from "../data/select";

View File

@@ -37,21 +37,11 @@ declare global {
"hass-language-select": {
language: string;
};
"hass-number-format-select": {
number_format: NumberFormat;
};
"hass-time-format-select": {
time_format: TimeFormat;
};
"hass-date-format-select": {
date_format: DateFormat;
};
"hass-time-zone-select": {
time_zone: TimeZone;
};
"hass-first-weekday-select": {
first_weekday: FirstWeekday;
};
"hass-number-format-select": NumberFormat;
"hass-time-format-select": TimeFormat;
"hass-date-format-select": DateFormat;
"hass-time-zone-select": TimeZone;
"hass-first-weekday-select": FirstWeekday;
"translations-updated": undefined;
}
}

View File

@@ -2903,7 +2903,7 @@ __metadata:
languageName: node
linkType: hard
"@material/mwc-menu@npm:0.27.0, @material/mwc-menu@npm:^0.27.0":
"@material/mwc-menu@npm:^0.27.0":
version: 0.27.0
resolution: "@material/mwc-menu@npm:0.27.0"
dependencies:
@@ -9103,7 +9103,6 @@ __metadata:
"@material/mwc-icon-button": "npm:0.27.0"
"@material/mwc-linear-progress": "npm:0.27.0"
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch"
"@material/mwc-menu": "npm:0.27.0"
"@material/mwc-radio": "npm:0.27.0"
"@material/mwc-select": "npm:0.27.0"
"@material/mwc-snackbar": "npm:0.27.0"