mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Migrate ha-form to lit (#4000)
* Migrate ha-form to lit * Fix import path * Update * add default, change suffix, fix import * Fix select
This commit is contained in:
parent
035057b185
commit
00f2d36cb5
@ -7,7 +7,7 @@ import {
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "../components/ha-form";
|
||||
import "../components/ha-form/ha-form";
|
||||
import "../components/ha-markdown";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { AuthProvider } from "../data/auth";
|
||||
|
@ -1,265 +0,0 @@
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "./ha-paper-slider";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
class HaForm extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
paper-checkbox {
|
||||
display: inline-block;
|
||||
padding: 22px 0;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[_isArray(schema)]]" restamp="">
|
||||
<template is="dom-if" if="[[error.base]]">
|
||||
<div class="error">[[computeError(error.base, schema)]]</div>
|
||||
</template>
|
||||
|
||||
<template is="dom-repeat" items="[[schema]]">
|
||||
<ha-form
|
||||
data="[[_getValue(data, item)]]"
|
||||
schema="[[item]]"
|
||||
error="[[_getValue(error, item)]]"
|
||||
on-data-changed="_valueChanged"
|
||||
compute-error="[[computeError]]"
|
||||
compute-label="[[computeLabel]]"
|
||||
compute-suffix="[[computeSuffix]]"
|
||||
></ha-form>
|
||||
</template>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_isArray(schema)]]" restamp="">
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="error">[[computeError(error, schema)]]</div>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(schema.type, "string")]]'
|
||||
restamp=""
|
||||
>
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_includes(schema.name, "password")]]'
|
||||
restamp=""
|
||||
>
|
||||
<paper-input
|
||||
type="[[_passwordFieldType(unmaskedPassword)]]"
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message="Required"
|
||||
>
|
||||
<paper-icon-button
|
||||
toggles
|
||||
active="{{unmaskedPassword}}"
|
||||
slot="suffix"
|
||||
icon="[[_passwordFieldIcon(unmaskedPassword)]]"
|
||||
id="iconButton"
|
||||
title="Click to toggle between masked and clear password"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</paper-input>
|
||||
</template>
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[!_includes(schema.name, "password")]]'
|
||||
restamp=""
|
||||
>
|
||||
<paper-input
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message="Required"
|
||||
></paper-input>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(schema.type, "integer")]]'
|
||||
restamp=""
|
||||
>
|
||||
<template is="dom-if" if="[[_isRange(schema)]]" restamp="">
|
||||
<div>
|
||||
[[computeLabel(schema)]]
|
||||
<ha-paper-slider
|
||||
pin=""
|
||||
value="{{data}}"
|
||||
min="[[schema.valueMin]]"
|
||||
max="[[schema.valueMax]]"
|
||||
></ha-paper-slider>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_isRange(schema)]]" restamp="">
|
||||
<paper-input
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
type="number"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message="Required"
|
||||
></paper-input>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(schema.type, "float")]]' restamp="">
|
||||
<!-- TODO -->
|
||||
<paper-input
|
||||
label="[[computeLabel(schema)]]"
|
||||
value="{{data}}"
|
||||
required="[[schema.required]]"
|
||||
auto-validate="[[schema.required]]"
|
||||
error-message="Required"
|
||||
>
|
||||
<span suffix="" slot="suffix">[[computeSuffix(schema)]]</span>
|
||||
</paper-input>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(schema.type, "boolean")]]'
|
||||
restamp=""
|
||||
>
|
||||
<div>
|
||||
<paper-checkbox checked="{{data}}"
|
||||
>[[computeLabel(schema)]]</paper-checkbox
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(schema.type, "select")]]'
|
||||
restamp=""
|
||||
>
|
||||
<paper-dropdown-menu label="[[computeLabel(schema)]]">
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-name"
|
||||
selected="{{data}}"
|
||||
>
|
||||
<template is="dom-repeat" items="[[schema.options]]">
|
||||
<paper-item item-name$="[[_optionValue(item)]]"
|
||||
>[[_optionLabel(item)]]</paper-item
|
||||
>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</template>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
data: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
},
|
||||
schema: Object,
|
||||
error: Object,
|
||||
|
||||
// A function that computes the label to be displayed for a given
|
||||
// schema object.
|
||||
computeLabel: {
|
||||
type: Function,
|
||||
value: () => (schema) => schema && schema.name,
|
||||
},
|
||||
|
||||
// A function that computes the suffix to be displayed for a given
|
||||
// schema object.
|
||||
computeSuffix: {
|
||||
type: Function,
|
||||
value: () => (schema) =>
|
||||
schema &&
|
||||
schema.description &&
|
||||
schema.description.unit_of_measurement,
|
||||
},
|
||||
|
||||
// A function that computes an error message to be displayed for a
|
||||
// given error ID, and relevant schema object
|
||||
computeError: {
|
||||
type: Function,
|
||||
value: () => (error, schema) => error, // eslint-disable-line no-unused-vars
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
focus() {
|
||||
const input = this.shadowRoot.querySelector(
|
||||
"ha-form, paper-input, ha-paper-slider, paper-checkbox, paper-dropdown-menu"
|
||||
);
|
||||
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.focus();
|
||||
}
|
||||
|
||||
_isArray(val) {
|
||||
return Array.isArray(val);
|
||||
}
|
||||
|
||||
_isRange(schema) {
|
||||
return "valueMin" in schema && "valueMax" in schema;
|
||||
}
|
||||
|
||||
_equals(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
_includes(a, b) {
|
||||
return a.indexOf(b) >= 0;
|
||||
}
|
||||
|
||||
_getValue(obj, item) {
|
||||
if (obj) {
|
||||
return obj[item.name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_valueChanged(ev) {
|
||||
let value = ev.detail.value;
|
||||
if (ev.model.item.type === "integer") {
|
||||
value = Number(ev.detail.value);
|
||||
}
|
||||
this.set(["data", ev.model.item.name], value);
|
||||
}
|
||||
|
||||
_passwordFieldType(unmaskedPassword) {
|
||||
return unmaskedPassword ? "text" : "password";
|
||||
}
|
||||
|
||||
_passwordFieldIcon(unmaskedPassword) {
|
||||
return unmaskedPassword ? "hass:eye-off" : "hass:eye";
|
||||
}
|
||||
|
||||
_optionValue(item) {
|
||||
return Array.isArray(item) ? item[0] : item;
|
||||
}
|
||||
|
||||
_optionLabel(item) {
|
||||
return Array.isArray(item) ? item[1] : item;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-form", HaForm);
|
70
src/components/ha-form/ha-form-boolean.ts
Normal file
70
src/components/ha-form/ha-form-boolean.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
html,
|
||||
property,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import {
|
||||
HaFormElement,
|
||||
HaFormBooleanData,
|
||||
HaFormBooleanSchema,
|
||||
} from "./ha-form";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
// Not duplicate, is for typing
|
||||
// tslint:disable-next-line
|
||||
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||
|
||||
@customElement("ha-form-boolean")
|
||||
export class HaFormBoolean extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormBooleanSchema;
|
||||
@property() public data!: HaFormBooleanData;
|
||||
@property() public label!: string;
|
||||
@property() public suffix!: string;
|
||||
@query("paper-checkbox") private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-checkbox .checked=${this.data} @change=${this._valueChanged}>
|
||||
${this.label}
|
||||
</paper-checkbox>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{
|
||||
value: (ev.target as PaperCheckboxElement).checked,
|
||||
},
|
||||
{ bubbles: false }
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-checkbox {
|
||||
display: inline-block;
|
||||
padding: 22px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-boolean": HaFormBoolean;
|
||||
}
|
||||
}
|
65
src/components/ha-form/ha-form-float.ts
Normal file
65
src/components/ha-form/ha-form-float.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
html,
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./ha-form";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
// Not duplicate, is for typing
|
||||
// tslint:disable-next-line
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
|
||||
@customElement("ha-form-float")
|
||||
export class HaFormFloat extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormFloatSchema;
|
||||
@property() public data!: HaFormFloatData;
|
||||
@property() public label!: string;
|
||||
@property() public suffix!: string;
|
||||
@query("paper-input") private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-input
|
||||
.label=${this.label}
|
||||
.value=${this.data}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<span suffix="" slot="suffix">${this.suffix}</span>
|
||||
</paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const value = Number((ev.target as PaperInputElement).value);
|
||||
if (this.data === value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{
|
||||
value,
|
||||
},
|
||||
{ bubbles: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-float": HaFormFloat;
|
||||
}
|
||||
}
|
85
src/components/ha-form/ha-form-integer.ts
Normal file
85
src/components/ha-form/ha-form-integer.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
html,
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import {
|
||||
HaFormElement,
|
||||
HaFormIntegerData,
|
||||
HaFormIntegerSchema,
|
||||
} from "./ha-form";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import "../ha-paper-slider";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
// Not duplicate, is for typing
|
||||
// tslint:disable-next-line
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { PaperSliderElement } from "@polymer/paper-slider/paper-slider";
|
||||
|
||||
@customElement("ha-form-integer")
|
||||
export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormIntegerSchema;
|
||||
@property() public data!: HaFormIntegerData;
|
||||
@property() public label!: string;
|
||||
@property() public suffix!: string;
|
||||
@query("paper-input ha-paper-slider") private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return "valueMin" in this.schema && "valueMax" in this.schema
|
||||
? html`
|
||||
<div>
|
||||
${this.label}
|
||||
<ha-paper-slider
|
||||
pin=""
|
||||
.value=${this.data}
|
||||
.min=${this.schema.valueMin}
|
||||
.max=${this.schema.valueMax}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-paper-slider>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<paper-input
|
||||
type="number"
|
||||
.label=${this.label}
|
||||
.value=${this.data}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const value = Number(
|
||||
(ev.target as PaperInputElement | PaperSliderElement).value
|
||||
);
|
||||
if (this.data === value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{
|
||||
value,
|
||||
},
|
||||
{ bubbles: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-integer": HaFormInteger;
|
||||
}
|
||||
}
|
78
src/components/ha-form/ha-form-select.ts
Normal file
78
src/components/ha-form/ha-form-select.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
html,
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
|
||||
@customElement("ha-form-select")
|
||||
export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormSelectSchema;
|
||||
@property() public data!: HaFormSelectData;
|
||||
@property() public label!: string;
|
||||
@property() public suffix!: string;
|
||||
@query("paper-dropdown-menu") private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-dropdown-menu .label=${this.label}>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-value"
|
||||
.selected=${this.data}
|
||||
@selected-item-changed=${this._valueChanged}
|
||||
>
|
||||
${this.schema.options!.map(
|
||||
(item) => html`
|
||||
<paper-item .itemValue=${this._optionValue(item)}>
|
||||
${this._optionLabel(item)}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
private _optionValue(item) {
|
||||
return Array.isArray(item) ? item[0] : item;
|
||||
}
|
||||
|
||||
private _optionLabel(item) {
|
||||
return Array.isArray(item) ? item[1] : item;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{
|
||||
value: ev.detail.value.itemValue,
|
||||
},
|
||||
{ bubbles: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-select": HaFormSelect;
|
||||
}
|
||||
}
|
93
src/components/ha-form/ha-form-string.ts
Normal file
93
src/components/ha-form/ha-form-string.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
html,
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
|
||||
import { HaFormElement, HaFormStringData, HaFormStringSchema } from "./ha-form";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
// Not duplicate, is for typing
|
||||
// tslint:disable-next-line
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
|
||||
@customElement("ha-form-string")
|
||||
export class HaFormString extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormStringSchema;
|
||||
@property() public data!: HaFormStringData;
|
||||
@property() public label!: string;
|
||||
@property() public suffix!: string;
|
||||
@property() private _unmaskedPassword = false;
|
||||
@query("paper-input") private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return this.schema.name.includes("password")
|
||||
? html`
|
||||
<paper-input
|
||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
||||
.label=${this.label}
|
||||
.value=${this.data}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-icon-button
|
||||
toggles
|
||||
.active=${this._unmaskedPassword}
|
||||
slot="suffix"
|
||||
.icon=${this._unmaskedPassword ? "hass:eye-off" : "hass:eye"}
|
||||
id="iconButton"
|
||||
title="Click to toggle between masked and clear password"
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
>
|
||||
</paper-icon-button>
|
||||
</paper-input>
|
||||
`
|
||||
: html`
|
||||
<paper-input
|
||||
.label=${this.label}
|
||||
.value=${this.data}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
error-message="Required"
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleUnmaskedPassword(ev: Event) {
|
||||
this._unmaskedPassword = (ev.target as any).active;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const value = (ev.target as PaperInputElement).value;
|
||||
if (this.data === value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{
|
||||
value,
|
||||
},
|
||||
{ bubbles: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-string": HaFormString;
|
||||
}
|
||||
}
|
225
src/components/ha-form/ha-form.ts
Normal file
225
src/components/ha-form/ha-form.ts
Normal file
@ -0,0 +1,225 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
html,
|
||||
property,
|
||||
query,
|
||||
CSSResult,
|
||||
css,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
|
||||
import "./ha-form-string";
|
||||
import "./ha-form-integer";
|
||||
import "./ha-form-float";
|
||||
import "./ha-form-boolean";
|
||||
import "./ha-form-select";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export type HaFormSchema =
|
||||
| HaFormStringSchema
|
||||
| HaFormIntegerSchema
|
||||
| HaFormFloatSchema
|
||||
| HaFormBooleanSchema
|
||||
| HaFormSelectSchema;
|
||||
|
||||
export interface HaFormBaseSchema {
|
||||
name: string;
|
||||
default?: HaFormData;
|
||||
required?: boolean;
|
||||
optional?: boolean;
|
||||
description?: { suffix?: string };
|
||||
}
|
||||
|
||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||
type: "integer";
|
||||
default?: HaFormIntegerData;
|
||||
valueMin?: number;
|
||||
valueMax?: number;
|
||||
}
|
||||
|
||||
export interface HaFormSelectSchema extends HaFormBaseSchema {
|
||||
type: "select";
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
||||
type: "float";
|
||||
}
|
||||
|
||||
export interface HaFormStringSchema extends HaFormBaseSchema {
|
||||
type: "string";
|
||||
}
|
||||
|
||||
export interface HaFormBooleanSchema extends HaFormBaseSchema {
|
||||
type: "boolean";
|
||||
}
|
||||
|
||||
export interface HaFormDataContainer {
|
||||
[key: string]: HaFormData;
|
||||
}
|
||||
|
||||
export type HaFormData =
|
||||
| HaFormStringData
|
||||
| HaFormIntegerData
|
||||
| HaFormFloatData
|
||||
| HaFormBooleanData
|
||||
| HaFormSelectData;
|
||||
|
||||
export type HaFormStringData = string;
|
||||
export type HaFormIntegerData = number;
|
||||
export type HaFormFloatData = number;
|
||||
export type HaFormBooleanData = boolean;
|
||||
export type HaFormSelectData = string;
|
||||
|
||||
export interface HaFormElement extends LitElement {
|
||||
schema: HaFormSchema;
|
||||
data: HaFormDataContainer | HaFormData;
|
||||
label?: string;
|
||||
suffix?: string;
|
||||
}
|
||||
|
||||
@customElement("ha-form")
|
||||
export class HaForm extends LitElement implements HaFormElement {
|
||||
@property() public data!: HaFormDataContainer | HaFormData;
|
||||
@property() public schema!: HaFormSchema;
|
||||
@property() public error;
|
||||
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
||||
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
||||
@property() public computeSuffix?: (schema: HaFormSchema) => string;
|
||||
@query("ha-form") private _childForm?: HaForm;
|
||||
@query("#element") private _elementContainer?: HTMLDivElement;
|
||||
|
||||
public focus() {
|
||||
const input = this._childForm
|
||||
? this._childForm
|
||||
: this._elementContainer
|
||||
? this._elementContainer.lastChild
|
||||
: undefined;
|
||||
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
(input as HTMLElement).focus();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (Array.isArray(this.schema)) {
|
||||
return html`
|
||||
${this.error && this.error.base
|
||||
? html`
|
||||
<div class="error">
|
||||
${this._computeError(this.error.base, this.schema)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this.schema.map(
|
||||
(item) => html`
|
||||
<ha-form
|
||||
.data=${this._getValue(this.data, item)}
|
||||
.schema=${item}
|
||||
.error=${this._getValue(this.error, item)}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeError=${this.computeError}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.computeSuffix=${this.computeSuffix}
|
||||
></ha-form>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.error
|
||||
? html`
|
||||
<div class="error">
|
||||
${this._computeError(this.error, this.schema)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div id="element" @value-changed=${this._valueChanged}></div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
const schemaChanged = changedProperties.has("schema");
|
||||
const oldSchema = schemaChanged
|
||||
? changedProperties.get("schema")
|
||||
: undefined;
|
||||
if (
|
||||
!Array.isArray(this.schema) &&
|
||||
schemaChanged &&
|
||||
(!oldSchema || (oldSchema as HaFormSchema).type !== this.schema.type)
|
||||
) {
|
||||
const element = document.createElement(
|
||||
`ha-form-${this.schema.type}`
|
||||
) as HaFormElement;
|
||||
element.schema = this.schema;
|
||||
element.data = this.data;
|
||||
element.label = this._computeLabel(this.schema);
|
||||
element.suffix = this._computeSuffix(this.schema);
|
||||
if (this._elementContainer!.lastChild) {
|
||||
this._elementContainer!.removeChild(this._elementContainer!.lastChild);
|
||||
}
|
||||
this._elementContainer!.append(element);
|
||||
} else if (this._elementContainer && this._elementContainer.lastChild) {
|
||||
const element = this._elementContainer!.lastChild as HaFormElement;
|
||||
element.schema = this.schema;
|
||||
element.data = this.data;
|
||||
element.label = this._computeLabel(this.schema);
|
||||
element.suffix = this._computeSuffix(this.schema);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeLabel(schema: HaFormSchema) {
|
||||
return this.computeLabel
|
||||
? this.computeLabel(schema)
|
||||
: schema
|
||||
? schema.name
|
||||
: "";
|
||||
}
|
||||
|
||||
private _computeSuffix(schema: HaFormSchema) {
|
||||
return this.computeSuffix
|
||||
? this.computeSuffix(schema)
|
||||
: schema && schema.description
|
||||
? schema.description.suffix
|
||||
: "";
|
||||
}
|
||||
|
||||
private _computeError(error, schema: HaFormSchema) {
|
||||
return this.computeError ? this.computeError(error, schema) : error;
|
||||
}
|
||||
|
||||
private _getValue(obj, item) {
|
||||
if (obj) {
|
||||
return obj[item.name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const schema = (ev.target as HaFormElement).schema;
|
||||
const data = this.data as HaFormDataContainer;
|
||||
data[schema.name] = ev.detail.value;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...data },
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form": HaForm;
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
import "../../components/ha-form";
|
||||
import "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
import "../../resources/ha-style";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
|
@ -12,10 +12,9 @@ import "@material/mwc-button";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
|
||||
import "../../components/ha-form";
|
||||
import "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
import "../../resources/ha-style";
|
||||
import { PolymerChangedEvent, applyPolymerEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
@ -69,7 +68,7 @@ class StepFlowForm extends LitElement {
|
||||
${this.flowConfig.renderShowFormStepDescription(this.hass, this.step)}
|
||||
<ha-form
|
||||
.data=${stepData}
|
||||
@data-changed=${this._stepDataChanged}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
.schema=${step.data_schema}
|
||||
.error=${step.errors}
|
||||
.computeLabel=${this._labelCallback}
|
||||
@ -169,8 +168,8 @@ class StepFlowForm extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _stepDataChanged(ev: PolymerChangedEvent<any>): void {
|
||||
this._stepData = applyPolymerEvent(ev, this._stepData);
|
||||
private _stepDataChanged(ev: CustomEvent): void {
|
||||
this._stepData = ev.detail.value;
|
||||
}
|
||||
|
||||
private _labelCallback = (field: FieldSchema): string =>
|
||||
|
@ -2,7 +2,7 @@ import { h, Component } from "preact";
|
||||
|
||||
import "../../../../components/device/ha-device-picker";
|
||||
import "../../../../components/device/ha-device-condition-picker";
|
||||
import "../../../../components/ha-form";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
|
||||
import {
|
||||
fetchDeviceConditionCapabilities,
|
||||
@ -64,9 +64,9 @@ export default class DeviceCondition extends Component<any, any> {
|
||||
{extraFieldsData && (
|
||||
<ha-form
|
||||
data={Object.assign({}, ...extraFieldsData)}
|
||||
onData-changed={this._extraFieldsChanged}
|
||||
schema={this.state.capabilities.extra_fields}
|
||||
computeLabel={this._extraFieldsComputeLabelCallback(hass.localize)}
|
||||
onvalue-changed={this._extraFieldsChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -98,15 +98,9 @@ export default class DeviceCondition extends Component<any, any> {
|
||||
}
|
||||
|
||||
private _extraFieldsChanged(ev) {
|
||||
if (!ev.detail.path) {
|
||||
return;
|
||||
}
|
||||
const item = ev.detail.path.replace("data.", "");
|
||||
const value = ev.detail.value || undefined;
|
||||
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
[item]: value,
|
||||
...ev.detail.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { h, Component } from "preact";
|
||||
|
||||
import "../../../../components/device/ha-device-picker";
|
||||
import "../../../../components/device/ha-device-action-picker";
|
||||
import "../../../../components/ha-form";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
|
||||
import {
|
||||
fetchDeviceActionCapabilities,
|
||||
@ -117,15 +117,9 @@ export default class DeviceActionEditor extends Component<
|
||||
}
|
||||
|
||||
private _extraFieldsChanged(ev) {
|
||||
if (!ev.detail.path) {
|
||||
return;
|
||||
}
|
||||
const item = ev.detail.path.replace("data.", "");
|
||||
const value = ev.detail.value || undefined;
|
||||
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.action,
|
||||
[item]: value,
|
||||
...ev.detail.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { h, Component } from "preact";
|
||||
|
||||
import "../../../../components/device/ha-device-picker";
|
||||
import "../../../../components/device/ha-device-trigger-picker";
|
||||
import "../../../../components/ha-form";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
|
||||
import {
|
||||
fetchDeviceTriggerCapabilities,
|
||||
@ -65,9 +65,9 @@ export default class DeviceTrigger extends Component<any, any> {
|
||||
{extraFieldsData && (
|
||||
<ha-form
|
||||
data={Object.assign({}, ...extraFieldsData)}
|
||||
onData-changed={this._extraFieldsChanged}
|
||||
schema={this.state.capabilities.extra_fields}
|
||||
computeLabel={this._extraFieldsComputeLabelCallback(hass.localize)}
|
||||
onvalue-changed={this._extraFieldsChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -99,15 +99,9 @@ export default class DeviceTrigger extends Component<any, any> {
|
||||
}
|
||||
|
||||
private _extraFieldsChanged(ev) {
|
||||
if (!ev.detail.path) {
|
||||
return;
|
||||
}
|
||||
const item = ev.detail.path.replace("data.", "");
|
||||
const value = ev.detail.value || undefined;
|
||||
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.trigger,
|
||||
[item]: value,
|
||||
...ev.detail.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import "../../components/ha-form";
|
||||
import "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
import "../../resources/ha-style";
|
||||
|
||||
|
@ -33,7 +33,9 @@ documentContainer.innerHTML = `<custom-style>
|
||||
|
||||
--scrollbar-thumb-color: rgb(194, 194, 194);
|
||||
|
||||
--error-state-color: #db4437;
|
||||
|
||||
--error-color: #db4437;
|
||||
--error-state-color: var(--error-color);
|
||||
|
||||
/* states and badges */
|
||||
--state-icon-color: #44739e;
|
||||
|
Loading…
x
Reference in New Issue
Block a user