Merge pull request #11969 from home-assistant/patch-release

This commit is contained in:
Bram Kragten 2022-03-07 17:08:58 +01:00 committed by GitHub
commit e9003ac35e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 908 additions and 771 deletions

View File

@ -38,6 +38,7 @@ const SCHEMAS: {
select: "Select", select: "Select",
icon: "Icon", icon: "Icon",
media: "Media", media: "Media",
location: "Location",
}, },
schema: [ schema: [
{ name: "addon", selector: { addon: {} } }, { name: "addon", selector: { addon: {} } },
@ -75,6 +76,10 @@ const SCHEMAS: {
media: {}, media: {},
}, },
}, },
{
name: "location",
selector: { location: { radius: true, icon: "mdi:home" } },
},
], ],
}, },
{ {

View File

@ -168,6 +168,11 @@ const SCHEMAS: {
}, },
icon: { name: "Icon", selector: { icon: {} } }, icon: { name: "Icon", selector: { icon: {} } },
media: { name: "Media", selector: { media: {} } }, media: { name: "Media", selector: { media: {} } },
location: { name: "Location", selector: { location: {} } },
location_radius: {
name: "Location with radius",
selector: { location: { radius: true, icon: "mdi:home" } },
},
}, },
}, },
]; ];

View File

@ -1,5 +1,4 @@
import { mdiFolderUpload } from "@mdi/js"; import { mdiFolderUpload } from "@mdi/js";
import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit"; import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
name = home-assistant-frontend name = home-assistant-frontend
version = 20220301.0 version = 20220301.1
author = The Home Assistant Authors author = The Home Assistant Authors
author_email = hello@home-assistant.io author_email = hello@home-assistant.io
license = Apache-2.0 license = Apache-2.0

View File

@ -41,7 +41,7 @@ export class HaDateInput extends LitElement {
return html`<ha-textfield return html`<ha-textfield
.label=${this.label} .label=${this.label}
.disabled=${this.disabled} .disabled=${this.disabled}
iconTrailing="calendar" iconTrailing
@click=${this._openDialog} @click=${this._openDialog}
.value=${this.value .value=${this.value
? formatDateNumeric(new Date(this.value), this.locale) ? formatDateNumeric(new Date(this.value), this.locale)

View File

@ -1,6 +1,13 @@
import { mdiChevronDown } from "@mdi/js"; import { mdiChevronDown } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, query } from "lit/decorators"; css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { nextRender } from "../common/util/render-status"; import { nextRender } from "../common/util/render-status";
@ -16,11 +23,21 @@ class HaExpansionPanel extends LitElement {
@property() secondary?: string; @property() secondary?: string;
@state() _showContent = this.expanded;
@query(".container") private _container!: HTMLDivElement; @query(".container") private _container!: HTMLDivElement;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="summary" @click=${this._toggleContainer}> <div
id="summary"
@click=${this._toggleContainer}
@keydown=${this._toggleContainer}
role="button"
tabindex="0"
aria-expanded=${this.expanded}
aria-controls="sect1"
>
<slot class="header" name="header"> <slot class="header" name="header">
${this.header} ${this.header}
<slot class="secondary" name="secondary">${this.secondary}</slot> <slot class="secondary" name="secondary">${this.secondary}</slot>
@ -33,21 +50,37 @@ class HaExpansionPanel extends LitElement {
<div <div
class="container ${classMap({ expanded: this.expanded })}" class="container ${classMap({ expanded: this.expanded })}"
@transitionend=${this._handleTransitionEnd} @transitionend=${this._handleTransitionEnd}
role="region"
aria-labelledby="summary"
aria-hidden=${!this.expanded}
tabindex="-1"
> >
<slot></slot> ${this._showContent ? html`<slot></slot>` : ""}
</div> </div>
`; `;
} }
private _handleTransitionEnd() { protected willUpdate(changedProps: PropertyValues) {
this._container.style.removeProperty("height"); if (changedProps.has("expanded") && this.expanded) {
this._showContent = this.expanded;
}
} }
private async _toggleContainer(): Promise<void> { private _handleTransitionEnd() {
this._container.style.removeProperty("height");
this._showContent = this.expanded;
}
private async _toggleContainer(ev): Promise<void> {
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
return;
}
ev.preventDefault();
const newExpanded = !this.expanded; const newExpanded = !this.expanded;
fireEvent(this, "expanded-will-change", { expanded: newExpanded }); fireEvent(this, "expanded-will-change", { expanded: newExpanded });
if (newExpanded) { if (newExpanded) {
this._showContent = true;
// allow for dynamic content to be rendered // allow for dynamic content to be rendered
await nextRender(); await nextRender();
} }
@ -80,17 +113,21 @@ class HaExpansionPanel extends LitElement {
var(--divider-color, #e0e0e0) var(--divider-color, #e0e0e0)
); );
border-radius: var(--ha-card-border-radius, 4px); border-radius: var(--ha-card-border-radius, 4px);
padding: 0 8px;
} }
.summary { #summary {
display: flex; display: flex;
padding: var(--expansion-panel-summary-padding, 0); padding: var(--expansion-panel-summary-padding, 0 8px);
min-height: 48px; min-height: 48px;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
font-weight: 500; font-weight: 500;
outline: none;
}
#summary:focus {
background: var(--input-fill-color);
} }
.summary-icon { .summary-icon {
@ -103,6 +140,7 @@ class HaExpansionPanel extends LitElement {
} }
.container { .container {
padding: var(--expansion-panel-content-padding, 0 8px);
overflow: hidden; overflow: hidden;
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
height: 0px; height: 0px;

View File

@ -1,6 +1,5 @@
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
@ -21,7 +20,7 @@ export class HaFileUpload extends LitElement {
@property() public accept!: string; @property() public accept!: string;
@property() public icon!: string; @property() public icon?: string;
@property() public label!: string; @property() public label!: string;
@ -39,15 +38,7 @@ export class HaFileUpload extends LitElement {
protected firstUpdated(changedProperties: PropertyValues) { protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties); super.firstUpdated(changedProperties);
if (this.autoOpenFileDialog) { if (this.autoOpenFileDialog) {
this._input?.click(); this._openFilePicker();
}
}
protected updated(changedProperties: PropertyValues) {
if (changedProperties.has("_drag") && !this.uploading) {
(
this.shadowRoot!.querySelector("paper-input-container") as any
)._setFocused(this._drag);
} }
} }
@ -60,31 +51,52 @@ export class HaFileUpload extends LitElement {
active active
></ha-circular-progress>` ></ha-circular-progress>`
: html` : html`
<label for="input"> <label
<paper-input-container for="input"
.alwaysFloatLabel=${Boolean(this.value)} class="mdc-text-field mdc-text-field--filled ${classMap({
"mdc-text-field--focused": this._drag,
"mdc-text-field--with-leading-icon": Boolean(this.icon),
"mdc-text-field--with-trailing-icon": Boolean(this.value),
})}"
@drop=${this._handleDrop} @drop=${this._handleDrop}
@dragenter=${this._handleDragStart} @dragenter=${this._handleDragStart}
@dragover=${this._handleDragStart} @dragover=${this._handleDragStart}
@dragleave=${this._handleDragEnd} @dragleave=${this._handleDragEnd}
@dragend=${this._handleDragEnd} @dragend=${this._handleDragEnd}
class=${classMap({
dragged: this._drag,
})}
> >
<label for="input" slot="label"> ${this.label} </label> <span class="mdc-text-field__ripple"></span>
<iron-input slot="input"> <span
class="mdc-floating-label ${this.value || this._drag
? "mdc-floating-label--float-above"
: ""}"
id="label"
>${this.label}</span
>
${this.icon
? html`<span
class="mdc-text-field__icon mdc-text-field__icon--leading"
tabindex="-1"
>
<ha-icon-button
@click=${this._openFilePicker}
.path=${this.icon}
></ha-icon-button>
</span>`
: ""}
<div class="value">${this.value}</div>
<input <input
id="input" id="input"
type="file" type="file"
class="file" class="mdc-text-field__input file"
accept=${this.accept} accept=${this.accept}
@change=${this._handleFilePicked} @change=${this._handleFilePicked}
aria-labelledby="label"
/> />
${this.value}
</iron-input>
${this.value ${this.value
? html` ? html`<span
class="mdc-text-field__icon mdc-text-field__icon--trailing"
tabindex="1"
>
<ha-icon-button <ha-icon-button
slot="suffix" slot="suffix"
@click=${this._clearValue} @click=${this._clearValue}
@ -92,19 +104,22 @@ export class HaFileUpload extends LitElement {
"close"} "close"}
.path=${mdiClose} .path=${mdiClose}
></ha-icon-button> ></ha-icon-button>
` </span>`
: html` : ""}
<ha-icon-button <span
slot="suffix" class="mdc-line-ripple ${this._drag
.path=${this.icon} ? "mdc-line-ripple--active"
></ha-icon-button> : ""}"
`} ></span>
</paper-input-container>
</label> </label>
`} `}
`; `;
} }
private _openFilePicker() {
this._input?.click();
}
private _handleDrop(ev: DragEvent) { private _handleDrop(ev: DragEvent) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@ -137,13 +152,35 @@ export class HaFileUpload extends LitElement {
} }
static get styles() { static get styles() {
return css` return [
paper-input-container { styles,
position: relative; css`
padding: 8px; :host {
margin: 0 -8px; display: block;
} }
paper-input-container.dragged:before { .mdc-text-field--filled {
height: auto;
padding-top: 16px;
cursor: pointer;
}
.mdc-text-field--filled.mdc-text-field--with-trailing-icon {
padding-top: 28px;
}
.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon {
color: var(--secondary-text-color);
}
.mdc-text-field--filled.mdc-text-field--with-trailing-icon
.mdc-text-field__icon {
align-self: flex-end;
}
.mdc-text-field__icon--leading {
margin-bottom: 12px;
}
.mdc-text-field--filled .mdc-floating-label--float-above {
transform: scale(0.75);
top: 8px;
}
.dragged:before {
position: var(--layout-fit_-_position); position: var(--layout-fit_-_position);
top: var(--layout-fit_-_top); top: var(--layout-fit_-_top);
right: var(--layout-fit_-_right); right: var(--layout-fit_-_right);
@ -155,11 +192,14 @@ export class HaFileUpload extends LitElement {
pointer-events: none; pointer-events: none;
border-radius: 4px; border-radius: 4px;
} }
.value {
width: 100%;
}
input.file { input.file {
display: none; display: none;
} }
img { img {
max-width: 125px; max-width: 100%;
max-height: 125px; max-height: 125px;
} }
ha-icon-button { ha-icon-button {
@ -170,7 +210,8 @@ export class HaFileUpload extends LitElement {
display: block; display: block;
text-align-last: center; text-align-last: center;
} }
`; `,
];
} }
} }

