${this.error && this.error.base
@@ -101,6 +110,9 @@ export class HaForm extends LitElement implements HaFormElement {
data: getValue(this.data, item),
label: this._computeLabel(item, this.data),
disabled: this.disabled,
+ hass: this.hass,
+ computeLabel: this.computeLabel,
+ computeHelper: this.computeHelper,
})}
`;
})}
@@ -115,8 +127,12 @@ export class HaForm extends LitElement implements HaFormElement {
ev.stopPropagation();
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
+ const newValue = !schema.name
+ ? ev.detail.value
+ : { [schema.name]: ev.detail.value };
+
fireEvent(this, "value-changed", {
- value: { ...this.data, [schema.name]: ev.detail.value },
+ value: { ...this.data, ...newValue },
});
});
return root;
diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts
index f476e759ca..5d9572147a 100644
--- a/src/components/ha-form/types.ts
+++ b/src/components/ha-form/types.ts
@@ -11,7 +11,8 @@ export type HaFormSchema =
| HaFormSelectSchema
| HaFormMultiSelectSchema
| HaFormTimeSchema
- | HaFormSelector;
+ | HaFormSelector
+ | HaFormGridSchema;
export interface HaFormBaseSchema {
name: string;
@@ -25,6 +26,12 @@ export interface HaFormBaseSchema {
};
}
+export interface HaFormGridSchema extends HaFormBaseSchema {
+ type: "grid";
+ name: "";
+ schema: HaFormSchema[];
+}
+
export interface HaFormSelector extends HaFormBaseSchema {
type?: never;
selector: Selector;
diff --git a/src/components/ha-selector/ha-selector-boolean.ts b/src/components/ha-selector/ha-selector-boolean.ts
index f140ba99e4..4ac5918b4c 100644
--- a/src/components/ha-selector/ha-selector-boolean.ts
+++ b/src/components/ha-selector/ha-selector-boolean.ts
@@ -35,9 +35,12 @@ export class HaBooleanSelector extends LitElement {
static get styles(): CSSResultGroup {
return css`
+ :host {
+ height: 56px;
+ display: flex;
+ }
ha-formfield {
width: 100%;
- margin: 16px 0;
--mdc-typography-body2-font-size: 1em;
}
`;
diff --git a/src/components/ha-selector/ha-selector-icon.ts b/src/components/ha-selector/ha-selector-icon.ts
index 046a612d5e..0e4a712588 100644
--- a/src/components/ha-selector/ha-selector-icon.ts
+++ b/src/components/ha-selector/ha-selector-icon.ts
@@ -22,6 +22,8 @@ export class HaIconSelector extends LitElement {
`;
diff --git a/src/components/ha-selector/ha-selector-theme.ts b/src/components/ha-selector/ha-selector-theme.ts
new file mode 100644
index 0000000000..d25539908f
--- /dev/null
+++ b/src/components/ha-selector/ha-selector-theme.ts
@@ -0,0 +1,34 @@
+import "../../panels/lovelace/components/hui-theme-select-editor";
+import { html, LitElement } from "lit";
+import { customElement, property } from "lit/decorators";
+import type { HomeAssistant } from "../../types";
+import type { ThemeSelector } from "../../data/selector";
+
+@customElement("ha-selector-theme")
+export class HaThemeSelector extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public selector!: ThemeSelector;
+
+ @property() public value?: string;
+
+ @property() public label?: string;
+
+ @property({ type: Boolean, reflect: true }) public disabled = false;
+
+ protected render() {
+ return html`
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-selector-theme": HaThemeSelector;
+ }
+}
diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts
index 0181c9c863..5f6a31aebc 100644
--- a/src/components/ha-selector/ha-selector.ts
+++ b/src/components/ha-selector/ha-selector.ts
@@ -1,8 +1,8 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
-import { Selector } from "../../data/selector";
-import { HomeAssistant } from "../../types";
+import type { Selector } from "../../data/selector";
+import type { HomeAssistant } from "../../types";
import "./ha-selector-action";
import "./ha-selector-addon";
import "./ha-selector-area";
@@ -19,6 +19,7 @@ import "./ha-selector-text";
import "./ha-selector-time";
import "./ha-selector-icon";
import "./ha-selector-media";
+import "./ha-selector-theme";
@customElement("ha-selector")
export class HaSelector extends LitElement {
diff --git a/src/data/selector.ts b/src/data/selector.ts
index e7be0ac147..b51c077b01 100644
--- a/src/data/selector.ts
+++ b/src/data/selector.ts
@@ -14,7 +14,8 @@ export type Selector =
| ObjectSelector
| SelectSelector
| IconSelector
- | MediaSelector;
+ | MediaSelector
+ | ThemeSelector;
export interface EntitySelector {
entity: {
@@ -147,8 +148,15 @@ export interface SelectSelector {
}
export interface IconSelector {
+ icon: {
+ placeholder?: string;
+ fallbackPath?: string;
+ };
+}
+
+export interface ThemeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
- icon: {};
+ theme: {};
}
export interface MediaSelector {
diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts
index 47e05ae1bf..30bc0bdef0 100644
--- a/src/panels/lovelace/components/hui-theme-select-editor.ts
+++ b/src/panels/lovelace/components/hui-theme-select-editor.ts
@@ -39,7 +39,7 @@ export class HuiThemeSelectEditor extends LitElement {
.sort()
.map(
(theme) =>
- html`
${theme} `
+ html`
${theme}`
)}
`;
@@ -57,7 +57,7 @@ export class HuiThemeSelectEditor extends LitElement {
if (!this.hass || ev.target.value === "") {
return;
}
- this.value = ev.target.value === "remove" ? "" : ev.target.selected;
+ this.value = ev.target.value === "remove" ? "" : ev.target.value;
fireEvent(this, "value-changed", { value: this.value });
}
}
diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts
index df9cb0e627..7dcf65a1bc 100644
--- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts
@@ -1,22 +1,18 @@
-import "@polymer/paper-input/paper-input";
-import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
+import "../../../../components/ha-form/ha-form";
+import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
+import memoizeOne from "memoize-one";
import { assert, assign, boolean, object, optional, string } from "superstruct";
+import type { HassEntity } from "home-assistant-js-websocket/dist/types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon";
-import "../../../../components/entity/ha-entity-attribute-picker";
-import "../../../../components/ha-icon-picker";
-import { HomeAssistant } from "../../../../types";
-import { EntityCardConfig } from "../../cards/types";
-import "../../components/hui-action-editor";
-import "../../components/hui-entity-editor";
-import "../../components/hui-theme-select-editor";
+import type { HaFormSchema } from "../../../../components/ha-form/types";
+import type { HomeAssistant } from "../../../../types";
+import type { EntityCardConfig } from "../../cards/types";
import { headerFooterConfigStructs } from "../../header-footer/structs";
-import { LovelaceCardEditor } from "../../types";
+import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
-import { EditorTarget, EntitiesEditorEvent } from "../types";
-import { configElementStyle } from "./config-elements-style";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -46,174 +42,82 @@ export class HuiEntityCardEditor
this._config = config;
}
- get _entity(): string {
- return this._config!.entity || "";
- }
+ private _schema = memoizeOne(
+ (entity: string, icon: string, entityState: HassEntity): HaFormSchema[] => [
+ { name: "entity", required: true, selector: { entity: {} } },
+ {
+ type: "grid",
+ name: "",
+ schema: [
+ { name: "name", selector: { text: {} } },
+ {
+ name: "icon",
+ selector: {
+ icon: {
+ placeholder: icon || entityState?.attributes.icon,
+ fallbackPath:
+ !icon && !entityState?.attributes.icon && entityState
+ ? domainIcon(computeDomain(entity), entityState)
+ : undefined,
+ },
+ },
+ },
- get _name(): string {
- return this._config!.name || "";
- }
-
- get _icon(): string {
- return this._config!.icon || "";
- }
-
- get _attribute(): string {
- return this._config!.attribute || "";
- }
-
- get _unit(): string {
- return this._config!.unit || "";
- }
-
- get _state_color(): boolean {
- return this._config!.state_color ?? false;
- }
-
- get _theme(): string {
- return this._config!.theme || "";
- }
+ {
+ name: "attribute",
+ selector: { attribute: { entity_id: entity } },
+ },
+ { name: "unit", selector: { text: {} } },
+ { name: "theme", selector: { theme: {} } },
+ { name: "state_color", selector: { boolean: {} } },
+ ],
+ },
+ ]
+ );
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
- const entityState = this.hass.states[this._entity];
+
+ const entityState = this.hass.states[this._config.entity];
+
+ const schema = this._schema(
+ this._config.entity,
+ this._config.icon,
+ entityState
+ );
return html`
-
+
`;
}
- private _valueChanged(ev: EntitiesEditorEvent): void {
- if (!this._config || !this.hass) {
- return;
- }
- const target = ev.currentTarget! as EditorTarget;
-
- if (
- this[`_${target.configValue}`] === target.value ||
- this[`_${target.configValue}`] === target.config
- ) {
- return;
- }
- if (target.configValue) {
- if (target.value === "") {
- this._config = { ...this._config };
- delete this._config[target.configValue!];
- } else {
- let newValue: string | undefined;
- if (
- target.configValue === "icon_height" &&
- !isNaN(Number(target.value))
- ) {
- newValue = `${String(target.value)}px`;
- }
- this._config = {
- ...this._config,
- [target.configValue!]:
- target.checked !== undefined
- ? target.checked
- : newValue !== undefined
- ? newValue
- : target.value
- ? target.value
- : target.config,
- };
- }
- }
- fireEvent(this, "config-changed", { config: this._config });
+ private _valueChanged(ev: CustomEvent): void {
+ const config = ev.detail.value;
+ Object.keys(config).forEach((k) => config[k] === "" && delete config[k]);
+ fireEvent(this, "config-changed", { config });
}
- static get styles(): CSSResultGroup {
- return configElementStyle;
- }
+ private _computeLabelCallback = (schema: HaFormSchema) => {
+ if (schema.name === "entity") {
+ return `${this.hass!.localize(
+ "ui.panel.lovelace.editor.card.generic.entity"
+ )} (${this.hass!.localize(
+ "ui.panel.lovelace.editor.card.config.required"
+ )})`;
+ }
+
+ return this.hass!.localize(
+ `ui.panel.lovelace.editor.card.generic.${schema.name}`
+ );
+ };
}
declare global {