Files
frontend/src/components/ha-form/ha-form.ts
Zack Barett cefa2ee183 State Trigger -> HA Form (#11631)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-02-10 09:26:28 -06:00

153 lines
4.3 KiB
TypeScript

import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../common/dom/fire_event";
import "../ha-alert";
import "./ha-form-boolean";
import "./ha-form-constant";
import "./ha-form-float";
import "./ha-form-integer";
import "./ha-form-multi_select";
import "./ha-form-positive_time_period_dict";
import "./ha-form-select";
import "./ha-form-string";
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
import { HomeAssistant } from "../../types";
const getValue = (obj, item) => (obj ? obj[item.name] : null);
let selectorImported = false;
@customElement("ha-form")
export class HaForm extends LitElement implements HaFormElement {
@property() public hass!: HomeAssistant;
@property() public data!: HaFormDataContainer;
@property() public schema!: HaFormSchema[];
@property() public error?: Record<string, string>;
@property({ type: Boolean }) public disabled = false;
@property() public computeError?: (schema: HaFormSchema, error) => string;
@property() public computeLabel?: (schema: HaFormSchema) => string;
public focus() {
const root = this.shadowRoot?.querySelector(".root");
if (!root) {
return;
}
for (const child of root.children) {
if (child.tagName !== "HA-ALERT") {
(child as HTMLElement).focus();
break;
}
}
}
willUpdate(changedProperties: PropertyValues) {
super.willUpdate(changedProperties);
if (
!selectorImported &&
changedProperties.has("schema") &&
this.schema?.some((item) => "selector" in item)
) {
selectorImported = true;
import("../ha-selector/ha-selector");
}
}
protected render() {
return html`
<div class="root">
${this.error && this.error.base
? html`
<ha-alert alert-type="error">
${this._computeError(this.error.base, this.schema)}
</ha-alert>
`
: ""}
${this.schema.map((item) => {
const error = getValue(this.error, item);
return html`
${error
? html`
<ha-alert own-margin alert-type="error">
${this._computeError(error, item)}
</ha-alert>
`
: ""}
${"selector" in item
? html`<ha-selector
.hass=${this.hass}
.selector=${item.selector}
.value=${getValue(this.data, item)}
.label=${this._computeLabel(item)}
.disabled=${this.disabled}
.required=${item.required}
></ha-selector>`
: dynamicElement(`ha-form-${item.type}`, {
schema: item,
data: getValue(this.data, item),
label: this._computeLabel(item),
disabled: this.disabled,
})}
`;
})}
</div>
`;
}
protected createRenderRoot() {
const root = super.createRenderRoot();
// attach it as soon as possible to make sure we fetch all events.
root.addEventListener("value-changed", (ev) => {
ev.stopPropagation();
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
fireEvent(this, "value-changed", {
value: { ...this.data, [schema.name]: ev.detail.value },
});
});
return root;
}
private _computeLabel(schema: HaFormSchema) {
return this.computeLabel
? this.computeLabel(schema)
: schema
? schema.name
: "";
}
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
return this.computeError ? this.computeError(error, schema) : error;
}
static get styles(): CSSResultGroup {
// .root has overflow: auto to avoid margin collapse
return css`
.root {
margin-bottom: -24px;
overflow: clip visible;
}
.root > * {
display: block;
}
.root > *:not([own-margin]) {
margin-bottom: 24px;
}
ha-alert[own-margin] {
margin-bottom: 4px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-form": HaForm;
}
}