View File

@ -9,7 +9,9 @@ export class HaFormConstant extends LitElement implements HaFormElement {
@property() public label!: string; @property() public label!: string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html`<span class="label">${this.label}</span>: ${this.schema.value}`; return html`<span class="label">${this.label}</span>${this.schema.value
? `: ${this.schema.value}`
: ""}`;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@ -25,6 +25,8 @@ import { HomeAssistant } from "../../types";
const getValue = (obj, item) => const getValue = (obj, item) =>
obj ? (!item.name ? obj : obj[item.name]) : null; obj ? (!item.name ? obj : obj[item.name]) : null;
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
let selectorImported = false; let selectorImported = false;
@customElement("ha-form") @customElement("ha-form")
@ -84,7 +86,7 @@ export class HaForm extends LitElement implements HaFormElement {
` `
: ""} : ""}
${this.schema.map((item) => { ${this.schema.map((item) => {
const error = getValue(this.error, item); const error = getError(this.error, item);
return html` return html`
${error ${error

View File

@ -40,7 +40,7 @@ export interface HaFormSelector extends HaFormBaseSchema {
export interface HaFormConstantSchema extends HaFormBaseSchema { export interface HaFormConstantSchema extends HaFormBaseSchema {
type: "constant"; type: "constant";
value: string; value?: string;
} }
export interface HaFormIntegerSchema extends HaFormBaseSchema { export interface HaFormIntegerSchema extends HaFormBaseSchema {

View File

@ -44,6 +44,9 @@ export class HaSelect extends SelectBase {
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon { .mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.mdc-select__anchor {
width: var(--ha-select-min-width, 200px);
}
`, `,
]; ];
} }

View File

@ -0,0 +1,80 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import type {
LocationSelector,
LocationSelectorValue,
} from "../../data/selector";
import "../../panels/lovelace/components/hui-theme-select-editor";
import type { HomeAssistant } from "../../types";
import type { MarkerLocation } from "../map/ha-locations-editor";
import "../map/ha-locations-editor";
@customElement("ha-selector-location")
export class HaLocationSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: LocationSelector;
@property() public value?: LocationSelectorValue;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
protected render() {
return html`
<ha-locations-editor
class="flex"
.hass=${this.hass}
.locations=${this._location(this.selector, this.value)}
@location-updated=${this._locationChanged}
@radius-updated=${this._radiusChanged}
></ha-locations-editor>
`;
}
private _location = memoizeOne(
(
selector: LocationSelector,
value?: LocationSelectorValue
): MarkerLocation[] => {
const computedStyles = getComputedStyle(this);
const zoneRadiusColor = selector.location.radius
? computedStyles.getPropertyValue("--zone-radius-color") ||
computedStyles.getPropertyValue("--accent-color")
: undefined;
return [
{
id: "location",
latitude: value?.latitude || this.hass.config.latitude,
longitude: value?.longitude || this.hass.config.longitude,
radius: selector.location.radius ? value?.radius || 1000 : undefined,
radius_color: zoneRadiusColor,
icon: selector.location.icon,
location_editable: true,
radius_editable: true,
},
];
}
);
private _locationChanged(ev: CustomEvent) {
const [latitude, longitude] = ev.detail.location;
fireEvent(this, "value-changed", {
value: { ...this.value, latitude, longitude },
});
}
private _radiusChanged(ev: CustomEvent) {
const radius = ev.detail.radius;
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-location": HaLocationSelector;
}
}

View File

@ -20,6 +20,7 @@ import "./ha-selector-time";
import "./ha-selector-icon"; import "./ha-selector-icon";
import "./ha-selector-media"; import "./ha-selector-media";
import "./ha-selector-theme"; import "./ha-selector-theme";
import "./ha-selector-location";
@customElement("ha-selector") @customElement("ha-selector")
export class HaSelector extends LitElement { export class HaSelector extends LitElement {

View File

@ -9,6 +9,12 @@ export class HaTextField extends TextFieldBase {
@property({ attribute: "error-message" }) public errorMessage?: string; @property({ attribute: "error-message" }) public errorMessage?: string;
// @ts-ignore
@property({ type: Boolean }) public icon?: boolean;
// @ts-ignore
@property({ type: Boolean }) public iconTrailing?: boolean;
override updated(changedProperties: PropertyValues) { override updated(changedProperties: PropertyValues) {
super.updated(changedProperties); super.updated(changedProperties);
if ( if (
@ -53,6 +59,11 @@ export class HaTextField extends TextFieldBase {
padding-right: var(--text-field-suffix-padding-right, 0px); padding-right: var(--text-field-suffix-padding-right, 0px);
} }
.mdc-text-field:not(.mdc-text-field--disabled)
.mdc-text-field__affix--suffix {
color: var(--secondary-text-color);
}
.mdc-text-field__icon { .mdc-text-field__icon {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }

View File

@ -35,7 +35,7 @@ class SearchInput extends LitElement {
.autofocus=${this.autofocus} .autofocus=${this.autofocus}
.label=${this.label || "Search"} .label=${this.label || "Search"}
.value=${this.filter || ""} .value=${this.filter || ""}
.icon=${true} icon
.iconTrailing=${this.filter || this.suffix} .iconTrailing=${this.filter || this.suffix}
@input=${this._filterInputChanged} @input=${this._filterInputChanged}
> >

View File

@ -29,7 +29,7 @@ export const createImage = async (
body: fd, body: fd,
}); });
if (resp.status === 413) { if (resp.status === 413) {
throw new Error("Uploaded image is too large"); throw new Error(`Uploaded image is too large (${file.name})`);
} else if (resp.status !== 200) { } else if (resp.status !== 200) {
throw new Error("Unknown error"); throw new Error("Unknown error");
} }

View File

@ -43,7 +43,7 @@ export const uploadLocalMedia = async (
} }
); );
if (resp.status === 413) { if (resp.status === 413) {
throw new Error("Uploaded image is too large"); throw new Error(`Uploaded file is too large (${file.name})`);
} else if (resp.status !== 200) { } else if (resp.status !== 200) {
throw new Error("Unknown error"); throw new Error("Unknown error");
} }

View File

@ -15,7 +15,8 @@ export type Selector =
| SelectSelector | SelectSelector
| IconSelector | IconSelector
| MediaSelector | MediaSelector
| ThemeSelector; | ThemeSelector
| LocationSelector;
export interface EntitySelector { export interface EntitySelector {
entity: { entity: {
@ -164,6 +165,16 @@ export interface MediaSelector {
media: {}; media: {};
} }
export interface LocationSelector {
location: { radius?: boolean; icon?: string };
}
export interface LocationSelectorValue {
latitude: number;
longitude: number;
radius?: number;
}
export interface MediaSelectorValue { export interface MediaSelectorValue {
entity_id?: string; entity_id?: string;
media_content_id?: string; media_content_id?: string;

View File

@ -12,12 +12,12 @@ export interface Zone {
} }
export interface ZoneMutableParams { export interface ZoneMutableParams {
name: string;
icon?: string; icon?: string;
latitude: number; latitude: number;
longitude: number; longitude: number;
name: string; passive?: boolean;
passive: boolean; radius?: number;
radius: number;
} }
export const fetchZones = (hass: HomeAssistant) => export const fetchZones = (hass: HomeAssistant) =>

View File

@ -117,13 +117,17 @@ class DataEntryFlowDialog extends LitElement {
); );
} catch (err: any) { } catch (err: any) {
this.closeDialog(); this.closeDialog();
let message = err.message || err.body || "Unknown error";
if (typeof message !== "string") {
message = JSON.stringify(message);
}
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error" "ui.panel.config.integrations.config_flow.error"
), ),
text: `${this.hass.localize( text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load" "ui.panel.config.integrations.config_flow.could_not_load"
)}: ${err.message || err.body}`, )}: ${message}`,
}); });
return; return;
} }
@ -373,13 +377,20 @@ class DataEntryFlowDialog extends LitElement {
step = await this._params!.flowConfig.createFlow(this.hass, handler); step = await this._params!.flowConfig.createFlow(this.hass, handler);
} catch (err: any) { } catch (err: any) {
this.closeDialog(); this.closeDialog();
const message =
err?.status_code === 404
? this.hass.localize(
"ui.panel.config.integrations.config_flow.no_config_flow"
)
: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load"
)}: ${err?.body?.message || err?.message}`;
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error" "ui.panel.config.integrations.config_flow.error"
), ),
text: `${this.hass.localize( text: message,
"ui.panel.config.integrations.config_flow.could_not_load"
)}: ${err.message || err.body}`,
}); });
return; return;
} finally { } finally {

View File

@ -1,3 +1,4 @@
import "@material/mwc-list/mwc-list-item";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -12,6 +13,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../common/util/compute_rtl";
import "../../../components/ha-select";
import "../../../components/ha-slider"; import "../../../components/ha-slider";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import { import {
@ -19,8 +21,6 @@ import {
HUMIDIFIER_SUPPORT_MODES, HUMIDIFIER_SUPPORT_MODES,
} from "../../../data/humidifier"; } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
class MoreInfoHumidifier extends LitElement { class MoreInfoHumidifier extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -67,8 +67,7 @@ class MoreInfoHumidifier extends LitElement {
${supportModes ${supportModes
? html` ? html`
<div class="container-modes"> <ha-select
<mwc-list
.label=${hass.localize("ui.card.humidifier.mode")} .label=${hass.localize("ui.card.humidifier.mode")}
.value=${stateObj.attributes.mode} .value=${stateObj.attributes.mode}
fixedMenuPosition fixedMenuPosition
@ -85,8 +84,7 @@ class MoreInfoHumidifier extends LitElement {
</mwc-list-item> </mwc-list-item>
` `
)} )}
</mwc-list> </ha-select>
</div>
` `
: ""} : ""}
</div> </div>

View File

@ -86,11 +86,11 @@ export class QuickBar extends LitElement {
@state() private _search = ""; @state() private _search = "";
@state() private _opened = false; @state() private _open = false;
@state() private _commandMode = false; @state() private _commandMode = false;
@state() private _done = false; @state() private _opened = false;
@state() private _narrow = false; @state() private _narrow = false;
@ -109,12 +109,12 @@ export class QuickBar extends LitElement {
"all and (max-width: 450px), all and (max-height: 500px)" "all and (max-width: 450px), all and (max-height: 500px)"
).matches; ).matches;
this._initializeItemsIfNeeded(); this._initializeItemsIfNeeded();
this._opened = true; this._open = true;
} }
public closeDialog() { public closeDialog() {
this._open = false;
this._opened = false; this._opened = false;
this._done = false;
this._focusSet = false; this._focusSet = false;
this._filter = ""; this._filter = "";
this._search = ""; this._search = "";
@ -133,7 +133,7 @@ export class QuickBar extends LitElement {
); );
protected render() { protected render() {
if (!this._opened) { if (!this._open) {
return html``; return html``;
} }
@ -162,7 +162,7 @@ export class QuickBar extends LitElement {
"ui.dialogs.quick-bar.filter_placeholder" "ui.dialogs.quick-bar.filter_placeholder"
)} )}
.value=${this._commandMode ? `>${this._search}` : this._search} .value=${this._commandMode ? `>${this._search}` : this._search}
.icon=${true} icon
.iconTrailing=${this._search !== undefined || this._narrow} .iconTrailing=${this._search !== undefined || this._narrow}
@input=${this._handleSearchChange} @input=${this._handleSearchChange}
@keydown=${this._handleInputKeyDown} @keydown=${this._handleInputKeyDown}
@ -218,7 +218,8 @@ export class QuickBar extends LitElement {
` `
: html` : html`
<mwc-list> <mwc-list>
<lit-virtualizer ${this._opened
? html`<lit-virtualizer
scroller scroller
@keydown=${this._handleListItemKeyDown} @keydown=${this._handleListItemKeyDown}
@rangechange=${this._handleRangeChanged} @rangechange=${this._handleRangeChanged}
@ -229,13 +230,14 @@ export class QuickBar extends LitElement {
? "calc(100vh - 56px)" ? "calc(100vh - 56px)"
: `${Math.min( : `${Math.min(
items.length * (this._commandMode ? 56 : 72) + 26, items.length * (this._commandMode ? 56 : 72) + 26,
this._done ? 500 : 0 500
)}px`, )}px`,
})} })}
.items=${items} .items=${items}
.renderItem=${this._renderItem} .renderItem=${this._renderItem}
> >
</lit-virtualizer> </lit-virtualizer>`
: ""}
</mwc-list> </mwc-list>
`} `}
${this._hint ? html`<div class="hint">${this._hint}</div>` : ""} ${this._hint ? html`<div class="hint">${this._hint}</div>` : ""}
@ -252,9 +254,7 @@ export class QuickBar extends LitElement {
} }
private _handleOpened() { private _handleOpened() {
this.updateComplete.then(() => { this._opened = true;
this._done = true;
});
} }
private async _handleRangeChanged(e) { private async _handleRangeChanged(e) {
@ -454,9 +454,10 @@ export class QuickBar extends LitElement {
} }
private _handleItemClick(ev) { private _handleItemClick(ev) {
const listItem = ev.target.closest("mwc-list-item");
this.processItemAndCloseDialog( this.processItemAndCloseDialog(
(ev.target as any).item, listItem.item,
Number((ev.target as HTMLElement).getAttribute("index")) Number(listItem.getAttribute("index"))
); );
} }

View File

@ -1,7 +1,6 @@
/* eslint-disable lit/prefer-static-styles */ /* eslint-disable lit/prefer-static-styles */
import "@material/mwc-button/mwc-button";
import { mdiMicrophone } from "@mdi/js"; import { mdiMicrophone } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -10,12 +9,16 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property, state, query } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { SpeechRecognition } from "../../common/dom/speech-recognition"; import { SpeechRecognition } from "../../common/dom/speech-recognition";
import { uid } from "../../common/util/uid"; import { uid } from "../../common/util/uid";
import "../../components/ha-dialog";
import type { HaDialog } from "../../components/ha-dialog";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-textfield";
import type { HaTextField } from "../../components/ha-textfield";
import { import {
AgentInfo, AgentInfo,
getAgentInfo, getAgentInfo,
@ -24,9 +27,6 @@ import {
} from "../../data/conversation"; } from "../../data/conversation";
import { haStyleDialog } from "../../resources/styles"; import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../../components/ha-dialog";
import type { HaDialog } from "../../components/ha-dialog";
import "@material/mwc-button/mwc-button";
interface Message { interface Message {
who: string; who: string;
@ -127,18 +127,19 @@ export class HaVoiceCommandDialog extends LitElement {
: ""} : ""}
</div> </div>
<div class="input" slot="primaryAction"> <div class="input" slot="primaryAction">
<paper-input <ha-textfield
@keyup=${this._handleKeyUp} @keyup=${this._handleKeyUp}
.label=${this.hass.localize( .label=${this.hass.localize(
`ui.dialogs.voice_command.${ `ui.dialogs.voice_command.${
SpeechRecognition ? "label_voice" : "label" SpeechRecognition ? "label_voice" : "label"
}` }`
)} )}
autofocus dialogInitialFocus
iconTrailing
> >
${SpeechRecognition ${SpeechRecognition
? html` ? html`
<span suffix="" slot="suffix"> <span slot="trailingIcon">
${this.results ${this.results
? html` ? html`
<div class="bouncer"> <div class="bouncer">
@ -155,7 +156,7 @@ export class HaVoiceCommandDialog extends LitElement {
</span> </span>
` `
: ""} : ""}
</paper-input> </ha-textfield>
${this._agentInfo && this._agentInfo.attribution ${this._agentInfo && this._agentInfo.attribution
? html` ? html`
<a <a
@ -195,7 +196,7 @@ export class HaVoiceCommandDialog extends LitElement {
} }
private _handleKeyUp(ev: KeyboardEvent) { private _handleKeyUp(ev: KeyboardEvent) {
const input = ev.target as PaperInputElement; const input = ev.target as HaTextField;
if (ev.keyCode === 13 && input.value) { if (ev.keyCode === 13 && input.value) {
this._processText(input.value); this._processText(input.value);
input.value = ""; input.value = "";
@ -327,6 +328,7 @@ export class HaVoiceCommandDialog extends LitElement {
css` css`
ha-icon-button { ha-icon-button {
color: var(--secondary-text-color); color: var(--secondary-text-color);
margin-right: -24px;
} }
ha-icon-button[active] { ha-icon-button[active] {
@ -338,7 +340,9 @@ export class HaVoiceCommandDialog extends LitElement {
--secondary-action-button-flex: 0; --secondary-action-button-flex: 0;
--mdc-dialog-max-width: 450px; --mdc-dialog-max-width: 450px;
} }
ha-textfield {
display: block;
}
a.button { a.button {
text-decoration: none; text-decoration: none;
} }
@ -406,7 +410,6 @@ export class HaVoiceCommandDialog extends LitElement {
width: 48px; width: 48px;
height: 48px; height: 48px;
position: absolute; position: absolute;
top: 0;
} }
.double-bounce1, .double-bounce1,
.double-bounce2 { .double-bounce2 {

View File

@ -1,7 +1,6 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@polymer/paper-input/paper-textarea";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-toggle"; import "../../../components/entity/ha-entity-toggle";
@ -11,6 +10,7 @@ import "../../../components/ha-circular-progress";
import "../../../components/ha-markdown"; import "../../../components/ha-markdown";
import "../../../components/ha-selector/ha-selector"; import "../../../components/ha-selector/ha-selector";
import "../../../components/ha-settings-row"; import "../../../components/ha-settings-row";
import "../../../components/ha-textfield";
import { import {
BlueprintAutomationConfig, BlueprintAutomationConfig,
triggerAutomationActions, triggerAutomationActions,
@ -38,6 +38,8 @@ export class HaBlueprintAutomationEditor extends LitElement {
@state() private _blueprints?: Blueprints; @state() private _blueprints?: Blueprints;
@state() private _showDescription = false;
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._getBlueprints(); this._getBlueprints();
@ -50,6 +52,17 @@ export class HaBlueprintAutomationEditor extends LitElement {
return this._blueprints[this.config.use_blueprint.path]; return this._blueprints[this.config.use_blueprint.path];
} }
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (
!this._showDescription &&
changedProps.has("config") &&
this.config.description
) {
this._showDescription = true;
}
}
protected render() { protected render() {
const blueprint = this._blueprint; const blueprint = this._blueprint;
return html` return html`
@ -64,16 +77,18 @@ export class HaBlueprintAutomationEditor extends LitElement {
</span> </span>
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
<paper-input <ha-textfield
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.automation.editor.alias" "ui.panel.config.automation.editor.alias"
)} )}
name="alias" name="alias"
.value=${this.config.alias} .value=${this.config.alias || ""}
@value-changed=${this._valueChanged} @change=${this._valueChanged}
> >
</paper-input> </ha-textfield>
<paper-textarea ${this._showDescription
? html`
<ha-textarea
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.automation.editor.description.label" "ui.panel.config.automation.editor.description.label"
)} )}
@ -81,9 +96,20 @@ export class HaBlueprintAutomationEditor extends LitElement {
"ui.panel.config.automation.editor.description.placeholder" "ui.panel.config.automation.editor.description.placeholder"
)} )}
name="description" name="description"
.value=${this.config.description} autogrow
@value-changed=${this._valueChanged} .value=${this.config.description || ""}
></paper-textarea> @change=${this._valueChanged}
></ha-textarea>
`
: html`
<div class="link-button-row">
<button class="link" @click=${this._addDescription}>
${this.hass.localize(
"ui.panel.config.automation.editor.description.add"
)}
</button>
</div>
`}
</div> </div>
${this.stateObj ${this.stateObj
? html` ? html`
@ -173,15 +199,14 @@ export class HaBlueprintAutomationEditor extends LitElement {
value?.default} value?.default}
@value-changed=${this._inputChanged} @value-changed=${this._inputChanged}
></ha-selector>` ></ha-selector>`
: html`<paper-input : html`<ha-textfield
.key=${key} .key=${key}
required required
.value=${(this.config.use_blueprint.input && .value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ?? this.config.use_blueprint.input[key]) ??
value?.default} value?.default}
@value-changed=${this._inputChanged} @input=${this._inputChanged}
no-label-float ></ha-textfield>`}
></paper-input>`}
</ha-settings-row>` </ha-settings-row>`
) )
: html`<p class="padding"> : html`<p class="padding">
@ -221,7 +246,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
const target = ev.target as any; const target = ev.target as any;
const key = target.key; const key = target.key;
const value = ev.detail.value; const value = ev.detail?.value || target.value;
if ( if (
(this.config.use_blueprint.input && (this.config.use_blueprint.input &&
this.config.use_blueprint.input[key] === value) || this.config.use_blueprint.input[key] === value) ||
@ -262,6 +287,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
}); });
} }
private _addDescription() {
this._showDescription = true;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
@ -273,9 +302,16 @@ export class HaBlueprintAutomationEditor extends LitElement {
.padding { .padding {
padding: 16px; padding: 16px;
} }
.link-button-row {
padding: 14px;
}
.blueprint-picker-container { .blueprint-picker-container {
padding: 0 16px 16px; padding: 0 16px 16px;
} }
ha-textarea,
ha-textfield {
display: block;
}
h3 { h3 {
margin: 16px; margin: 16px;
} }
@ -292,9 +328,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
--paper-time-input-justify-content: flex-end; --paper-time-input-justify-content: flex-end;
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
} }
:host(:not([narrow])) ha-settings-row paper-input { :host(:not([narrow])) ha-settings-row ha-textfield,
width: 60%;
}
:host(:not([narrow])) ha-settings-row ha-selector { :host(:not([narrow])) ha-settings-row ha-selector {
width: 60%; width: 60%;
} }

View File

@ -1,10 +1,11 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state, query } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-circular-progress"; import "../../../../components/ha-circular-progress";
import "../../../../components/ha-dialog";
import "../../../../components/ha-textfield";
import type { HaTextField } from "../../../../components/ha-textfield";
import type { AutomationConfig } from "../../../../data/automation"; import type { AutomationConfig } from "../../../../data/automation";
import { convertThingTalk } from "../../../../data/cloud"; import { convertThingTalk } from "../../../../data/cloud";
import { haStyle, haStyleDialog } from "../../../../resources/styles"; import { haStyle, haStyleDialog } from "../../../../resources/styles";
@ -12,7 +13,6 @@ import type { HomeAssistant } from "../../../../types";
import "./ha-thingtalk-placeholders"; import "./ha-thingtalk-placeholders";
import type { PlaceholderValues } from "./ha-thingtalk-placeholders"; import type { PlaceholderValues } from "./ha-thingtalk-placeholders";
import type { ThingtalkDialogParams } from "./show-dialog-thingtalk"; import type { ThingtalkDialogParams } from "./show-dialog-thingtalk";
import "../../../../components/ha-dialog";
export interface Placeholder { export interface Placeholder {
name: string; name: string;
@ -38,7 +38,7 @@ class DialogThingtalk extends LitElement {
@state() private _placeholders?: PlaceholderContainer; @state() private _placeholders?: PlaceholderContainer;
@query("#input") private _input?: PaperInputElement; @query("#input") private _input?: HaTextField;
private _value?: string; private _value?: string;
@ -58,7 +58,7 @@ class DialogThingtalk extends LitElement {
this._placeholders = undefined; this._placeholders = undefined;
this._params = undefined; this._params = undefined;
if (this._input) { if (this._input) {
this._input.value = null; this._input.value = "";
} }
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@ -127,13 +127,13 @@ class DialogThingtalk extends LitElement {
</button> </button>
</li> </li>
</ul> </ul>
<paper-input <ha-textfield
id="input" id="input"
label="What should this automation do?" label="What should this automation do?"
.value=${this._value} .value=${this._value}
autofocus autofocus
@keyup=${this._handleKeyUp} @keyup=${this._handleKeyUp}
></paper-input> ></ha-textfield>
<a <a
href="https://almond.stanford.edu/" href="https://almond.stanford.edu/"
target="_blank" target="_blank"

View File

@ -88,7 +88,7 @@ export class HaWebhookTrigger extends LitElement {
.helper=${this.hass.localize( .helper=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.webhook.webhook_id_helper" "ui.panel.config.automation.editor.triggers.type.webhook.webhook_id_helper"
)} )}
.iconTrailing=${true} iconTrailing
.value=${webhookId || ""} .value=${webhookId || ""}
@input=${this._valueChanged} @input=${this._valueChanged}
> >

View File

@ -1,11 +1,12 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement } from "lit"; import { css, CSSResultGroup, html, LitElement } from "lit";
import { state } from "lit/decorators"; import { query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import type { HaTextField } from "../../../../components/ha-textfield";
import "../../../../components/ha-textfield";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles"; import { haStyle, haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { documentationUrl } from "../../../../util/documentation-url"; import { documentationUrl } from "../../../../util/documentation-url";
import { WebhookDialogParams } from "./show-dialog-manage-cloudhook"; import { WebhookDialogParams } from "./show-dialog-manage-cloudhook";
@ -17,6 +18,8 @@ export class DialogManageCloudhook extends LitElement {
@state() private _params?: WebhookDialogParams; @state() private _params?: WebhookDialogParams;
@query("ha-textfield") _input!: HaTextField;
public showDialog(params: WebhookDialogParams) { public showDialog(params: WebhookDialogParams) {
this._params = params; this._params = params;
} }
@ -53,12 +56,12 @@ export class DialogManageCloudhook extends LitElement {
"ui.panel.config.cloud.dialog_cloudhook.available_at" "ui.panel.config.cloud.dialog_cloudhook.available_at"
)} )}
</p> </p>
<paper-input <ha-textfield
label=${inputLabel} .label=${inputLabel}
value=${cloudhook.cloudhook_url} .value=${cloudhook.cloudhook_url}
@click=${this._copyClipboard} @click=${this._copyClipboard}
@blur=${this._restoreLabel} @blur=${this._restoreLabel}
></paper-input> ></ha-textfield>
<p> <p>
${cloudhook.managed ${cloudhook.managed
? html` ? html`
@ -98,10 +101,6 @@ export class DialogManageCloudhook extends LitElement {
`; `;
} }
private get _paperInput(): PaperInputElement {
return this.shadowRoot!.querySelector("paper-input")!;
}
private async _disableWebhook() { private async _disableWebhook() {
showConfirmationDialog(this, { showConfirmationDialog(this, {
text: this.hass!.localize( text: this.hass!.localize(
@ -117,14 +116,10 @@ export class DialogManageCloudhook extends LitElement {
} }
private _copyClipboard(ev: FocusEvent) { private _copyClipboard(ev: FocusEvent) {
// paper-input -> iron-input -> input const textField = ev.currentTarget as HaTextField;
const paperInput = ev.currentTarget as PaperInputElement;
const input = (paperInput.inputElement as any)
.inputElement as HTMLInputElement;
input.setSelectionRange(0, input.value.length);
try { try {
document.execCommand("copy"); copyToClipboard(textField.value);
paperInput.label = this.hass!.localize( textField.label = this.hass!.localize(
"ui.panel.config.cloud.dialog_cloudhook.copied_to_clipboard" "ui.panel.config.cloud.dialog_cloudhook.copied_to_clipboard"
); );
} catch (err: any) { } catch (err: any) {
@ -133,18 +128,19 @@ export class DialogManageCloudhook extends LitElement {
} }
private _restoreLabel() { private _restoreLabel() {
this._paperInput.label = inputLabel; this._input.label = inputLabel;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
haStyleDialog,
css` css`
ha-dialog { ha-dialog {
width: 650px; width: 650px;
} }
paper-input { ha-textfield {
margin-top: -8px; display: block;
} }
button.link { button.link {
color: var(--primary-color); color: var(--primary-color);

View File

@ -19,6 +19,7 @@ import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-radio"; import "../../../../components/ha-radio";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import "../../../../components/ha-textfield";
import type { HaRadio } from "../../../../components/ha-radio"; import type { HaRadio } from "../../../../components/ha-radio";
@customElement("dialog-energy-gas-settings") @customElement("dialog-energy-gas-settings")
@ -188,20 +189,19 @@ export class DialogEnergyGasSettings
></ha-radio> ></ha-radio>
</ha-formfield> </ha-formfield>
${this._costs === "number" ${this._costs === "number"
? html`<paper-input ? html`<ha-textfield
.label=${this.hass.localize( .label=${this.hass.localize(
`ui.panel.config.energy.gas.dialog.cost_number_input`, `ui.panel.config.energy.gas.dialog.cost_number_input`,
{ unit } { unit }
)} )}
no-label-float
class="price-options" class="price-options"
step=".01" step=".01"
type="number" type="number"
.value=${this._source.number_energy_price} .value=${this._source.number_energy_price}
@value-changed=${this._numberPriceChanged} @change=${this._numberPriceChanged}
.suffix=${`${this.hass.config.currency}/${unit}`}
> >
<span slot="suffix">${this.hass.config.currency}/${unit}</span> </ha-textfield>`
</paper-input>`
: ""} : ""}
<mwc-button @click=${this.closeDialog} slot="secondaryAction"> <mwc-button @click=${this.closeDialog} slot="secondaryAction">
@ -223,10 +223,10 @@ export class DialogEnergyGasSettings
this._costs = input.value as any; this._costs = input.value as any;
} }
private _numberPriceChanged(ev: CustomEvent) { private _numberPriceChanged(ev) {
this._source = { this._source = {
...this._source!, ...this._source!,
number_energy_price: Number(ev.detail.value), number_energy_price: Number(ev.target.value),
entity_energy_price: null, entity_energy_price: null,
stat_cost: null, stat_cost: null,
}; };
@ -295,13 +295,10 @@ export class DialogEnergyGasSettings
ha-formfield { ha-formfield {
display: block; display: block;
} }
ha-statistic-picker {
width: 100%;
}
.price-options { .price-options {
display: block; display: block;
padding-left: 52px; padding-left: 52px;
margin-top: -16px; margin-top: -8px;
} }
`, `,
]; ];

View File

@ -190,24 +190,21 @@ export class DialogEnergyGridFlowSettings
></ha-radio> ></ha-radio>
</ha-formfield> </ha-formfield>
${this._costs === "number" ${this._costs === "number"
? html`<paper-input ? html`<ha-textfield
.label=${this.hass.localize( .label=${this.hass.localize(
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_input` `ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_input`
)} )}
no-label-float
class="price-options" class="price-options"
step=".01" step=".01"
type="number" type="number"
.value=${this._source.number_energy_price} .value=${this._source.number_energy_price}
@value-changed=${this._numberPriceChanged} .suffix=${this.hass.localize(
>
<span slot="suffix"
>${this.hass.localize(
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_suffix`, `ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_suffix`,
{ currency: this.hass.config.currency } { currency: this.hass.config.currency }
)}</span )}
@change=${this._numberPriceChanged}
> >
</paper-input>` </ha-textfield>`
: ""} : ""}
<mwc-button @click=${this.closeDialog} slot="secondaryAction"> <mwc-button @click=${this.closeDialog} slot="secondaryAction">
@ -302,13 +299,10 @@ export class DialogEnergyGridFlowSettings
ha-formfield { ha-formfield {
display: block; display: block;
} }
ha-statistic-picker {
width: 100%;
}
.price-options { .price-options {
display: block; display: block;
padding-left: 52px; padding-left: 52px;
margin-top: -16px; margin-top: -8px;
} }
`, `,
]; ];

View File

@ -30,6 +30,7 @@ import "../../../components/ha-check-list-item";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import { import {
getConfigFlowHandlers,
getConfigFlowInProgressCollection, getConfigFlowInProgressCollection,
localizeConfigFlowTitle, localizeConfigFlowTitle,
subscribeConfigFlowInProgress, subscribeConfigFlowInProgress,
@ -51,7 +52,10 @@ import {
} from "../../../data/integration"; } from "../../../data/integration";
import { scanUSBDevices } from "../../../data/usb"; import { scanUSBDevices } from "../../../data/usb";
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow"; import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
@ -652,6 +656,19 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
if (!domain) { if (!domain) {
return; return;
} }
const handlers = await getConfigFlowHandlers(this.hass);
if (!handlers.includes(domain)) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.no_config_flow"
),
});
return;
}
const localize = await localizePromise; const localize = await localizePromise;
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {

View File

@ -1,7 +1,5 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js"; import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
@ -11,28 +9,30 @@ import { HaCheckbox } from "../../../../../components/ha-checkbox";
import "../../../../../components/ha-circular-progress"; import "../../../../../components/ha-circular-progress";
import { createCloseHeading } from "../../../../../components/ha-dialog"; import { createCloseHeading } from "../../../../../components/ha-dialog";
import "../../../../../components/ha-formfield"; import "../../../../../components/ha-formfield";
import "../../../../../components/ha-qr-scanner";
import "../../../../../components/ha-radio"; import "../../../../../components/ha-radio";
import "../../../../../components/ha-switch"; import "../../../../../components/ha-switch";
import "../../../../../components/ha-textfield";
import type { HaTextField } from "../../../../../components/ha-textfield";
import { import {
zwaveGrantSecurityClasses,
InclusionStrategy, InclusionStrategy,
MINIMUM_QR_STRING_LENGTH, MINIMUM_QR_STRING_LENGTH,
zwaveParseQrCode, PlannedProvisioningEntry,
provisionZwaveSmartStartNode, provisionZwaveSmartStartNode,
QRProvisioningInformation, QRProvisioningInformation,
RequestedGrant, RequestedGrant,
SecurityClass, SecurityClass,
stopZwaveInclusion, stopZwaveInclusion,
subscribeAddZwaveNode, subscribeAddZwaveNode,
ZWaveFeature,
zwaveGrantSecurityClasses,
zwaveParseQrCode,
zwaveSupportsFeature, zwaveSupportsFeature,
zwaveValidateDskAndEnterPin, zwaveValidateDskAndEnterPin,
ZWaveFeature,
PlannedProvisioningEntry,
} from "../../../../../data/zwave_js"; } from "../../../../../data/zwave_js";
import { haStyle, haStyleDialog } from "../../../../../resources/styles"; import { haStyle, haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node"; import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node";
import "../../../../../components/ha-qr-scanner";
export interface ZWaveJSAddNodeDevice { export interface ZWaveJSAddNodeDevice {
id: string; id: string;
@ -98,7 +98,7 @@ class DialogZWaveJSAddNode extends LitElement {
this._startInclusion(); this._startInclusion();
} }
@query("#pin-input") private _pinInput?: PaperInputElement; @query("#pin-input") private _pinInput?: HaTextField;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._entryId) { if (!this._entryId) {
@ -202,12 +202,11 @@ class DialogZWaveJSAddNode extends LitElement {
: "" : ""
} }
<div class="flex-container"> <div class="flex-container">
<paper-input <ha-textfield
label="PIN" label="PIN"
id="pin-input" id="pin-input"
@keyup=${this._handlePinKeyUp} @keyup=${this._handlePinKeyUp}
no-label-float ></ha-textfield>
></paper-input>
${this._dsk} ${this._dsk}
</div> </div>
<mwc-button <mwc-button
@ -814,6 +813,9 @@ class DialogZWaveJSAddNode extends LitElement {
width: 68px; width: 68px;
height: 48px; height: 48px;
} }
ha-textfield {
display: block;
}
.secondary { .secondary {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }

View File

@ -25,6 +25,7 @@ import "../../../../../components/ha-select";
import "../../../../../components/ha-settings-row"; import "../../../../../components/ha-settings-row";
import "../../../../../components/ha-svg-icon"; import "../../../../../components/ha-svg-icon";
import "../../../../../components/ha-switch"; import "../../../../../components/ha-switch";
import "../../../../../components/ha-textfield";
import { import {
computeDeviceName, computeDeviceName,
DeviceRegistryEntry, DeviceRegistryEntry,
@ -265,7 +266,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
if (item.configuration_value_type === "manual_entry") { if (item.configuration_value_type === "manual_entry") {
return html`${labelAndDescription} return html`${labelAndDescription}
<paper-input <ha-textfield
type="number" type="number"
.value=${item.value} .value=${item.value}
.min=${item.metadata.min} .min=${item.metadata.min}
@ -274,12 +275,12 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
.propertyKey=${item.property_key} .propertyKey=${item.property_key}
.key=${id} .key=${id}
.disabled=${!item.metadata.writeable} .disabled=${!item.metadata.writeable}
@value-changed=${this._numericInputChanged} @input=${this._numericInputChanged}
> >
${item.metadata.unit ${item.metadata.unit
? html`<span slot="suffix">${item.metadata.unit}</span>` ? html`<span slot="suffix">${item.metadata.unit}</span>`
: ""} : ""}
</paper-input> `; </ha-textfield>`;
} }
if (item.configuration_value_type === "enumerated") { if (item.configuration_value_type === "enumerated") {
@ -492,7 +493,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
font-size: 1.3em; font-size: 1.3em;
} }
:host(:not([narrow])) ha-settings-row paper-input { :host(:not([narrow])) ha-settings-row ha-textfield {
width: 30%; width: 30%;
text-align: right; text-align: right;
} }

View File

@ -1,20 +1,18 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { slugify } from "../../../../common/string/slugify"; import { slugify } from "../../../../common/string/slugify";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../../components/ha-dialog"; import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-formfield"; import "../../../../components/ha-form/ha-form";
import "../../../../components/ha-icon-picker"; import { HaFormSchema } from "../../../../components/ha-form/types";
import "../../../../components/ha-switch"; import { CoreFrontendUserData } from "../../../../data/frontend";
import type { HaSwitch } from "../../../../components/ha-switch";
import { import {
LovelaceDashboard, LovelaceDashboard,
LovelaceDashboardCreateParams, LovelaceDashboardCreateParams,
LovelaceDashboardMutableParams,
} from "../../../../data/lovelace"; } from "../../../../data/lovelace";
import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel"; import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { haStyleDialog } from "../../../../resources/styles"; import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail"; import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
@ -25,62 +23,54 @@ export class DialogLovelaceDashboardDetail extends LitElement {
@state() private _params?: LovelaceDashboardDetailsDialogParams; @state() private _params?: LovelaceDashboardDetailsDialogParams;
@state() private _urlPath!: LovelaceDashboard["url_path"]; @state() private _urlPathChanged = false;
@state() private _showInSidebar!: boolean; @state() private _data?: Partial<LovelaceDashboard>;
@state() private _icon!: string; @state() private _error?: Record<string, string>;
@state() private _title!: string;
@state()
private _requireAdmin!: LovelaceDashboard["require_admin"];
@state() private _error?: string;
@state() private _submitting = false; @state() private _submitting = false;
public async showDialog( public showDialog(params: LovelaceDashboardDetailsDialogParams): void {
params: LovelaceDashboardDetailsDialogParams
): Promise<void> {
this._params = params; this._params = params;
this._error = undefined; this._error = undefined;
this._urlPath = this._params.urlPath || ""; this._urlPathChanged = false;
if (this._params.dashboard) { if (this._params.dashboard) {
this._showInSidebar = !!this._params.dashboard.show_in_sidebar; this._data = this._params.dashboard;
this._icon = this._params.dashboard.icon || "";
this._title = this._params.dashboard.title || "";
this._requireAdmin = this._params.dashboard.require_admin || false;
} else { } else {
this._showInSidebar = true; this._data = {
this._icon = ""; show_in_sidebar: true,
this._title = ""; icon: "",
this._requireAdmin = false; title: "",
require_admin: false,
mode: "storage",
};
} }
await this.updateComplete; }
public closeDialog(): void {
this._params = undefined;
this._data = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._params) { if (!this._params || !this._data) {
return html``; return html``;
} }
const defaultPanelUrlPath = this.hass.defaultPanel; const defaultPanelUrlPath = this.hass.defaultPanel;
const urlInvalid = const titleInvalid = !this._data.title || !this._data.title.trim();
this._params.urlPath !== "lovelace" &&
!/^[a-zA-Z0-9_-]+-[a-zA-Z0-9_-]+$/.test(this._urlPath);
const titleInvalid = !this._title.trim();
const dir = computeRTLDirection(this.hass);
return html` return html`
<ha-dialog <ha-dialog
open open
@closed=${this._close} @closed=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, this.hass,
this._params.urlPath this._params.urlPath
? this._title || ? this._data.title ||
this.hass.localize( this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard" "ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
) )
@ -99,76 +89,14 @@ export class DialogLovelaceDashboardDetail extends LitElement {
"ui.panel.config.lovelace.dashboards.cant_edit_default" "ui.panel.config.lovelace.dashboards.cant_edit_default"
) )
: html` : html`
${this._error <ha-form
? html` <div class="error">${this._error}</div> ` .schema=${this._schema(this._params, this.hass.userData)}
: ""} .data=${this._data}
<div class="form"> .hass=${this.hass}
<paper-input .error=${this._error}
.value=${this._title} .computeLabel=${this._computeLabel}
@value-changed=${this._titleChanged} @value-changed=${this._valueChanged}
.label=${this.hass.localize( ></ha-form>
"ui.panel.config.lovelace.dashboards.detail.title"
)}
@blur=${this.hass.userData?.showAdvanced
? this._fillUrlPath
: undefined}
.invalid=${titleInvalid}
.errorMessage=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.title_required"
)}
dialogInitialFocus
></paper-input>
<ha-icon-picker
.value=${this._icon}
@value-changed=${this._iconChanged}
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.icon"
)}
></ha-icon-picker>
${!this._params.dashboard && this.hass.userData?.showAdvanced
? html`
<paper-input
.value=${this._urlPath}
@value-changed=${this._urlChanged}
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.url"
)}
.errorMessage=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
)}
.invalid=${urlInvalid}
></paper-input>
`
: ""}
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.show_sidebar"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._showInSidebar}
@change=${this._showSidebarChanged}
>
</ha-switch>
</ha-formfield>
</div>
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.require_admin"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._requireAdmin}
@change=${this._requireAdminChanged}
>
</ha-switch>
</ha-formfield>
</div>
</div>
`} `}
</div> </div>
${this._params.urlPath ${this._params.urlPath
@ -206,7 +134,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._updateDashboard} @click=${this._updateDashboard}
.disabled=${urlInvalid || titleInvalid || this._submitting} .disabled=${(this._error && "url_path" in this._error) ||
titleInvalid ||
this._submitting}
dialogInitialFocus dialogInitialFocus
> >
${this._params.urlPath ${this._params.urlPath
@ -223,41 +153,97 @@ export class DialogLovelaceDashboardDetail extends LitElement {
`; `;
} }
private _urlChanged(ev: PolymerChangedEvent<string>) { private _schema = memoizeOne(
this._error = undefined; (
this._urlPath = ev.detail.value; params: LovelaceDashboardDetailsDialogParams,
} userData: CoreFrontendUserData | null | undefined
) =>
[
{
name: "title",
required: true,
selector: {
text: {},
},
},
{
name: "icon",
required: true,
selector: {
icon: {},
},
},
!params.dashboard &&
userData?.showAdvanced && {
name: "url_path",
required: true,
selector: { text: {} },
},
{
name: "require_admin",
required: true,
selector: {
boolean: {},
},
},
{
name: "show_in_sidebar",
required: true,
selector: {
boolean: {},
},
},
].filter(Boolean)
);
private _iconChanged(ev: PolymerChangedEvent<string>) { private _computeLabel = (entry: HaFormSchema): string =>
this._error = undefined; this.hass.localize(
this._icon = ev.detail.value; `ui.panel.config.lovelace.dashboards.detail.${
} entry.name === "show_in_sidebar"
? "show_sidebar"
: entry.name === "url_path"
? "url"
: entry.name
}`
);
private _titleChanged(ev: PolymerChangedEvent<string>) { private _valueChanged(ev: CustomEvent) {
this._error = undefined; this._error = undefined;
this._title = ev.detail.value; const value = ev.detail.value;
if (!this.hass.userData?.showAdvanced) { if (value.url_path !== this._data?.url_path) {
this._fillUrlPath(); this._urlPathChanged = true;
if (
!value.url_path ||
value.url_path === "lovelace" ||
!/^[a-zA-Z0-9_-]+-[a-zA-Z0-9_-]+$/.test(value.url_path)
) {
this._error = {
url_path: this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
),
};
}
}
if (value.title !== this._data?.title) {
this._data = value;
this._fillUrlPath(value.title);
} else {
this._data = value;
} }
} }
private _fillUrlPath() { private _fillUrlPath(title: string) {
if ((this.hass.userData?.showAdvanced && this._urlPath) || !this._title) { if ((this.hass.userData?.showAdvanced && this._urlPathChanged) || !title) {
return; return;
} }
const slugifyTitle = slugify(this._title, "-"); const slugifyTitle = slugify(title, "-");
this._urlPath = slugifyTitle.includes("-") this._data = {
...this._data,
url_path: slugifyTitle.includes("-")
? slugifyTitle ? slugifyTitle
: `lovelace-${slugifyTitle}`; : `lovelace-${slugifyTitle}`,
} };
private _showSidebarChanged(ev: Event) {
this._showInSidebar = (ev.target as HaSwitch).checked;
}
private _requireAdminChanged(ev: Event) {
this._requireAdmin = (ev.target as HaSwitch).checked;
} }
private _toggleDefault() { private _toggleDefault() {
@ -273,29 +259,20 @@ export class DialogLovelaceDashboardDetail extends LitElement {
private async _updateDashboard() { private async _updateDashboard() {
if (this._params?.urlPath && !this._params.dashboard?.id) { if (this._params?.urlPath && !this._params.dashboard?.id) {
this._close(); this.closeDialog();
} }
this._submitting = true; this._submitting = true;
try { try {
const values: Partial<LovelaceDashboardMutableParams> = {
require_admin: this._requireAdmin,
show_in_sidebar: this._showInSidebar,
icon: this._icon || undefined,
title: this._title,
};
if (this._params!.dashboard) { if (this._params!.dashboard) {
await this._params!.updateDashboard(values); await this._params!.updateDashboard(this._data as LovelaceDashboard);
} else { } else {
(values as LovelaceDashboardCreateParams).url_path =
this._urlPath.trim();
(values as LovelaceDashboardCreateParams).mode = "storage";
await this._params!.createDashboard( await this._params!.createDashboard(
values as LovelaceDashboardCreateParams this._data as LovelaceDashboardCreateParams
); );
} }
this._close(); this.closeDialog();
} catch (err: any) { } catch (err: any) {
this._error = err?.message || "Unknown error"; this._error = { base: err?.message || "Unknown error" };
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
@ -305,26 +282,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
this._submitting = true; this._submitting = true;
try { try {
if (await this._params!.removeDashboard()) { if (await this._params!.removeDashboard()) {
this._close(); this.closeDialog();
} }
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
} }
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [haStyleDialog, css``];
haStyleDialog,
css`
ha-switch {
padding: 16px 0;
}
`,
];
} }
} }

View File

@ -1,21 +1,20 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../common/dom/stop_propagation"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../components/ha-dialog"; import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-select"; import "../../../../components/ha-form/ha-form";
import { import { HaFormSchema } from "../../../../components/ha-form/types";
LovelaceResource, import { LovelaceResourcesMutableParams } from "../../../../data/lovelace";
LovelaceResourcesMutableParams,
} from "../../../../data/lovelace";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { haStyleDialog } from "../../../../resources/styles"; import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail"; import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";
const detectResourceType = (url: string) => { const detectResourceType = (url?: string) => {
if (!url) {
return undefined;
}
const ext = url.split(".").pop() || ""; const ext = url.split(".").pop() || "";
if (ext === "css") { if (ext === "css") {
@ -35,38 +34,41 @@ export class DialogLovelaceResourceDetail extends LitElement {
@state() private _params?: LovelaceResourceDetailsDialogParams; @state() private _params?: LovelaceResourceDetailsDialogParams;
@state() private _url!: LovelaceResource["url"]; @state() private _data?: Partial<LovelaceResourcesMutableParams>;
@state() private _type?: LovelaceResource["type"]; @state() private _error?: Record<string, string>;
@state() private _error?: string;
@state() private _submitting = false; @state() private _submitting = false;
public async showDialog( public showDialog(params: LovelaceResourceDetailsDialogParams): void {
params: LovelaceResourceDetailsDialogParams
): Promise<void> {
this._params = params; this._params = params;
this._error = undefined; this._error = undefined;
if (this._params.resource) { if (this._params.resource) {
this._url = this._params.resource.url || ""; this._data = {
this._type = this._params.resource.type || undefined; url: this._params.resource.url,
res_type: this._params.resource.type,
};
} else { } else {
this._url = ""; this._data = {
this._type = undefined; url: "",
};
} }
await this.updateComplete; }
public closeDialog(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._params) { if (!this._params) {
return html``; return html``;
} }
const urlInvalid = this._url.trim() === ""; const urlInvalid = !this._data?.url || this._data.url.trim() === "";
return html` return html`
<ha-dialog <ha-dialog
open open
@closed=${this._close} @closed=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
@ -79,68 +81,25 @@ export class DialogLovelaceResourceDetail extends LitElement {
)} )}
> >
<div> <div>
${this._error ? html` <div class="error">${this._error}</div> ` : ""} <ha-alert
<div class="form"> alert-type="warning"
<h3 class="warning"> .title=${this.hass!.localize(
${this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.warning_header" "ui.panel.config.lovelace.resources.detail.warning_header"
)} )}
</h3> >
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.warning_text" "ui.panel.config.lovelace.resources.detail.warning_text"
)} )}
<paper-input </ha-alert>
.value=${this._url}
@value-changed=${this._urlChanged} <ha-form
.label=${this.hass!.localize( .schema=${this._schema(this._data)}
"ui.panel.config.lovelace.resources.detail.url" .data=${this._data}
)} .hass=${this.hass}
.errorMessage=${this.hass!.localize( .error=${this._error}
"ui.panel.config.lovelace.resources.detail.url_error_msg" .computeLabel=${this._computeLabel}
)} @value-changed=${this._valueChanged}
.invalid=${urlInvalid} ></ha-form>
dialogInitialFocus
></paper-input>
<br />
<ha-select
.label=${this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.type"
)}
.value=${this._type}
@selected=${this._typeChanged}
@closed=${stopPropagation}
.invalid=${!this._type}
>
<mwc-list-item value="module">
${this.hass!.localize(
"ui.panel.config.lovelace.resources.types.module"
)}
</mwc-list-item>
${this._type === "js"
? html`
<mwc-list-item value="js">
${this.hass!.localize(
"ui.panel.config.lovelace.resources.types.js"
)}
</mwc-list-item>
`
: ""}
<mwc-list-item value="css">
${this.hass!.localize(
"ui.panel.config.lovelace.resources.types.css"
)}
</mwc-list-item>
${this._type === "html"
? html`
<mwc-list-item value="html">
${this.hass!.localize(
"ui.panel.config.lovelace.resources.types.html"
)}
</mwc-list-item>
`
: ""}
</ha-select>
</div>
</div> </div>
${this._params.resource ${this._params.resource
? html` ? html`
@ -159,7 +118,7 @@ export class DialogLovelaceResourceDetail extends LitElement {
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._updateResource} @click=${this._updateResource}
.disabled=${urlInvalid || !this._type || this._submitting} .disabled=${urlInvalid || !this._data?.res_type || this._submitting}
> >
${this._params.resource ${this._params.resource
? this.hass!.localize( ? this.hass!.localize(
@ -173,37 +132,86 @@ export class DialogLovelaceResourceDetail extends LitElement {
`; `;
} }
private _urlChanged(ev: PolymerChangedEvent<string>) { private _schema = memoizeOne((data) => [
this._error = undefined; {
this._url = ev.detail.value; name: "url",
if (!this._type) { required: true,
this._type = detectResourceType(this._url); selector: {
} text: {},
} },
},
{
name: "res_type",
required: true,
selector: {
select: {
options: [
{
value: "module",
label: this.hass!.localize(
"ui.panel.config.lovelace.resources.types.module"
),
},
{
value: "css",
label: this.hass!.localize(
"ui.panel.config.lovelace.resources.types.css"
),
},
data.type === "js" && {
value: "js",
label: this.hass!.localize(
"ui.panel.config.lovelace.resources.types.js"
),
},
data.type === "html" && {
value: "html",
label: this.hass!.localize(
"ui.panel.config.lovelace.resources.types.html"
),
},
].filter(Boolean),
},
},
},
]);
private _typeChanged(ev) { private _computeLabel = (entry: HaFormSchema): string =>
this._type = ev.target.value; this.hass.localize(
`ui.panel.config.lovelace.resources.detail.${entry.name}`
);
private _valueChanged(ev: CustomEvent) {
this._data = ev.detail.value;
if (!this._data!.res_type) {
const type = detectResourceType(this._data!.url);
if (!type) {
return;
}
this._data = {
...this._data,
res_type: type,
};
}
} }
private async _updateResource() { private async _updateResource() {
if (!this._type) { if (!this._data?.res_type) {
return; return;
} }
this._submitting = true; this._submitting = true;
try { try {
const values: LovelaceResourcesMutableParams = {
url: this._url.trim(),
res_type: this._type,
};
if (this._params!.resource) { if (this._params!.resource) {
await this._params!.updateResource(values); await this._params!.updateResource(this._data!);
} else { } else {
await this._params!.createResource(values); await this._params!.createResource(
this._data! as LovelaceResourcesMutableParams
);
} }
this._params = undefined; this._params = undefined;
} catch (err: any) { } catch (err: any) {
this._error = err?.message || "Unknown error"; this._error = { base: err?.message || "Unknown error" };
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
@ -213,26 +221,15 @@ export class DialogLovelaceResourceDetail extends LitElement {
this._submitting = true; this._submitting = true;
try { try {
if (await this._params!.removeResource()) { if (await this._params!.removeResource()) {
this._close(); this.closeDialog();
} }
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
} }
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return haStyleDialog;
haStyleDialog,
css`
.warning {
color: var(--error-color);
}
`,
];
} }
} }

View File

@ -464,6 +464,9 @@ class DialogPersonDetail extends LitElement {
ha-textfield { ha-textfield {
display: block; display: block;
} }
ha-picture-upload {
margin-top: 16px;
}
ha-formfield { ha-formfield {
display: block; display: block;
padding: 16px 0; padding: 16px 0;

View File

@ -186,9 +186,7 @@ export class HaBlueprintScriptEditor extends LitElement {
--paper-time-input-justify-content: flex-end; --paper-time-input-justify-content: flex-end;
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
} }
:host(:not([narrow])) ha-settings-row paper-input { :host(:not([narrow])) ha-settings-row ha-textfield,
width: 60%;
}
:host(:not([narrow])) ha-settings-row ha-selector { :host(:not([narrow])) ha-settings-row ha-selector {
width: 60%; width: 60%;
} }

View File

@ -1,17 +1,12 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord"; import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../components/ha-dialog"; import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield"; import "../../../components/ha-form/ha-form";
import "../../../components/ha-icon-picker"; import { HaFormSchema } from "../../../components/ha-form/types";
import "../../../components/ha-switch";
import "../../../components/map/ha-locations-editor";
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone"; import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone";
import { haStyleDialog } from "../../../resources/styles"; import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -20,19 +15,9 @@ import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
class DialogZoneDetail extends LitElement { class DialogZoneDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _name!: string; @state() private _error?: Record<string, string>;
@state() private _icon!: string; @state() private _data?: ZoneMutableParams;
@state() private _latitude!: number;
@state() private _longitude!: number;
@state() private _passive!: boolean;
@state() private _radius!: number;
@state() private _error?: string;
@state() private _params?: ZoneDetailDialogParams; @state() private _params?: ZoneDetailDialogParams;
@ -42,13 +27,7 @@ class DialogZoneDetail extends LitElement {
this._params = params; this._params = params;
this._error = undefined; this._error = undefined;
if (this._params.entry) { if (this._params.entry) {
this._name = this._params.entry.name || ""; this._data = this._params.entry;
this._icon = this._params.entry.icon || "";
this._latitude = this._params.entry.latitude || this.hass.config.latitude;
this._longitude =
this._params.entry.longitude || this.hass.config.longitude;
this._passive = this._params.entry.passive || false;
this._radius = this._params.entry.radius || 100;
} else { } else {
const initConfig = getZoneEditorInitData(); const initConfig = getZoneEditorInitData();
let movedHomeLocation; let movedHomeLocation;
@ -59,30 +38,34 @@ class DialogZoneDetail extends LitElement {
Math.random() * 500 * (Math.random() < 0.5 ? -1 : 1) Math.random() * 500 * (Math.random() < 0.5 ? -1 : 1)
); );
} }
this._latitude = initConfig?.latitude || movedHomeLocation[0]; this._data = {
this._longitude = initConfig?.longitude || movedHomeLocation[1]; latitude: initConfig?.latitude || movedHomeLocation[0],
this._name = initConfig?.name || ""; longitude: initConfig?.longitude || movedHomeLocation[1],
this._icon = initConfig?.icon || "mdi:map-marker"; name: initConfig?.name || "",
icon: initConfig?.icon || "mdi:map-marker",
this._passive = false; passive: false,
this._radius = 100; radius: 100,
};
} }
} }
public closeDialog(): void { public closeDialog(): void {
this._params = undefined; this._params = undefined;
this._data = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._params) { if (!this._params || !this._data) {
return html``; return html``;
} }
const nameInvalid = this._name.trim() === ""; const nameInvalid = this._data.name.trim() === "";
const iconInvalid = Boolean(this._icon && !this._icon.trim().includes(":")); const iconInvalid = Boolean(
const latInvalid = String(this._latitude) === ""; this._data.icon && !this._data.icon.trim().includes(":")
const lngInvalid = String(this._longitude) === ""; );
const radiusInvalid = String(this._radius) === ""; const latInvalid = String(this._data.latitude) === "";
const lngInvalid = String(this._data.longitude) === "";
const radiusInvalid = String(this._data.radius) === "";
const valid = const valid =
!nameInvalid && !nameInvalid &&
@ -105,96 +88,15 @@ class DialogZoneDetail extends LitElement {
)} )}
> >
<div> <div>
${this._error ? html` <div class="error">${this._error}</div> ` : ""} <ha-form
<div class="form">
<paper-input
dialogInitialFocus
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
.label=${this.hass!.localize("ui.panel.config.zone.detail.name")}
.errorMessage=${this.hass!.localize(
"ui.panel.config.zone.detail.required_error_msg"
)}
required
auto-validate
></paper-input>
<ha-icon-picker
.value=${this._icon}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
.label=${this.hass!.localize("ui.panel.config.zone.detail.icon")}
.errorMessage=${this.hass!.localize(
"ui.panel.config.zone.detail.icon_error_msg"
)}
.invalid=${iconInvalid}
></ha-icon-picker>
<ha-locations-editor
class="flex"
.hass=${this.hass} .hass=${this.hass}
.locations=${this._location( .schema=${this._schema(this._data.icon)}
this._latitude, .data=${this._formData(this._data)}
this._longitude, .error=${this._error}
this._radius, .computeLabel=${this._computeLabel}
this._passive, class=${this._data.passive ? "passive" : ""}
this._icon
)}
@location-updated=${this._locationChanged}
@radius-updated=${this._radiusChanged}
></ha-locations-editor>
<div class="location">
<paper-input
.value=${this._latitude}
.configValue=${"latitude"}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
.label=${this.hass!.localize( ></ha-form>
"ui.panel.config.zone.detail.latitude"
)}
.errorMessage=${this.hass!.localize(
"ui.panel.config.zone.detail.required_error_msg"
)}
.invalid=${latInvalid}
></paper-input>
<paper-input
.value=${this._longitude}
.configValue=${"longitude"}
@value-changed=${this._valueChanged}
.label=${this.hass!.localize(
"ui.panel.config.zone.detail.longitude"
)}
.errorMessage=${this.hass!.localize(
"ui.panel.config.zone.detail.required_error_msg"
)}
.invalid=${lngInvalid}
></paper-input>
</div>
<paper-input
.value=${this._radius}
.configValue=${"radius"}
@value-changed=${this._valueChanged}
.label=${this.hass!.localize(
"ui.panel.config.zone.detail.radius"
)}
.errorMessage=${this.hass!.localize(
"ui.panel.config.zone.detail.required_error_msg"
)}
.invalid=${radiusInvalid}
></paper-input>
<p>
${this.hass!.localize("ui.panel.config.zone.detail.passive_note")}
</p>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.zone.detail.passive"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._passive}
@change=${this._passiveChanged}
></ha-switch>
</ha-formfield>
</div>
</div> </div>
${this._params.entry ${this._params.entry
? html` ? html`
@ -221,74 +123,94 @@ class DialogZoneDetail extends LitElement {
`; `;
} }
private _location = memoizeOne( private _schema = memoizeOne((icon?: string): HaFormSchema[] => [
(
lat: number,
lng: number,
radius: number,
passive: boolean,
icon: string
): MarkerLocation[] => {
const computedStyles = getComputedStyle(this);
const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
const passiveRadiusColor = computedStyles.getPropertyValue(
"--secondary-text-color"
);
return [
{ {
id: "location", name: "name",
latitude: Number(lat), required: true,
longitude: Number(lng), selector: {
radius, text: {},
radius_color: passive ? passiveRadiusColor : zoneRadiusColor,
icon,
location_editable: true,
radius_editable: true,
}, },
]; },
} {
); name: "icon",
required: false,
selector: {
icon: {},
},
},
{
name: "location",
required: true,
selector: { location: { radius: true, icon } },
},
{
name: "",
type: "grid",
schema: [
{
name: "latitude",
required: true,
selector: { text: {} },
},
{
name: "longitude",
required: true,
private _locationChanged(ev: CustomEvent) { selector: { text: {} },
[this._latitude, this._longitude] = ev.detail.location; },
} ],
},
{ name: "passive_note", type: "constant" },
{ name: "passive", selector: { boolean: {} } },
{
name: "radius",
required: false,
selector: { number: { min: 0, max: 999999, mode: "box" } },
},
]);
private _radiusChanged(ev: CustomEvent) { private _formData = memoizeOne((data: ZoneMutableParams) => ({
this._radius = ev.detail.radius; ...data,
} location: {
latitude: data.latitude,
private _passiveChanged(ev) { longitude: data.longitude,
this._passive = ev.target.checked; radius: data.radius,
} },
}));
private _valueChanged(ev: CustomEvent) { private _valueChanged(ev: CustomEvent) {
const configValue = (ev.target as any).configValue;
this._error = undefined; this._error = undefined;
this[`_${configValue}`] = ev.detail.value; const value = ev.detail.value;
if (
value.location.latitude !== this._data!.latitude ||
value.location.longitude !== this._data!.longitude ||
value.location.radius !== this._data!.radius
) {
value.latitude = value.location.latitude;
value.longitude = value.location.longitude;
value.radius = Math.round(value.location.radius);
} }
delete value.location;
if (!value.icon) {
delete value.icon;
}
this._data = value;
}
private _computeLabel = (entry: HaFormSchema): string =>
this.hass.localize(`ui.panel.config.zone.detail.${entry.name}`);
private async _updateEntry() { private async _updateEntry() {
this._submitting = true; this._submitting = true;
try { try {
const values: ZoneMutableParams = {
name: this._name.trim(),
latitude: this._latitude,
longitude: this._longitude,
passive: this._passive,
radius: this._radius,
};
if (this._icon) {
values.icon = this._icon.trim();
}
if (this._params!.entry) { if (this._params!.entry) {
await this._params!.updateEntry!(values); await this._params!.updateEntry!(this._data!);
} else { } else {
await this._params!.createEntry(values); await this._params!.createEntry(this._data!);
} }
this._params = undefined; this.closeDialog();
} catch (err: any) { } catch (err: any) {
this._error = err ? err.message : "Unknown error"; this._error = { base: err ? err.message : "Unknown error" };
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
@ -309,24 +231,18 @@ class DialogZoneDetail extends LitElement {
return [ return [
haStyleDialog, haStyleDialog,
css` css`
.location { ha-dialog {
display: flex; --mdc-dialog-min-width: 600px;
} }
.location > * { @media all and (max-width: 450px), all and (max-height: 500px) {
flex-grow: 1; ha-dialog {
min-width: 0; --mdc-dialog-min-width: calc(
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
);
} }
.location > *:first-child {
margin-right: 4px;
} }
.location > *:last-child { ha-form.passive {
margin-left: 4px; --zone-radius-color: var(--secondary-text-color);
}
ha-locations-editor {
margin-top: 16px;
}
a {
color: var(--primary-color);
} }
`, `,
]; ];

View File

@ -1,11 +1,11 @@
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { EditorTarget } from "../editor/types"; import { EditorTarget } from "../editor/types";
import "../../../components/ha-textfield";
@customElement("hui-input-list-editor") @customElement("hui-input-list-editor")
export class HuiInputListEditor extends LitElement { export class HuiInputListEditor extends LitElement {
@ -23,30 +23,31 @@ export class HuiInputListEditor extends LitElement {
return html` return html`
${this.value.map( ${this.value.map(
(listEntry, index) => html` (listEntry, index) => html`
<paper-input <ha-textfield
label=${this.inputLabel} .label=${this.inputLabel}
.value=${listEntry} .value=${listEntry}
.configValue=${"entry"} .configValue=${"entry"}
.index=${index} .index=${index}
@value-changed=${this._valueChanged} @input=${this._valueChanged}
@blur=${this._consolidateEntries} @blur=${this._consolidateEntries}
@keydown=${this._handleKeyDown} @keydown=${this._handleKeyDown}
iconTrailing
><ha-icon-button ><ha-icon-button
slot="suffix" slot="trailingIcon"
class="clear-button" class="clear-button"
.path=${mdiClose} .path=${mdiClose}
no-ripple no-ripple
@click=${this._removeEntry} @click=${this._removeEntry}
.label=${this.hass!.localize("ui.common.clear")} .label=${this.hass!.localize("ui.common.clear")}
>Clear</ha-icon-button
></paper-input
> >
</ha-icon-button>
</ha-textfield>
` `
)} )}
<paper-input <ha-textfield
label=${this.inputLabel} .label=${this.inputLabel}
@change=${this._addEntry} @change=${this._addEntry}
></paper-input> ></ha-textfield>
`; `;
} }
@ -103,10 +104,12 @@ export class HuiInputListEditor extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
ha-icon-button { ha-icon-button {
--mdc-icon-button-size: 24px; margin-right: -24px;
padding: 2px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-textfield {
display: block;
}
`; `;
} }
} }

View File

@ -1,5 +1,4 @@
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { import {
@ -24,7 +23,7 @@ import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { processEditorEntities } from "../process-editor-entities"; import { processEditorEntities } from "../process-editor-entities";
import { entitiesConfigStruct } from "../structs/entities-struct"; import { entitiesConfigStruct } from "../structs/entities-struct";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { HaFormSchema } from "../../../../components/ha-form/types"; import { HaFormSchema } from "../../../../components/ha-form/types";
@ -127,10 +126,6 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return; return;
} }
const target = ev.target! as EditorTarget;
if (!target.configValue) {
return;
}
const value = ev.detail.value; const value = ev.detail.value;

View File

@ -1,4 +1,3 @@
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { assert, object, optional, string, assign } from "superstruct"; import { assert, object, optional, string, assign } from "superstruct";

View File

@ -153,7 +153,7 @@ export class HuiStatisticsGraphCardEditor
.pickedStatisticLabel=${`Statistic`} .pickedStatisticLabel=${`Statistic`}
.value=${this._configEntities} .value=${this._configEntities}
.configValue=${"entities"} .configValue=${"entities"}
@value-changed=${this._valueChanged} @value-changed=${this._entitiesChanged}
></ha-statistics-picker> ></ha-statistics-picker>
</div> </div>
`; `;
@ -163,6 +163,12 @@ export class HuiStatisticsGraphCardEditor
fireEvent(this, "config-changed", { config: ev.detail.value }); fireEvent(this, "config-changed", { config: ev.detail.value });
} }
private _entitiesChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", {
config: { ...this._config!, entities: ev.detail.value },
});
}
private _computeLabelCallback = (schema: HaFormSchema) => private _computeLabelCallback = (schema: HaFormSchema) =>
this.hass!.localize( this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}` `ui.panel.lovelace.editor.card.generic.${schema.name}`

View File

@ -87,6 +87,7 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
} }
ha-select { ha-select {
width: 100%; width: 100%;
--ha-select-min-width: 0;
} }
`; `;

View File

@ -2605,6 +2605,7 @@
"finish": "Finish", "finish": "Finish",
"submit": "Submit", "submit": "Submit",
"next": "Next", "next": "Next",
"no_config_flow": "This integration does not support configuration via the UI. If you followed this link from the Home Assistant website, make sure you run the latest version of Home Assistant.",
"not_all_required_fields": "Not all required fields are filled in.", "not_all_required_fields": "Not all required fields are filled in.",
"error_saving_area": "Error saving area: {error}", "error_saving_area": "Error saving area: {error}",
"created_config": "Created configuration for {name}.", "created_config": "Created configuration for {name}.",