diff --git a/demo/src/custom-cards/card-tools.js b/demo/src/custom-cards/card-tools.js
index 27b37cabeb..86f7c597b2 100644
--- a/demo/src/custom-cards/card-tools.js
+++ b/demo/src/custom-cards/card-tools.js
@@ -138,7 +138,7 @@ if (!window.cardTools) {
return cardTools.createThing("row", config);
const domain = config.entity.split(".", 1)[0];
- Object.assign(config, { type: DEFAULT_ROWS[domain] || "text" });
+ Object.assign(config, { type: DEFAULT_ROWS[domain] || "simple" });
return cardTools.createThing("entity-row", config);
};
diff --git a/gallery/src/pages/lovelace/entities-card.ts b/gallery/src/pages/lovelace/entities-card.ts
index 6cec33cba5..9aebd67b7d 100644
--- a/gallery/src/pages/lovelace/entities-card.ts
+++ b/gallery/src/pages/lovelace/entities-card.ts
@@ -98,6 +98,9 @@ const ENTITIES = [
minimum: 0,
maximum: 10,
}),
+ getEntity("text", "message", "Hello!", {
+ friendly_name: "Message",
+ }),
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Bed Light",
@@ -129,6 +132,9 @@ const ENTITIES = [
friendly_name: "Who cooks",
icon: "mdi:cheff",
}),
+ getEntity("text", "unavailable", "unavailable", {
+ friendly_name: "Message",
+ }),
];
const CONFIGS = [
@@ -147,6 +153,7 @@ const CONFIGS = [
- climate.ecobee
- input_number.number
- sensor.humidity
+ - text.message
`,
},
{
@@ -219,6 +226,7 @@ const CONFIGS = [
- climate.unavailable
- input_number.unavailable
- input_select.unavailable
+ - text.unavailable
`,
},
{
diff --git a/src/common/const.ts b/src/common/const.ts
index 359eb677da..53ac073b8e 100644
--- a/src/common/const.ts
+++ b/src/common/const.ts
@@ -114,6 +114,7 @@ export const FIXED_DOMAIN_ICONS = {
siren: mdiBullhorn,
simple_alarm: mdiBell,
sun: mdiWhiteBalanceSunny,
+ text: mdiFormTextbox,
timer: mdiTimerOutline,
updater: mdiCloudUpload,
vacuum: mdiRobotVacuum,
@@ -182,6 +183,7 @@ export const DOMAINS_WITH_CARD = [
"script",
"select",
"timer",
+ "text",
"vacuum",
"water_heater",
];
@@ -214,6 +216,7 @@ export const DOMAINS_INPUT_ROW = [
"script",
"select",
"switch",
+ "text",
"vacuum",
];
diff --git a/src/data/text.ts b/src/data/text.ts
new file mode 100644
index 0000000000..45b501f457
--- /dev/null
+++ b/src/data/text.ts
@@ -0,0 +1,19 @@
+import {
+ HassEntityAttributeBase,
+ HassEntityBase,
+} from "home-assistant-js-websocket";
+import { HomeAssistant } from "../types";
+
+interface TextEntityAttributes extends HassEntityAttributeBase {
+ min?: number;
+ max?: number;
+ pattern?: string;
+ mode?: "text" | "password";
+}
+
+export interface TextEntity extends HassEntityBase {
+ attributes: TextEntityAttributes;
+}
+
+export const setValue = (hass: HomeAssistant, entity: string, value: string) =>
+ hass.callService("text", "set_value", { value }, { entity_id: entity });
diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts
index 15ba2da551..30ac679e33 100644
--- a/src/dialogs/more-info/const.ts
+++ b/src/dialogs/more-info/const.ts
@@ -50,8 +50,9 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_text",
"number",
"scene",
- "update",
"select",
+ "text",
+ "update",
];
/** Domains that should have the history hidden in the more info dialog. */
diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts
index b805707259..71f8a2359e 100644
--- a/src/panels/lovelace/create-element/create-row-element.ts
+++ b/src/panels/lovelace/create-element/create-row-element.ts
@@ -2,7 +2,7 @@ import "../entity-rows/hui-media-player-entity-row";
import "../entity-rows/hui-scene-entity-row";
import "../entity-rows/hui-script-entity-row";
import "../entity-rows/hui-sensor-entity-row";
-import "../entity-rows/hui-text-entity-row";
+import "../entity-rows/hui-simple-entity-row";
import "../entity-rows/hui-toggle-entity-row";
import { LovelaceRowConfig } from "../entity-rows/types";
import "../special-rows/hui-attribute-row";
@@ -18,7 +18,7 @@ const ALWAYS_LOADED_TYPES = new Set([
"scene-entity",
"script-entity",
"sensor-entity",
- "text-entity",
+ "simple-entity",
"toggle-entity",
"button",
"call-service",
@@ -41,6 +41,7 @@ const LAZY_LOAD_TYPES = {
"lock-entity": () => import("../entity-rows/hui-lock-entity-row"),
"number-entity": () => import("../entity-rows/hui-number-entity-row"),
"select-entity": () => import("../entity-rows/hui-select-entity-row"),
+ "text-entity": () => import("../entity-rows/hui-text-entity-row"),
"timer-entity": () => import("../entity-rows/hui-timer-entity-row"),
conditional: () => import("../special-rows/hui-conditional-row"),
"weather-entity": () => import("../entity-rows/hui-weather-entity-row"),
@@ -53,7 +54,7 @@ const LAZY_LOAD_TYPES = {
text: () => import("../special-rows/hui-text-row"),
};
const DOMAIN_TO_ELEMENT_TYPE = {
- _domain_not_found: "text",
+ _domain_not_found: "simple",
alert: "toggle",
automation: "toggle",
button: "button",
@@ -78,6 +79,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
sensor: "sensor",
siren: "toggle",
switch: "toggle",
+ text: "text",
timer: "timer",
vacuum: "toggle",
// Temporary. Once climate is rewritten,
diff --git a/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts b/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts
new file mode 100644
index 0000000000..e0dde82dd5
--- /dev/null
+++ b/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts
@@ -0,0 +1,73 @@
+import {
+ css,
+ CSSResultGroup,
+ html,
+ LitElement,
+ PropertyValues,
+ TemplateResult,
+} from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { computeStateDisplay } from "../../../common/entity/compute_state_display";
+import { HomeAssistant } from "../../../types";
+import { EntitiesCardEntityConfig } from "../cards/types";
+import { hasConfigOrEntityChanged } from "../common/has-changed";
+import "../components/hui-generic-entity-row";
+import { createEntityNotFoundWarning } from "../components/hui-warning";
+import { LovelaceRow } from "./types";
+
+@customElement("hui-simple-entity-row")
+class HuiSimpleEntityRow extends LitElement implements LovelaceRow {
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @state() private _config?: EntitiesCardEntityConfig;
+
+ public setConfig(config: EntitiesCardEntityConfig): void {
+ if (!config) {
+ throw new Error("Invalid configuration");
+ }
+ this._config = config;
+ }
+
+ protected shouldUpdate(changedProps: PropertyValues): boolean {
+ return hasConfigOrEntityChanged(this, changedProps);
+ }
+
+ protected render(): TemplateResult {
+ if (!this._config || !this.hass) {
+ return html``;
+ }
+
+ const stateObj = this.hass.states[this._config.entity];
+
+ if (!stateObj) {
+ return html`
+
+ ${createEntityNotFoundWarning(this.hass, this._config.entity)}
+
+ `;
+ }
+
+ return html`
+
+ ${computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale)}
+
+ `;
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ div {
+ text-align: right;
+ }
+ .pointer {
+ cursor: pointer;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-simple-entity-row": HuiSimpleEntityRow;
+ }
+}
diff --git a/src/panels/lovelace/entity-rows/hui-text-entity-row.ts b/src/panels/lovelace/entity-rows/hui-text-entity-row.ts
index 0c82d55e56..222a788ae3 100644
--- a/src/panels/lovelace/entity-rows/hui-text-entity-row.ts
+++ b/src/panels/lovelace/entity-rows/hui-text-entity-row.ts
@@ -1,27 +1,22 @@
-import {
- css,
- CSSResultGroup,
- html,
- LitElement,
- PropertyValues,
- TemplateResult,
-} from "lit";
+import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
-import { computeStateDisplay } from "../../../common/entity/compute_state_display";
+import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
+import { TextEntity, setValue } from "../../../data/text";
import { HomeAssistant } from "../../../types";
-import { EntitiesCardEntityConfig } from "../cards/types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
-import { LovelaceRow } from "./types";
+import { EntityConfig, LovelaceRow } from "./types";
+import "../../../components/ha-textfield";
+import { computeStateName } from "../../../common/entity/compute_state_name";
@customElement("hui-text-entity-row")
class HuiTextEntityRow extends LitElement implements LovelaceRow {
@property({ attribute: false }) public hass?: HomeAssistant;
- @state() private _config?: EntitiesCardEntityConfig;
+ @state() private _config?: EntityConfig;
- public setConfig(config: EntitiesCardEntityConfig): void {
+ public setConfig(config: EntityConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
@@ -37,7 +32,9 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
return html``;
}
- const stateObj = this.hass.states[this._config.entity];
+ const stateObj = this.hass.states[this._config.entity] as
+ | TextEntity
+ | undefined;
if (!stateObj) {
return html`
@@ -48,22 +45,53 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
}
return html`
-
- ${computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale)}
+
+
`;
}
- static get styles(): CSSResultGroup {
- return css`
- div {
- text-align: right;
- }
- .pointer {
- cursor: pointer;
- }
- `;
+ private _valueChanged(ev): void {
+ const stateObj = this.hass!.states[this._config!.entity] as TextEntity;
+ const newValue = ev.target.value;
+
+ // Filter out invalid text states
+ if (newValue && UNAVAILABLE_STATES.includes(newValue)) {
+ ev.target.value = stateObj.state;
+ return;
+ }
+
+ if (newValue !== stateObj.state) {
+ setValue(this.hass!, stateObj.entity_id, newValue);
+ }
+
+ ev.target.blur();
}
+
+ static styles = css`
+ hui-generic-entity-row {
+ display: flex;
+ align-items: center;
+ }
+ ha-textfield {
+ width: 100%;
+ }
+ `;
}
declare global {
diff --git a/src/state-summary/state-card-content.js b/src/state-summary/state-card-content.js
index 5f2d5ea484..21ca9d08c1 100644
--- a/src/state-summary/state-card-content.js
+++ b/src/state-summary/state-card-content.js
@@ -17,6 +17,7 @@ import "./state-card-number";
import "./state-card-scene";
import "./state-card-script";
import "./state-card-select";
+import "./state-card-text";
import "./state-card-timer";
import "./state-card-toggle";
import "./state-card-vacuum";
diff --git a/src/state-summary/state-card-text.ts b/src/state-summary/state-card-text.ts
new file mode 100644
index 0000000000..5ef0456b95
--- /dev/null
+++ b/src/state-summary/state-card-text.ts
@@ -0,0 +1,73 @@
+import "../components/ha-textfield";
+import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
+import { customElement, property } from "lit/decorators";
+import { computeStateName } from "../common/entity/compute_state_name";
+import { stopPropagation } from "../common/dom/stop_propagation";
+import "../components/entity/state-badge";
+import { UNAVAILABLE, UNAVAILABLE_STATES } from "../data/entity";
+import { TextEntity, setValue } from "../data/text";
+import type { HomeAssistant } from "../types";
+
+@customElement("state-card-text")
+class StateCardText extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property() public stateObj!: TextEntity;
+
+ protected render(): TemplateResult {
+ return html`
+
+
+ `;
+ }
+
+ private _valueChanged(ev): void {
+ const value = ev.target.value;
+
+ // Filter out invalid text states
+ if (value && UNAVAILABLE_STATES.includes(value)) {
+ ev.target.value = this.stateObj.state;
+ return;
+ }
+
+ if (value === this.stateObj.state) {
+ return;
+ }
+ setValue(this.hass!, this.stateObj.entity_id, value);
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ :host {
+ display: flex;
+ }
+
+ state-badge {
+ float: left;
+ margin-top: 10px;
+ }
+
+ ha-textfield {
+ width: 100%;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "state-card-text": StateCardText;
+ }
+}
diff --git a/src/translations/en.json b/src/translations/en.json
index f46100b1b2..091b488c16 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -235,6 +235,9 @@
"installing_with_progress": "Installing ({progress}%)",
"up_to_date": "Up-to-date"
},
+ "text": {
+ "emtpy_value": "(empty value)"
+ },
"timer": {
"actions": {
"start": "start",