diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 15482b68d0..972c7d7f26 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -261,6 +261,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { @state() private _required = false; + @state() private _helper = false; + @state() private _label = true; private data = SCHEMAS.map(() => ({})); @@ -418,6 +420,13 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { @change=${this._handleOptionChange} > + + + ${SCHEMAS.map((info, idx) => { const data = this.data[idx]; @@ -446,6 +455,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { .disabled=${this._disabled} .required=${this._required} @value-changed=${valueChanged} + .helper=${this._helper ? "Helper text" : undefined} > ` @@ -466,7 +476,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { width: 60; } .options { - padding: 16px 48px; + max-width: 800px; + margin: 16px auto; } .options ha-formfield { margin-right: 16px; diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index 718afc3869..45faee620f 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -180,7 +180,7 @@ export class SupervisorBackupContent extends LitElement { > diff --git a/setup.cfg b/setup.cfg index ba0f034718..7f23b545dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220401.0 +version = 20220405.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index ea62482637..9ff64d812f 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -52,6 +52,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { @property() public value?: string; + @property() public helper?: string; + @property() public devices?: DeviceRegistryEntry[]; @property() public areas?: AreaRegistryEntry[]; @@ -269,6 +271,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { ? this.hass.localize("ui.components.device-picker.device") : this.label} .value=${this._value} + .helper=${this.helper} .renderer=${rowRenderer} .disabled=${this.disabled} .required=${this.required} diff --git a/src/components/device/ha-devices-picker.ts b/src/components/device/ha-devices-picker.ts index 5b2e0245b9..688467c2a8 100644 --- a/src/components/device/ha-devices-picker.ts +++ b/src/components/device/ha-devices-picker.ts @@ -11,6 +11,8 @@ class HaDevicesPicker extends LitElement { @property() public value?: string[]; + @property() public helper?: string; + @property({ type: Boolean }) public required?: boolean; /** @@ -64,6 +66,7 @@ class HaDevicesPicker extends LitElement {
diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index 49bfd1db33..3a73ad3fcb 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -28,6 +28,8 @@ class HaEntityAttributePicker extends LitElement { @property() public value?: string; + @property() public helper?: string; + @property({ type: Boolean }) private _opened = false; @query("ha-combo-box", true) private _comboBox!: HaComboBox; @@ -64,6 +66,7 @@ class HaEntityAttributePicker extends LitElement { )} .disabled=${this.disabled || !this.entityId} .required=${this.required} + .helper=${this.helper} .allowCustomValue=${this.allowCustomValue} item-value-path="value" item-label-path="label" diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index c8f288ad70..7f9c8d3d80 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -48,6 +48,8 @@ export class HaEntityPicker extends LitElement { @property() public value?: string; + @property() public helper?: string; + /** * Show entities from specific domains. * @type {Array} @@ -304,6 +306,7 @@ export class HaEntityPicker extends LitElement { .label=${this.label === undefined ? this.hass.localize("ui.components.entity.entity-picker.entity") : this.label} + .helper=${this.helper} .allowCustomValue=${this.allowCustomEntity} .filteredItems=${this._states} .renderer=${rowRenderer} diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index 7a7abf13b1..d2b413cace 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -29,6 +29,8 @@ class HaAddonPicker extends LitElement { @property() public value = ""; + @property() public helper?: string; + @state() private _addons?: HassioAddonInfo[]; @property({ type: Boolean }) public disabled = false; @@ -62,6 +64,7 @@ class HaAddonPicker extends LitElement { .value=${this._value} .required=${this.required} .disabled=${this.disabled} + .helper=${this.helper} .renderer=${rowRenderer} .items=${this._addons} item-value-path="slug" diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index b841575b67..2192bd931c 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -49,6 +49,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { @property() public value?: string; + @property() public helper?: string; + @property() public placeholder?: string; @property({ type: Boolean, attribute: "no-add" }) @@ -312,6 +314,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { return html` PM `}
- ${this.helper ? html`
${this.helper}
` : ""} + ${this.helper + ? html`${this.helper}` + : ""} `; } @@ -350,13 +353,6 @@ export class HaBaseTimeInput extends LitElement { color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87)); padding-left: 4px; } - - .helper { - color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6)); - font-size: 0.75rem; - padding-left: 16px; - padding-right: 16px; - } `; } diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index c1bc266ed9..c0166ddfa8 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -64,6 +64,8 @@ export class HaComboBox extends LitElement { @property() public validationMessage?: string; + @property() public helper?: string; + @property({ attribute: "error-message" }) public errorMessage?: string; @property({ type: Boolean }) public invalid?: boolean; @@ -147,6 +149,8 @@ export class HaComboBox extends LitElement { .suffix=${html`
`} .icon=${this.icon} .invalid=${this.invalid} + .helper=${this.helper} + helperPersistent > diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index 0963b1fd02..c776519ceb 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -39,11 +39,15 @@ export class HaDateInput extends LitElement { @property() public label?: string; + @property() public helper?: string; + render() { return html` +
${this.error && this.error.base ? html` @@ -173,7 +173,6 @@ export class HaForm extends LitElement implements HaFormElement { } static get styles(): CSSResultGroup { - // .root has overflow: auto to avoid margin collapse return css` .root { margin-bottom: -24px; diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 719976bde2..220c434dea 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -31,6 +31,8 @@ export class HaIconPicker extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property() public placeholder?: string; @property() public fallbackPath?: string; @@ -57,6 +59,7 @@ export class HaIconPicker extends LitElement { allow-custom-value .filteredItems=${iconItems} .label=${this.label} + .helper=${this.helper} .disabled=${this.disabled} .required=${this.required} .placeholder=${this.placeholder} diff --git a/src/components/ha-input-helper-text.ts b/src/components/ha-input-helper-text.ts new file mode 100644 index 0000000000..a6c34ccdc2 --- /dev/null +++ b/src/components/ha-input-helper-text.ts @@ -0,0 +1,25 @@ +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement } from "lit/decorators"; + +@customElement("ha-input-helper-text") +class InputHelperText extends LitElement { + protected render(): TemplateResult { + return html``; + } + + static styles = css` + :host { + display: block; + color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6)); + font-size: 0.75rem; + padding-left: 16px; + padding-right: 16px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-input-helper-text": InputHelperText; + } +} diff --git a/src/components/ha-labeled-slider.js b/src/components/ha-labeled-slider.js index af7134ab13..00d1b1848f 100644 --- a/src/components/ha-labeled-slider.js +++ b/src/components/ha-labeled-slider.js @@ -46,6 +46,9 @@ class HaLabeledSlider extends PolymerElement { value="{{value}}" >
+ `; } @@ -62,6 +65,7 @@ class HaLabeledSlider extends PolymerElement { max: Number, pin: Boolean, step: Number, + helper: String, extra: { type: Boolean, diff --git a/src/components/ha-menu-button.ts b/src/components/ha-menu-button.ts index 294418195c..b51dd78800 100644 --- a/src/components/ha-menu-button.ts +++ b/src/components/ha-menu-button.ts @@ -3,7 +3,6 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; -import { computeDomain } from "../common/entity/compute_domain"; import { subscribeNotifications } from "../data/persistent_notification"; import { HomeAssistant } from "../types"; import "./ha-icon-button"; @@ -43,18 +42,15 @@ class HaMenuButton extends LitElement { protected render(): TemplateResult { const hasNotifications = - (this.narrow || this.hass.dockedSidebar === "always_hidden") && - (this._hasNotifications || - Object.keys(this.hass.states).some( - (entityId) => computeDomain(entityId) === "configurator" - )); + this._hasNotifications && + (this.narrow || this.hass.dockedSidebar === "always_hidden"); return html` - ${hasNotifications ? html`
` : ""} + ${hasNotifications ? html`
` : ""} `; } diff --git a/src/components/ha-selector/ha-selector-addon.ts b/src/components/ha-selector/ha-selector-addon.ts index acabb24cf5..91aee42777 100644 --- a/src/components/ha-selector/ha-selector-addon.ts +++ b/src/components/ha-selector/ha-selector-addon.ts @@ -14,6 +14,8 @@ export class HaAddonSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public required = true; @@ -23,6 +25,7 @@ export class HaAddonSelector extends LitElement { .hass=${this.hass} .value=${this.value} .label=${this.label} + .helper=${this.helper} .disabled=${this.disabled} .required=${this.required} allow-custom-entity diff --git a/src/components/ha-selector/ha-selector-area.ts b/src/components/ha-selector/ha-selector-area.ts index 8aaada7e23..f922d8d0c6 100644 --- a/src/components/ha-selector/ha-selector-area.ts +++ b/src/components/ha-selector/ha-selector-area.ts @@ -18,6 +18,8 @@ export class HaAreaSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @state() public _configEntries?: ConfigEntry[]; @property({ type: Boolean }) public disabled = false; @@ -47,6 +49,7 @@ export class HaAreaSelector extends LitElement { .hass=${this.hass} .value=${this.value} .label=${this.label} + .helper=${this.helper} no-add .deviceFilter=${this._filterDevices} .entityFilter=${this._filterEntities} @@ -66,6 +69,7 @@ export class HaAreaSelector extends LitElement { - - `; + return html` + + + + ${this.helper + ? html`${this.helper}` + : ""} + `; } private _handleChange(ev) { @@ -35,12 +43,10 @@ export class HaBooleanSelector extends LitElement { static get styles(): CSSResultGroup { return css` - :host { - height: 56px; - display: flex; - } ha-formfield { - width: 100%; + display: flex; + height: 56px; + align-items: center; --mdc-typography-body2-font-size: 1em; } `; diff --git a/src/components/ha-selector/ha-selector-color-rgb.ts b/src/components/ha-selector/ha-selector-color-rgb.ts index 780d83c832..8597fa783e 100644 --- a/src/components/ha-selector/ha-selector-color-rgb.ts +++ b/src/components/ha-selector/ha-selector-color-rgb.ts @@ -16,6 +16,8 @@ export class HaColorRGBSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean }) public required = true; @@ -24,9 +26,11 @@ export class HaColorRGBSelector extends LitElement { return html` diff --git a/src/components/ha-selector/ha-selector-color-temp.ts b/src/components/ha-selector/ha-selector-color-temp.ts index 67ac8abcc7..c3cbb5ffe8 100644 --- a/src/components/ha-selector/ha-selector-color-temp.ts +++ b/src/components/ha-selector/ha-selector-color-temp.ts @@ -15,6 +15,8 @@ export class HaColorTempSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean }) public required = true; @@ -29,6 +31,7 @@ export class HaColorTempSelector extends LitElement { .max=${this.selector.color_temp.max_mireds ?? 500} .value=${this.value} .disabled=${this.disabled} + .helper=${this.helper} .required=${this.required} @change=${this._valueChanged} > diff --git a/src/components/ha-selector/ha-selector-date.ts b/src/components/ha-selector/ha-selector-date.ts index 47fa5b7d91..cfde72537e 100644 --- a/src/components/ha-selector/ha-selector-date.ts +++ b/src/components/ha-selector/ha-selector-date.ts @@ -14,6 +14,8 @@ export class HaDateSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean }) public required = true; @@ -26,6 +28,7 @@ export class HaDateSelector extends LitElement { .disabled=${this.disabled} .value=${this.value} .required=${this.required} + .helper=${this.helper} > `; diff --git a/src/components/ha-selector/ha-selector-datetime.ts b/src/components/ha-selector/ha-selector-datetime.ts index c8d1dab2d3..cdb15ccacc 100644 --- a/src/components/ha-selector/ha-selector-datetime.ts +++ b/src/components/ha-selector/ha-selector-datetime.ts @@ -6,6 +6,7 @@ import type { HomeAssistant } from "../../types"; import "../ha-date-input"; import type { HaDateInput } from "../ha-date-input"; import "../ha-time-input"; +import "../ha-input-helper-text"; import type { HaTimeInput } from "../ha-time-input"; @customElement("ha-selector-datetime") @@ -18,6 +19,8 @@ export class HaDateTimeSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean }) public required = true; @@ -30,23 +33,28 @@ export class HaDateTimeSelector extends LitElement { const values = this.value?.split(" "); return html` - - - +
+ + + +
+ ${this.helper + ? html`${this.helper}` + : ""} `; } @@ -58,7 +66,7 @@ export class HaDateTimeSelector extends LitElement { } static styles = css` - :host { + .input { display: flex; align-items: center; flex-direction: row; diff --git a/src/components/ha-selector/ha-selector-device.ts b/src/components/ha-selector/ha-selector-device.ts index efb5dee013..b6358bf284 100644 --- a/src/components/ha-selector/ha-selector-device.ts +++ b/src/components/ha-selector/ha-selector-device.ts @@ -17,6 +17,8 @@ export class HaDeviceSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @state() public _configEntries?: ConfigEntry[]; @property({ type: Boolean }) public disabled = false; @@ -43,6 +45,7 @@ export class HaDeviceSelector extends LitElement { .hass=${this.hass} .value=${this.value} .label=${this.label} + .helper=${this.helper} .deviceFilter=${this._filterDevices} .includeDeviceClasses=${this.selector.device.entity?.device_class ? [this.selector.device.entity.device_class] @@ -62,6 +65,7 @@ export class HaDeviceSelector extends LitElement { + ${!isBox + ? html` ` + : ""} + + + + ${!isBox && this.helper + ? html`${this.helper}` : ""} - - `; + `; } private get _value() { @@ -92,7 +103,7 @@ export class HaNumberSelector extends LitElement { static get styles(): CSSResultGroup { return css` - :host { + .input { display: flex; justify-content: space-between; align-items: center; diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index 2f79979e90..85eaefd610 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -58,6 +58,7 @@ export class HaSelectSelector extends LitElement { ` )} + ${this._renderHelper()} `; } @@ -76,6 +77,7 @@ export class HaSelectSelector extends LitElement { ` )} + ${this._renderHelper()} `; } @@ -107,6 +109,7 @@ export class HaSelectSelector extends LitElement { item-label-path="label" .hass=${this.hass} .label=${this.label} + .helper=${this.helper} .disabled=${this.disabled} .required=${this.required && !value.length} .value=${this._filter} @@ -131,6 +134,7 @@ export class HaSelectSelector extends LitElement { item-label-path="label" .hass=${this.hass} .label=${this.label} + .helper=${this.helper} .disabled=${this.disabled} .required=${this.required} .items=${options} @@ -161,6 +165,12 @@ export class HaSelectSelector extends LitElement { `; } + private _renderHelper() { + return this.helper + ? html`${this.helper}` + : ""; + } + private get _mode(): "list" | "dropdown" { return ( this.selector.select.mode || diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts index cf25317cce..4720598a77 100644 --- a/src/components/ha-selector/ha-selector-target.ts +++ b/src/components/ha-selector/ha-selector-target.ts @@ -26,6 +26,8 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) { @property() public label?: string; + @property() public helper?: string; + @state() private _entityPlaformLookup?: Record; @state() private _configEntries?: ConfigEntry[]; @@ -64,6 +66,7 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) { return html` diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index b5e1ba55f5..5e3d63b9b1 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -36,10 +36,9 @@ import memoizeOne from "memoize-one"; import { LocalStorage } from "../common/decorators/local-storage"; import { fireEvent } from "../common/dom/fire_event"; import { toggleAttribute } from "../common/dom/toggle_attribute"; -import { computeDomain } from "../common/entity/compute_domain"; -import { computeStateDomain } from "../common/entity/compute_state_domain"; import { stringCompare } from "../common/string/compare"; import { computeRTL } from "../common/util/compute_rtl"; +import { throttle } from "../common/util/throttle"; import { ActionHandlerDetail } from "../data/lovelace"; import { PersistentNotification, @@ -294,11 +293,7 @@ class HaSidebar extends LitElement { toggleAttribute(this, "rtl", computeRTL(this.hass)); } - this._updatesCount = Object.values(this.hass.states).filter( - (entity) => - computeStateDomain(entity) === "update" && - updateCanInstall(entity as UpdateEntity) - ).length; + this._calculateCounts(); if (!SUPPORT_SCROLL_IF_NEEDED) { return; @@ -312,6 +307,21 @@ class HaSidebar extends LitElement { } } + private _calculateCounts = throttle(() => { + let updateCount = 0; + + for (const entityId of Object.keys(this.hass.states)) { + if ( + entityId.startsWith("update.") && + updateCanInstall(this.hass.states[entityId] as UpdateEntity) + ) { + updateCount++; + } + } + + this._updatesCount = updateCount; + }, 5000); + private _renderHeader() { return html` - `; + + + ${this.helper + ? html`${this.helper}` + : ""} `; } private async _showPicker(ev) { diff --git a/src/components/ha-time-input.ts b/src/components/ha-time-input.ts index 2bf5659841..eadbc744ce 100644 --- a/src/components/ha-time-input.ts +++ b/src/components/ha-time-input.ts @@ -14,6 +14,8 @@ export class HaTimeInput extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public required = false; @@ -46,6 +48,7 @@ export class HaTimeInput extends LitElement { @value-changed=${this._timeChanged} .enableSecond=${this.enableSecond} .required=${this.required} + .helper=${this.helper} > `; } diff --git a/src/components/map/ha-locations-editor.ts b/src/components/map/ha-locations-editor.ts index d21a7185ac..2dcc446099 100644 --- a/src/components/map/ha-locations-editor.ts +++ b/src/components/map/ha-locations-editor.ts @@ -21,6 +21,7 @@ import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map"; import type { HomeAssistant } from "../../types"; import "./ha-map"; import type { HaMap } from "./ha-map"; +import "../ha-input-helper-text"; declare global { // for fire event @@ -50,6 +51,8 @@ export class HaLocationsEditor extends LitElement { @property({ attribute: false }) public locations?: MarkerLocation[]; + @property() public helper?: string; + @property({ type: Boolean }) public autoFit = false; @property({ type: Number }) public zoom = 16; @@ -102,13 +105,18 @@ export class HaLocationsEditor extends LitElement { } protected render(): TemplateResult { - return html``; + return html` + + ${this.helper + ? html`${this.helper}` + : ""} + `; } private _getLayers = memoizeOne( @@ -287,13 +295,10 @@ export class HaLocationsEditor extends LitElement { static get styles(): CSSResultGroup { return css` - :host { + ha-map { display: block; height: 300px; } - ha-map { - height: 100%; - } `; } } diff --git a/src/dialogs/config-flow/dialog-data-entry-flow.ts b/src/dialogs/config-flow/dialog-data-entry-flow.ts index 1d84b6efed..18de3e81d9 100644 --- a/src/dialogs/config-flow/dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/dialog-data-entry-flow.ts @@ -29,6 +29,7 @@ import { DeviceRegistryEntry, subscribeDeviceRegistry, } from "../../data/device_registry"; +import { fetchIntegrationManifest } from "../../data/integration"; import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; @@ -43,10 +44,10 @@ import "./step-flow-create-entry"; import "./step-flow-external"; import "./step-flow-form"; import "./step-flow-loading"; +import "./step-flow-menu"; import "./step-flow-pick-flow"; import "./step-flow-pick-handler"; import "./step-flow-progress"; -import "./step-flow-menu"; let instance = 0; @@ -237,22 +238,32 @@ class DataEntryFlowDialog extends LitElement { "" : html`
- ${["form", "menu", "external"].includes( - this._step?.type as any - ) + ${([ + "form", + "menu", + "external", + "progress", + "data_entry_flow_progressed", + ].includes(this._step?.type as any) && + this._params.manifest?.is_built_in) || + this._params.manifest?.documentation ? html` + + ` : ""} @@ -427,6 +438,17 @@ class DataEntryFlowDialog extends LitElement { this._handler = undefined; } this._processStep(step); + if (this._params!.manifest === undefined) { + try { + this._params!.manifest = await fetchIntegrationManifest( + this.hass, + this._params?.domain || step.handler + ); + } catch (_) { + // No manifest + this._params!.manifest = null; + } + } } else { this._step = null; this._flowsInProgress = flowsInProgress; diff --git a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts index 35f6630216..ca57038f8b 100644 --- a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts @@ -10,6 +10,7 @@ import { DataEntryFlowStepMenu, DataEntryFlowStepProgress, } from "../../data/data_entry_flow"; +import { IntegrationManifest } from "../../data/integration"; import { HomeAssistant } from "../../types"; export interface FlowHandlers { @@ -122,6 +123,8 @@ export interface DataEntryFlowDialogParams { startFlowHandler?: string; searchQuery?: string; continueFlowId?: string; + manifest?: IntegrationManifest | null; + domain?: string; dialogClosedCallback?: (params: { flowFinished: boolean; entryId?: string; diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index 4a90b08889..14adfcb9e4 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -1,6 +1,6 @@ import { html } from "lit"; import { ConfigEntry } from "../../data/config_entries"; -import { domainToName } from "../../data/integration"; +import { domainToName, IntegrationManifest } from "../../data/integration"; import { createOptionsFlow, deleteOptionsFlow, @@ -16,12 +16,15 @@ export const loadOptionsFlowDialog = loadDataEntryFlowDialog; export const showOptionsFlowDialog = ( element: HTMLElement, - configEntry: ConfigEntry + configEntry: ConfigEntry, + manifest?: IntegrationManifest | null ): void => showFlowDialog( element, { startFlowHandler: configEntry.entry_id, + domain: configEntry.domain, + manifest, }, { loadDevicesAndAreas: false, diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index e88128b326..5ec2e499d8 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -190,6 +190,10 @@ class StepFlowForm extends LitElement { margin-top: 24px; display: block; } + h2 { + word-break: break-word; + padding-right: 72px; + } `, ]; } diff --git a/src/dialogs/more-info/controls/more-info-fan.js b/src/dialogs/more-info/controls/more-info-fan.js index 5a2f042448..3da9229e1c 100644 --- a/src/dialogs/more-info/controls/more-info-fan.js +++ b/src/dialogs/more-info/controls/more-info-fan.js @@ -35,6 +35,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) { .has-direction .container-direction, .has-oscillating .container-oscillating { display: block; + margin-top: 8px; } ha-select { diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index b48dfcf8a9..57c1ba574b 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -243,6 +243,10 @@ class MoreInfoUpdate extends LitElement { width: 100%; justify-content: center; } + mwc-linear-progress { + margin-bottom: -10px; + margin-top: -10px; + } `; } } diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index 43e360c459..97d97d4e38 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -165,6 +165,9 @@ export class HaChooseAction extends LitElement implements ActionElement { right: 0; padding: 4px; } + ha-form::part(root) { + overflow: visible; + } `, ]; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 3060139753..13254779b1 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -442,6 +442,9 @@ export default class HaAutomationTriggerRow extends LitElement { z-index: 3; --mdc-theme-text-primary-on-background: var(--primary-text-color); } + .rtl .card-menu { + float: left; + } .triggered { cursor: pointer; position: absolute; @@ -470,9 +473,6 @@ export default class HaAutomationTriggerRow extends LitElement { background-color: var(--accent-color); color: var(--text-accent-color, var(--text-primary-color)); } - .rtl .card-menu { - float: left; - } mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 48ebb5df49..eabbf8fbaf 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -11,10 +11,13 @@ import { TemplateResult, } 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 { computeDomain } from "../../../common/entity/compute_domain"; import { domainIcon } from "../../../common/entity/domain_icon"; +import { stringCompare } from "../../../common/string/compare"; +import { LocalizeFunc } from "../../../common/translations/localize"; import "../../../components/ha-alert"; import "../../../components/ha-area-picker"; import "../../../components/ha-expansion-panel"; @@ -96,7 +99,7 @@ const OVERRIDE_SENSOR_UNITS = { pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"], }; -const SWITCH_AS_DOMAINS = ["light", "lock", "cover", "fan", "siren"]; +const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren"]; @customElement("entity-registry-settings") export class EntityRegistrySettings extends SubscribeMixin(LitElement) { @@ -273,22 +276,29 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { @selected=${this._deviceClassChanged} @closed=${stopPropagation} > - ${this._deviceClassOptions[0].map( - (deviceClass: string) => html` - - ${this.hass.localize( - `ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}` - )} + ${this._deviceClassesSorted( + domain, + this._deviceClassOptions[0], + this.hass.localize + ).map( + (entry) => html` + + ${entry.label} ` )} -
  • - ${this._deviceClassOptions[1].map( - (deviceClass: string) => html` - - ${this.hass.localize( - `ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}` - )} + ${this._deviceClassOptions[0].length && + this._deviceClassOptions[1].length + ? html`
  • ` + : ""} + ${this._deviceClassesSorted( + domain, + this._deviceClassOptions[1], + this.hass.localize + ).map( + (entry) => html` + + ${entry.label} ` )} @@ -296,9 +306,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { ` : ""} ${this._deviceClass && - stateObj.attributes.unit_of_measurement && + stateObj?.attributes.unit_of_measurement && OVERRIDE_SENSOR_UNITS[this._deviceClass]?.includes( - stateObj.attributes.unit_of_measurement + stateObj?.attributes.unit_of_measurement ) ? html` ${domainToName(this.hass.localize, "switch")}
    - ${SWITCH_AS_DOMAINS.map( - (as_domain) => html` - - ${domainToName(this.hass.localize, as_domain)} +
  • + ${this._switchAsDomainsSorted( + SWITCH_AS_DOMAINS, + this.hass.localize + ).map( + (entry) => html` + + ${entry.label} ` )} @@ -716,9 +730,31 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { } private async _showOptionsFlow() { - showOptionsFlowDialog(this, this._helperConfigEntry!); + showOptionsFlowDialog(this, this._helperConfigEntry!, null); } + private _switchAsDomainsSorted = memoizeOne( + (domains: string[], localize: LocalizeFunc) => + domains + .map((entry) => ({ + domain: entry, + label: domainToName(localize, entry), + })) + .sort((a, b) => stringCompare(a.label, b.label)) + ); + + private _deviceClassesSorted = memoizeOne( + (domain: string, deviceClasses: string[], localize: LocalizeFunc) => + deviceClasses + .map((entry) => ({ + deviceClass: entry, + label: localize( + `ui.dialogs.entity_registry.editor.device_classes.${domain}.${entry}` + ), + })) + .sort((a, b) => stringCompare(a.label, b.label)) + ); + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index a78eb0e94b..79cd2d7828 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -700,6 +700,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { this._handleFlowUpdated(); }, startFlowHandler: domain, + manifest: this._manifests[domain], showAdvanced: this.hass.userData?.showAdvanced, }); } diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 28154e279c..d2790ec98f 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -482,7 +482,11 @@ export class HaIntegrationCard extends LitElement { ); private _showOptions(ev) { - showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry); + showOptionsFlowDialog( + this, + ev.target.closest("ha-card").configEntry, + this.manifest + ); } private _handleRename(ev: CustomEvent): void { diff --git a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts index bae0576f9e..b29db5117b 100644 --- a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts +++ b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts @@ -1,6 +1,7 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; import { mdiChevronRight } from "@mdi/js"; +import formatISO9075 from "date-fns/formatISO9075"; import { css, CSSResultGroup, @@ -69,12 +70,11 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { public showDialog(params: DialogStatisticsAdjustSumParams): void { this._params = params; + // moment is in format YYYY-MM-DD HH:mm:ss because of selector + // Here we create a date with minutes set to %5 const now = new Date(); - this._moment = `${now.getFullYear()}-${ - now.getMonth() + 1 - }-${now.getDate()} ${now.getHours()}:${ - now.getMinutes() - (now.getMinutes() % 5) - }:00`; + now.setMinutes(now.getMinutes() - (now.getMinutes() % 5), 0); + this._moment = formatISO9075(now); this._fetchStats(); } @@ -167,6 +167,9 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { time. This can mess up your beautiful graphs! Select a time below to find the bad moment and adjust the data.
    +
    + Statistic: ${this._params!.statistic.statistic_id} +
    - ${this._params!.statistic.name || this._params!.statistic.statistic_id} + Statistic: ${this._params!.statistic.statistic_id}
    @@ -250,7 +253,10 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { this._stats5min = undefined; this._statsHour = undefined; const statId = this._params!.statistic.statistic_id; - const moment = new Date(this._moment!); + + // moment is in format YYYY-MM-DD HH:mm:ss because of selector + // Here we convert it to an ISO string. + const moment = new Date(this._moment!.replace(" ", "T")); // Search 3 hours before and 3 hours after chosen time const hourStatStart = new Date(moment.getTime()); diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 076d2f02db..f47011d22a 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -253,6 +253,10 @@ class HaPanelHistory extends LitElement { padding: 8px 16px 0; } + :host([narrow]) .filters { + flex-wrap: wrap; + } + ha-date-range-picker { margin-right: 16px; max-width: 100%; diff --git a/src/panels/lovelace/common/entity/turn-on-off-entity.ts b/src/panels/lovelace/common/entity/turn-on-off-entity.ts index 83c675b9dc..701c2c4996 100644 --- a/src/panels/lovelace/common/entity/turn-on-off-entity.ts +++ b/src/panels/lovelace/common/entity/turn-on-off-entity.ts @@ -21,6 +21,9 @@ export const turnOnOffEntity = ( case "input_button": service = "press"; break; + case "scene": + service = "turn_on"; + break; default: service = turnOn ? "turn_on" : "turn_off"; } diff --git a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts index e8c524cfdf..ed49622a37 100644 --- a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts @@ -15,6 +15,7 @@ import type { LovelaceRowEditor } from "../../types"; import { entitiesConfigStruct } from "../structs/entities-struct"; const SecondaryInfoValues: { [key: string]: { domains?: string[] } } = { + none: {}, "entity-id": {}, "last-changed": {}, "last-updated": {},