diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index b739bba20d..caa51098cb 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -277,13 +277,13 @@ export class HaEntityPicker extends LitElement { .disabled=${this.disabled} .autofocus=${this.autofocus} .allowCustomValue=${this.allowCustomEntity} - .label=${this.label} .required=${this.required} + .label=${this.label} + .placeholder=${placeholder} .helper=${this.helper} + .value=${this.addButton ? undefined : this.value} .searchLabel=${this.searchLabel} .notFoundLabel=${this._notFoundLabel} - .placeholder=${placeholder} - .value=${this.addButton ? undefined : this.value} .rowRenderer=${this._rowRenderer} .getItems=${this._getItems} .getAdditionalItems=${this._getAdditionalItems} @@ -291,6 +291,7 @@ export class HaEntityPicker extends LitElement { .searchFn=${this._searchFn} .valueRenderer=${this._valueRenderer} .searchKeys=${entityComboBoxKeys} + use-top-label .addButtonLabel=${this.addButton ? this.hass.localize("ui.components.entity.entity-picker.add") : undefined} diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index e75f170e6f..382cdc72d9 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -471,14 +471,14 @@ export class HaStatisticPicker extends LitElement { .hass=${this.hass} .autofocus=${this.autofocus} .allowCustomValue=${this.allowCustomEntity} - .label=${this.label} .disabled=${this.disabled} + .label=${this.label} + .placeholder=${placeholder} + .value=${this.value} .notFoundLabel=${this._notFoundLabel} .emptyLabel=${this.hass.localize( "ui.components.statistic-picker.no_statistics" )} - .placeholder=${placeholder} - .value=${this.value} .rowRenderer=${this._rowRenderer} .getItems=${this._getItems} .getAdditionalItems=${this._getAdditionalItems} diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index ee43bdcd71..ea3db8f559 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -61,6 +61,11 @@ class HaAddonPicker extends LitElement { } protected render() { + const label = + this.label === undefined && this.hass + ? this.hass.localize("ui.components.addon-picker.addon") + : this.label; + if (this._error) { return html`${this._error}`; } @@ -72,14 +77,11 @@ class HaAddonPicker extends LitElement { ; - @property({ attribute: false }) - public valueRenderer?: PickerValueRenderer; - @property({ attribute: false }) public searchFn?: PickerComboBoxSearchFn; @@ -119,7 +96,8 @@ export class HaGenericPicker extends LitElement { @property({ attribute: "selected-section" }) public selectedSection?: string; - @property({ attribute: "unknown-item-text" }) public unknownItemText?: string; + @property({ type: Boolean, attribute: "use-top-label" }) + public useTopLabel = false; @query(".container") private _containerElement?: HTMLDivElement; @@ -150,11 +128,13 @@ export class HaGenericPicker extends LitElement { private _unsubscribeTinyKeys?: () => void; protected render() { - return html` - ${this.label - ? html`` - : nothing} -
+ // Only show label if it's not a top label and there is a value. + const label = this.useTopLabel && this.value ? undefined : this.label; + + return html`
+ ${this.useTopLabel && this.label + ? html`` + : nothing}
${this.addButtonLabel && !this.value @@ -180,8 +160,10 @@ export class HaGenericPicker extends LitElement { @click=${this.open} @clear=${this._clear} .icon=${this.icon} - .showLabel=${this.showLabel} + .image=${this.image} + .label=${label} .placeholder=${this.placeholder} + .helper=${this.helper} .value=${this.value} .valueRenderer=${this.valueRenderer} .required=${this.required} @@ -189,6 +171,7 @@ export class HaGenericPicker extends LitElement { .invalid=${this.invalid} .hideClearIcon=${this.hideClearIcon} > + `}
@@ -227,8 +210,7 @@ export class HaGenericPicker extends LitElement { ` : nothing}
- ${this._renderHelper()} - `; + ${this._renderHelper()}`; } private _renderComboBox(dialogMode = false) { @@ -321,7 +303,7 @@ export class HaGenericPicker extends LitElement { this._newValue = value; } - private _clear(e) { + private _clear(e: CustomEvent) { e.stopPropagation(); this._setValue(undefined); } diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index a8fde869f1..f7fcf8a813 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -113,7 +113,6 @@ export class HaIconPicker extends LitElement { + `; } diff --git a/src/components/ha-language-picker.ts b/src/components/ha-language-picker.ts index d7e4d25c9f..4e01181b34 100644 --- a/src/components/ha-language-picker.ts +++ b/src/components/ha-language-picker.ts @@ -116,6 +116,11 @@ export class HaLanguagePicker extends LitElement { > `; protected render() { + const label = + this.label ?? + (this.hass?.localize("ui.components.language-picker.language") || + "Language"); + const value = this.value ?? (this.required && !this.disabled ? this._getItems()[0].id : this.value); @@ -129,10 +134,7 @@ export class HaLanguagePicker extends LitElement { .emptyLabel=${this.hass?.localize( "ui.components.language-picker.no_languages" ) || "No languages available"} - .placeholder=${this.label ?? - (this.hass?.localize("ui.components.language-picker.language") || - "Language")} - show-label + .label=${label} .value=${value} .valueRenderer=${this._valueRenderer} .disabled=${this.disabled} diff --git a/src/components/ha-picker-field.ts b/src/components/ha-picker-field.ts index 5296863480..cf13ba0ea8 100644 --- a/src/components/ha-picker-field.ts +++ b/src/components/ha-picker-field.ts @@ -9,7 +9,9 @@ import { type TemplateResult, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { ifDefined } from "lit/directives/if-defined"; import { fireEvent } from "../common/dom/fire_event"; +import { PickerMixin } from "../mixins/picker-mixin"; import { localizeContext } from "../data/context"; import type { HomeAssistant } from "../types"; import "./ha-combo-box-item"; @@ -26,32 +28,7 @@ declare global { export type PickerValueRenderer = (value: string) => TemplateResult<1>; @customElement("ha-picker-field") -export class HaPickerField extends LitElement { - @property({ type: Boolean }) public disabled = false; - - @property({ type: Boolean }) public required = false; - - @property() public value?: string; - - @property() public icon?: string; - - @property() public helper?: string; - - @property() public placeholder?: string; - - @property({ type: Boolean, reflect: true }) public unknown = false; - - @property({ attribute: "unknown-item-text" }) public unknownItemText?: string; - - @property({ attribute: "hide-clear-icon", type: Boolean }) - public hideClearIcon = false; - - @property({ attribute: "show-label", type: Boolean }) - public showLabel = false; - - @property({ attribute: false }) - public valueRenderer?: PickerValueRenderer; - +export class HaPickerField extends PickerMixin(LitElement) { @property({ type: Boolean, reflect: true }) public invalid = false; @query("ha-combo-box-item", true) public item!: HaComboBoxItem; @@ -66,15 +43,17 @@ export class HaPickerField extends LitElement { } protected render() { - const hasValue = !!this.value?.length; + const hasValue = !!this.value; const showClearIcon = !!this.value && !this.required && !this.disabled && !this.hideClearIcon; + const placeholderText = this.placeholder ?? this.label; + const overlineLabel = - this.showLabel && hasValue && this.placeholder + this.label && hasValue ? html`${this.placeholder}${this.required ? " *" : ""}${this.label}${this.required ? " *" : ""}` : nothing; @@ -82,17 +61,30 @@ export class HaPickerField extends LitElement { ? this.valueRenderer ? this.valueRenderer(this.value ?? "") : html`${this.value}` - : this.placeholder + : placeholderText ? html` - ${this.placeholder}${this.required ? " *" : ""} + ${placeholderText}${this.required ? " *" : ""} ` : nothing; return html` - - ${this.icon - ? html`` - : nothing} + + ${this.image + ? html`${this.label` + : this.icon + ? html`` + : html``} ${overlineLabel}${headlineContent} ${this.unknown ? html`
@@ -119,7 +111,7 @@ export class HaPickerField extends LitElement { `; } - private _clear(e) { + private _clear(e: CustomEvent) { e.stopPropagation(); fireEvent(this, "clear"); } diff --git a/src/components/ha-selector/ha-selector-entity.ts b/src/components/ha-selector/ha-selector-entity.ts index d05e470a83..7622ba394c 100644 --- a/src/components/ha-selector/ha-selector-entity.ts +++ b/src/components/ha-selector/ha-selector-entity.ts @@ -66,12 +66,12 @@ export class HaEntitySelector extends LitElement { .hass=${this.hass} .value=${this.value} .label=${this.label} + .placeholder=${this.placeholder} .helper=${this.helper} .includeEntities=${this.selector.entity?.include_entities} .excludeEntities=${this.selector.entity?.exclude_entities} .entityFilter=${this._filterEntities} .createDomains=${this._createDomains} - .placeholder=${this.placeholder} .disabled=${this.disabled} .required=${this.required} allow-custom-entity @@ -83,13 +83,13 @@ export class HaEntitySelector extends LitElement { .hass=${this.hass} .value=${this.value} .label=${this.label} + .placeholder=${this.placeholder} .helper=${this.helper} .includeEntities=${this.selector.entity.include_entities} .excludeEntities=${this.selector.entity.exclude_entities} .reorder=${this.selector.entity.reorder ?? false} .entityFilter=${this._filterEntities} .createDomains=${this._createDomains} - .placeholder=${this.placeholder} .disabled=${this.disabled} .required=${this.required} > diff --git a/src/components/ha-selector/ha-selector-icon.ts b/src/components/ha-selector/ha-selector-icon.ts index c325055911..f31dbc261c 100644 --- a/src/components/ha-selector/ha-selector-icon.ts +++ b/src/components/ha-selector/ha-selector-icon.ts @@ -52,7 +52,7 @@ export class HaIconSelector extends LitElement { ${!placeholder && stateObj ? html` diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 67903b946e..92bebfba9c 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -393,6 +393,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { .rowRenderer=${this._renderRow} .getItems=${this._getItems} @value-changed=${this._targetPicked} + use-top-label .addButtonLabel=${this.hass.localize( "ui.components.target-picker.add_target" )} diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts index 49f6d6205a..ff789e8397 100644 --- a/src/components/user/ha-user-picker.ts +++ b/src/components/user/ha-user-picker.ts @@ -132,9 +132,9 @@ class HaUserPicker extends LitElement { .hass=${this.hass} .autofocus=${this.autofocus} .label=${this.label} - .notFoundLabel=${this._notFoundLabel} .placeholder=${placeholder} .value=${this.value} + .notFoundLabel=${this._notFoundLabel} .getItems=${this._getItems} .valueRenderer=${this._valueRenderer} .rowRenderer=${this._rowRenderer} diff --git a/src/mixins/picker-mixin.ts b/src/mixins/picker-mixin.ts new file mode 100644 index 0000000000..b2e6912576 --- /dev/null +++ b/src/mixins/picker-mixin.ts @@ -0,0 +1,38 @@ +import type { ReactiveElement } from "lit"; +import { property } from "lit/decorators"; +import type { Constructor } from "../types"; +import type { PickerValueRenderer } from "../components/ha-picker-field"; + +export const PickerMixin = >( + superClass: T +) => { + class PickerFieldClass extends superClass { + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = false; + + @property() public icon?: string; + + @property() public image?: string; + + @property() public label?: string; + + @property() public placeholder?: string; + + @property() public helper?: string; + + @property() public value?: string; + + @property({ type: Boolean, reflect: true }) public unknown = false; + + @property({ attribute: "unknown-item-text" }) + public unknownItemText?: string; + + @property({ attribute: "hide-clear-icon", type: Boolean }) + public hideClearIcon = false; + + @property({ attribute: false }) + public valueRenderer?: PickerValueRenderer; + } + return PickerFieldClass; +}; diff --git a/src/panels/config/areas/dialog-floor-registry-detail.ts b/src/panels/config/areas/dialog-floor-registry-detail.ts index fa23c3d096..dcb4a5430e 100644 --- a/src/panels/config/areas/dialog-floor-registry-detail.ts +++ b/src/panels/config/areas/dialog-floor-registry-detail.ts @@ -160,7 +160,7 @@ class DialogFloorDetail extends LitElement { ${!this._icon ? html` ` diff --git a/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts b/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts index cd6137dea3..8ed58cacfb 100644 --- a/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts +++ b/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts @@ -148,7 +148,7 @@ class DialogAutomationSave extends LitElement implements HassDialog { @value-changed=${this._iconChanged} > @@ -176,8 +176,10 @@ class DialogAutomationSave extends LitElement implements HassDialog { id="category" .hass=${this.hass} .scope=${this._params.domain} + .label=${this.hass.localize( + "ui.components.category-picker.category" + )} .value=${this._entryUpdates.category} - show-label @value-changed=${this._registryEntryChanged} >` : nothing} @@ -194,7 +196,6 @@ class DialogAutomationSave extends LitElement implements HassDialog { id="area" .hass=${this.hass} .value=${this._entryUpdates.area} - show-label @value-changed=${this._registryEntryChanged} >` : nothing} diff --git a/src/panels/config/category/dialog-assign-category.ts b/src/panels/config/category/dialog-assign-category.ts index 1243b967ee..b15f38d72b 100644 --- a/src/panels/config/category/dialog-assign-category.ts +++ b/src/panels/config/category/dialog-assign-category.ts @@ -65,6 +65,9 @@ class DialogAssignCategory extends LitElement { diff --git a/src/panels/config/category/ha-category-picker.ts b/src/panels/config/category/ha-category-picker.ts index 27f7732243..2f91d54390 100644 --- a/src/panels/config/category/ha-category-picker.ts +++ b/src/panels/config/category/ha-category-picker.ts @@ -39,9 +39,6 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { @property({ type: Boolean, attribute: "no-add" }) public noAdd = false; - @property({ type: Boolean, attribute: "show-label" }) - public showLabel = false; - @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public required = false; @@ -183,10 +180,6 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { }; protected render(): TemplateResult { - const placeholder = - this.placeholder ?? - this.hass.localize("ui.components.category-picker.category"); - const valueRenderer = this._computeValueRenderer(this._categories); return html` @@ -194,13 +187,12 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) { .hass=${this.hass} .autofocus=${this.autofocus} .label=${this.label} + .placeholder=${this.placeholder} + .value=${this.value} .notFoundLabel=${this._notFoundLabel} .emptyLabel=${this.hass.localize( "ui.components.category-picker.no_categories" )} - .placeholder=${placeholder} - .showLabel=${this.showLabel} - .value=${this.value} .getItems=${this._getItems} .getAdditionalItems=${this._getAdditionalItems} .valueRenderer=${valueRenderer} diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index 7b14b44920..4c350f46c0 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -80,7 +80,6 @@ class DialogDeviceRegistryDetail extends LitElement { @@ -778,7 +778,6 @@ export class EntityRegistrySettingsEditor extends LitElement { .hass=${this.hass} .value=${this._areaId} .disabled=${this.disabled} - show-label @value-changed=${this._areaPicked} >` : ""} @@ -1013,7 +1012,6 @@ export class EntityRegistrySettingsEditor extends LitElement { ? html``