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",
icon: "Icon",
media: "Media",
location: "Location",
},
schema: [
{ name: "addon", selector: { addon: {} } },
@ -75,6 +76,10 @@ const SCHEMAS: {
media: {},
},
},
{
name: "location",
selector: { location: { radius: true, icon: "mdi:home" } },
},
],
},
{

View File

@ -168,6 +168,11 @@ const SCHEMAS: {
},
icon: { name: "Icon", selector: { icon: {} } },
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 "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,9 @@ export class HaFormConstant extends LitElement implements HaFormElement {
@property() public label!: string;
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 {

View File

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

View File

@ -40,7 +40,7 @@ export interface HaFormSelector extends HaFormBaseSchema {
export interface HaFormConstantSchema extends HaFormBaseSchema {
type: "constant";
value: string;
value?: string;
}
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 {
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-media";
import "./ha-selector-theme";
import "./ha-selector-location";
@customElement("ha-selector")
export class HaSelector extends LitElement {

View File

@ -9,6 +9,12 @@ export class HaTextField extends TextFieldBase {
@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) {
super.updated(changedProperties);
if (
@ -53,6 +59,11 @@ export class HaTextField extends TextFieldBase {
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 {
color: var(--secondary-text-color);
}

View File

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

View File

@ -29,7 +29,7 @@ export const createImage = async (
body: fd,
});
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) {
throw new Error("Unknown error");
}

View File

@ -43,7 +43,7 @@ export const uploadLocalMedia = async (
}
);
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) {
throw new Error("Unknown error");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -190,24 +190,21 @@ export class DialogEnergyGridFlowSettings
></ha-radio>
</ha-formfield>
${this._costs === "number"
? html`<paper-input
? html`<ha-textfield
.label=${this.hass.localize(
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_input`
)}
no-label-float
class="price-options"
step=".01"
type="number"
.value=${this._source.number_energy_price}
@value-changed=${this._numberPriceChanged}
>
<span slot="suffix"
>${this.hass.localize(
.suffix=${this.hass.localize(
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_suffix`,
{ currency: this.hass.config.currency }
)}</span
)}
@change=${this._numberPriceChanged}
>
</paper-input>`
</ha-textfield>`
: ""}
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
@ -302,13 +299,10 @@ export class DialogEnergyGridFlowSettings
ha-formfield {
display: block;
}
ha-statistic-picker {
width: 100%;
}
.price-options {
display: block;
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 { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import {
getConfigFlowHandlers,
getConfigFlowInProgressCollection,
localizeConfigFlowTitle,
subscribeConfigFlowInProgress,
@ -51,7 +52,10 @@ import {
} from "../../../data/integration";
import { scanUSBDevices } from "../../../data/usb";
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-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
@ -652,6 +656,19 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
if (!domain) {
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;
if (
!(await showConfirmationDialog(this, {

View File

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

View File

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

View File

@ -1,20 +1,18 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
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 { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon-picker";
import "../../../../components/ha-switch";
import type { HaSwitch } from "../../../../components/ha-switch";
import "../../../../components/ha-form/ha-form";
import { HaFormSchema } from "../../../../components/ha-form/types";
import { CoreFrontendUserData } from "../../../../data/frontend";
import {
LovelaceDashboard,
LovelaceDashboardCreateParams,
LovelaceDashboardMutableParams,
} from "../../../../data/lovelace";
import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
@ -25,62 +23,54 @@ export class DialogLovelaceDashboardDetail extends LitElement {
@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 _title!: string;
@state()
private _requireAdmin!: LovelaceDashboard["require_admin"];
@state() private _error?: string;
@state() private _error?: Record<string, string>;
@state() private _submitting = false;
public async showDialog(
params: LovelaceDashboardDetailsDialogParams
): Promise<void> {
public showDialog(params: LovelaceDashboardDetailsDialogParams): void {
this._params = params;
this._error = undefined;
this._urlPath = this._params.urlPath || "";
this._urlPathChanged = false;
if (this._params.dashboard) {
this._showInSidebar = !!this._params.dashboard.show_in_sidebar;
this._icon = this._params.dashboard.icon || "";
this._title = this._params.dashboard.title || "";
this._requireAdmin = this._params.dashboard.require_admin || false;
this._data = this._params.dashboard;
} else {
this._showInSidebar = true;
this._icon = "";
this._title = "";
this._requireAdmin = false;
this._data = {
show_in_sidebar: true,
icon: "",
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 {
if (!this._params) {
if (!this._params || !this._data) {
return html``;
}
const defaultPanelUrlPath = this.hass.defaultPanel;
const urlInvalid =
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);
const titleInvalid = !this._data.title || !this._data.title.trim();
return html`
<ha-dialog
open
@closed=${this._close}
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this._params.urlPath
? this._title ||
? this._data.title ||
this.hass.localize(
"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"
)
: html`
${this._error
? html` <div class="error">${this._error}</div> `
: ""}
<div class="form">
<paper-input
.value=${this._title}
@value-changed=${this._titleChanged}
.label=${this.hass.localize(
"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>
<ha-form
.schema=${this._schema(this._params, this.hass.userData)}
.data=${this._data}
.hass=${this.hass}
.error=${this._error}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
`}
</div>
${this._params.urlPath
@ -206,7 +134,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
<mwc-button
slot="primaryAction"
@click=${this._updateDashboard}
.disabled=${urlInvalid || titleInvalid || this._submitting}
.disabled=${(this._error && "url_path" in this._error) ||
titleInvalid ||
this._submitting}
dialogInitialFocus
>
${this._params.urlPath
@ -223,41 +153,97 @@ export class DialogLovelaceDashboardDetail extends LitElement {
`;
}
private _urlChanged(ev: PolymerChangedEvent<string>) {
this._error = undefined;
this._urlPath = ev.detail.value;
}
private _schema = memoizeOne(
(
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>) {
this._error = undefined;
this._icon = ev.detail.value;
}
private _computeLabel = (entry: HaFormSchema): string =>
this.hass.localize(
`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._title = ev.detail.value;
if (!this.hass.userData?.showAdvanced) {
this._fillUrlPath();
const value = ev.detail.value;
if (value.url_path !== this._data?.url_path) {
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() {
if ((this.hass.userData?.showAdvanced && this._urlPath) || !this._title) {
private _fillUrlPath(title: string) {
if ((this.hass.userData?.showAdvanced && this._urlPathChanged) || !title) {
return;
}
const slugifyTitle = slugify(this._title, "-");
this._urlPath = slugifyTitle.includes("-")
const slugifyTitle = slugify(title, "-");
this._data = {
...this._data,
url_path: slugifyTitle.includes("-")
? 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;
: `lovelace-${slugifyTitle}`,
};
}
private _toggleDefault() {
@ -273,29 +259,20 @@ export class DialogLovelaceDashboardDetail extends LitElement {
private async _updateDashboard() {
if (this._params?.urlPath && !this._params.dashboard?.id) {
this._close();
this.closeDialog();
}
this._submitting = true;
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) {
await this._params!.updateDashboard(values);
await this._params!.updateDashboard(this._data as LovelaceDashboard);
} else {
(values as LovelaceDashboardCreateParams).url_path =
this._urlPath.trim();
(values as LovelaceDashboardCreateParams).mode = "storage";
await this._params!.createDashboard(
values as LovelaceDashboardCreateParams
this._data as LovelaceDashboardCreateParams
);
}
this._close();
this.closeDialog();
} catch (err: any) {
this._error = err?.message || "Unknown error";
this._error = { base: err?.message || "Unknown error" };
} finally {
this._submitting = false;
}
@ -305,26 +282,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
this._submitting = true;
try {
if (await this._params!.removeDashboard()) {
this._close();
this.closeDialog();
}
} finally {
this._submitting = false;
}
}
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-switch {
padding: 16px 0;
}
`,
];
return [haStyleDialog, css``];
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -153,7 +153,7 @@ export class HuiStatisticsGraphCardEditor
.pickedStatisticLabel=${`Statistic`}
.value=${this._configEntities}
.configValue=${"entities"}
@value-changed=${this._valueChanged}
@value-changed=${this._entitiesChanged}
></ha-statistics-picker>
</div>
`;
@ -163,6 +163,12 @@ export class HuiStatisticsGraphCardEditor
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) =>
this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`

View File

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

View File

@ -2605,6 +2605,7 @@
"finish": "Finish",
"submit": "Submit",
"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.",
"error_saving_area": "Error saving area: {error}",
"created_config": "Created configuration for {name}.",