1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-18 07:56:44 +01:00

Migrate form/selector components ha-textfield to ha-input (#30294)

* Migrate ha-textfield to ha-input across form components and update related logic

* Apply suggestions from code review

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
Wendelin
2026-03-24 14:18:09 +01:00
committed by GitHub
parent 5bbfa36228
commit 4bdac1f385
5 changed files with 68 additions and 67 deletions

View File

@@ -1,10 +1,10 @@
import type { PropertyValues, TemplateResult } from "lit"; import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import type { LocalizeFunc } from "../../common/translations/localize"; import type { LocalizeFunc } from "../../common/translations/localize";
import "../ha-textfield"; import "../input/ha-input";
import type { HaTextField } from "../ha-textfield"; import type { HaInput } from "../input/ha-input";
import type { import type {
HaFormElement, HaFormElement,
HaFormFloatData, HaFormFloatData,
@@ -25,7 +25,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@query("ha-textfield", true) private _input?: HaTextField; @query("ha-input", true) private _input?: HaInput;
static shadowRootOptions = { static shadowRootOptions = {
...LitElement.shadowRootOptions, ...LitElement.shadowRootOptions,
@@ -38,23 +38,25 @@ export class HaFormFloat extends LitElement implements HaFormElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-textfield <ha-input
type="number" type="number"
inputMode="decimal" inputMode="decimal"
step="any" step="any"
.label=${this.label} .label=${this.label}
.helper=${this.helper} .hint=${this.helper}
helperPersistent
.value=${this.data !== undefined ? this.data : ""} .value=${this.data !== undefined ? this.data : ""}
.disabled=${this.disabled} .disabled=${this.disabled}
.required=${this.schema.required} .required=${this.schema.required}
.autoValidate=${this.schema.required} .autoValidate=${this.schema.required}
.suffix=${this.schema.description?.suffix}
.validationMessage=${this.schema.required .validationMessage=${this.schema.required
? this.localize?.("ui.common.error_required") ? this.localize?.("ui.common.error_required")
: undefined} : undefined}
@input=${this._valueChanged} @input=${this._handleInput}
></ha-textfield> >
${this.schema.description?.suffix
? html`<span slot="end">${this.schema.description?.suffix}</span>`
: nothing}
</ha-input>
`; `;
} }
@@ -64,9 +66,9 @@ export class HaFormFloat extends LitElement implements HaFormElement {
} }
} }
private _valueChanged(ev: Event) { private _handleInput(ev: InputEvent) {
const source = ev.target as HaTextField; const source = ev.target as HaInput;
const rawValue = source.value.replace(",", "."); const rawValue = (source.value ?? "").replace(",", ".");
let value: number | undefined; let value: number | undefined;
@@ -74,6 +76,11 @@ export class HaFormFloat extends LitElement implements HaFormElement {
return; return;
} }
// Allow user to keep typing decimal places (e.g., 5.0, 5.00, 5.10)
if (rawValue.includes(".") && rawValue.endsWith("0")) {
return;
}
// Allow user to start typing a negative value // Allow user to start typing a negative value
if (rawValue === "-") { if (rawValue === "-") {
return; return;
@@ -105,9 +112,6 @@ export class HaFormFloat extends LitElement implements HaFormElement {
:host([own-margin]) { :host([own-margin]) {
margin-bottom: 5px; margin-bottom: 5px;
} }
ha-textfield {
display: block;
}
`; `;
} }

View File

@@ -1,5 +1,5 @@
import type { PropertyValues, TemplateResult } from "lit"; import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import type { LocalizeFunc } from "../../common/translations/localize"; import type { LocalizeFunc } from "../../common/translations/localize";
@@ -7,8 +7,8 @@ import "../ha-checkbox";
import type { HaCheckbox } from "../ha-checkbox"; import type { HaCheckbox } from "../ha-checkbox";
import "../ha-input-helper-text"; import "../ha-input-helper-text";
import "../ha-slider"; import "../ha-slider";
import "../ha-textfield"; import "../input/ha-input";
import type { HaTextField } from "../ha-textfield"; import type { HaInput } from "../input/ha-input";
import type { import type {
HaFormElement, HaFormElement,
HaFormIntegerData, HaFormIntegerData,
@@ -29,8 +29,8 @@ export class HaFormInteger extends LitElement implements HaFormElement {
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@query("ha-textfield, ha-slider", true) private _input?: @query("ha-input, ha-slider", true) private _input?:
| HaTextField | HaInput
| HTMLInputElement; | HTMLInputElement;
private _lastValue?: HaFormIntegerData; private _lastValue?: HaFormIntegerData;
@@ -89,28 +89,30 @@ export class HaFormInteger extends LitElement implements HaFormElement {
? html`<ha-input-helper-text .disabled=${this.disabled} ? html`<ha-input-helper-text .disabled=${this.disabled}
>${this.helper}</ha-input-helper-text >${this.helper}</ha-input-helper-text
>` >`
: ""} : nothing}
</div> </div>
`; `;
} }
return html` return html`
<ha-textfield <ha-input
type="number" type="number"
inputMode="numeric" inputMode="numeric"
.label=${this.label} .label=${this.label}
.helper=${this.helper} .hint=${this.helper}
helperPersistent .value=${this.data !== undefined ? this.data.toString() : ""}
.value=${this.data !== undefined ? this.data : ""}
.disabled=${this.disabled} .disabled=${this.disabled}
.required=${this.schema.required} .required=${this.schema.required}
.autoValidate=${this.schema.required} .autoValidate=${this.schema.required}
.suffix=${this.schema.description?.suffix}
.validationMessage=${this.schema.required .validationMessage=${this.schema.required
? this.localize?.("ui.common.error_required") ? this.localize?.("ui.common.error_required")
: undefined} : undefined}
@input=${this._valueChanged} @input=${this._valueChanged}
></ha-textfield> >
${this.schema.description?.suffix
? html`<span slot="end">${this.schema.description.suffix}</span>`
: nothing}
</ha-input>
`; `;
} }
@@ -167,8 +169,8 @@ export class HaFormInteger extends LitElement implements HaFormElement {
}); });
} }
private _valueChanged(ev: Event) { private _valueChanged(ev: InputEvent) {
const source = ev.target as HaTextField | HTMLInputElement; const source = ev.target as HaInput | HTMLInputElement;
const rawValue = source.value; const rawValue = source.value;
let value: number | undefined; let value: number | undefined;
@@ -201,9 +203,6 @@ export class HaFormInteger extends LitElement implements HaFormElement {
ha-slider { ha-slider {
flex: 1; flex: 1;
} }
ha-textfield {
display: block;
}
`; `;
} }

View File

@@ -3,13 +3,10 @@ import { customElement, property } from "lit/decorators";
import { hex2rgb, rgb2hex } from "../../common/color/convert-color"; import { hex2rgb, rgb2hex } from "../../common/color/convert-color";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import type { ColorRGBSelector } from "../../data/selector"; import type { ColorRGBSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types"; import "../input/ha-input";
import "../ha-textfield";
@customElement("ha-selector-color_rgb") @customElement("ha-selector-color_rgb")
export class HaColorRGBSelector extends LitElement { export class HaColorRGBSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: ColorRGBSelector; @property({ attribute: false }) public selector!: ColorRGBSelector;
@property() public value?: string; @property() public value?: string;
@@ -24,16 +21,15 @@ export class HaColorRGBSelector extends LitElement {
protected render() { protected render() {
return html` return html`
<ha-textfield <ha-input
type="color" type="color"
helperPersistent
.value=${this.value ? rgb2hex(this.value as any) : ""} .value=${this.value ? rgb2hex(this.value as any) : ""}
.label=${this.label || ""} .label=${this.label || ""}
.required=${this.required} .required=${this.required}
.helper=${this.helper} .hint=${this.helper}
.disabled=${this.disabled} .disabled=${this.disabled}
@change=${this._valueChanged} @change=${this._valueChanged}
></ha-textfield> ></ha-input>
`; `;
} }
@@ -50,14 +46,10 @@ export class HaColorRGBSelector extends LitElement {
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
} }
ha-textfield { ha-input {
--text-field-padding-top: 8px;
--text-field-padding-bottom: 8px;
--text-field-padding-start: 8px;
--text-field-padding-end: 8px;
min-width: 75px; min-width: 75px;
flex-grow: 1; flex-grow: 1;
margin: 0 4px; margin: 0 var(--ha-space-1);
} }
`; `;
} }

View File

@@ -1,19 +1,15 @@
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import type { NumberSelector } from "../../data/selector"; import type { NumberSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-input-helper-text"; import "../ha-input-helper-text";
import "../ha-slider"; import "../ha-slider";
import "../ha-textfield"; import "../input/ha-input";
import type { HaTextField } from "../ha-textfield"; import type { HaInput } from "../input/ha-input";
@customElement("ha-selector-number") @customElement("ha-selector-number")
export class HaNumberSelector extends LitElement { export class HaNumberSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: NumberSelector; @property({ attribute: false }) public selector!: NumberSelector;
@property({ type: Number }) public value?: number; @property({ type: Number }) public value?: number;
@@ -31,7 +27,7 @@ export class HaNumberSelector extends LitElement {
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@query("ha-textfield", true) private _input?: HaTextField | HTMLInputElement; @query("ha-input", true) private _input?: HaInput;
private _valueStr = ""; private _valueStr = "";
@@ -99,29 +95,30 @@ export class HaNumberSelector extends LitElement {
</ha-slider> </ha-slider>
` `
: nothing} : nothing}
<ha-textfield <ha-input
.inputMode=${this.selector.number?.step === "any" || .inputMode=${this.selector.number?.step === "any" ||
(this.selector.number?.step ?? 1) % 1 !== 0 (this.selector.number?.step ?? 1) % 1 !== 0
? "decimal" ? "decimal"
: "numeric"} : "numeric"}
.label=${!isBox ? undefined : this.label} .label=${!isBox ? undefined : this.label}
.placeholder=${this.placeholder} .placeholder=${this.placeholder !== undefined
class=${classMap({ single: isBox })} ? this.placeholder.toString()
: ""}
class=${isBox ? "single" : ""}
.min=${this.selector.number?.min} .min=${this.selector.number?.min}
.max=${this.selector.number?.max} .max=${this.selector.number?.max}
.value=${this._valueStr ?? ""} .value=${this._valueStr ?? ""}
.step=${this.selector.number?.step ?? 1} .step=${this.selector.number?.step ?? 1}
helperPersistent .hint=${isBox ? this.helper : undefined}
.helper=${isBox ? this.helper : undefined}
.disabled=${this.disabled} .disabled=${this.disabled}
.required=${this.required} .required=${this.required}
.suffix=${unit}
type="number" type="number"
autoValidate autoValidate
?no-spinner=${!isBox} .withoutSpinButtons=${!isBox}
@input=${this._handleInputChange} @input=${this._handleInputChange}
> >
</ha-textfield> ${unit ? html`<span slot="end">${unit}</span>` : nothing}
</ha-input>
</div> </div>
${!isBox && this.helper ${!isBox && this.helper
? html`<ha-input-helper-text .disabled=${this.disabled} ? html`<ha-input-helper-text .disabled=${this.disabled}
@@ -166,11 +163,10 @@ export class HaNumberSelector extends LitElement {
margin-inline-end: 16px; margin-inline-end: 16px;
margin-inline-start: 0; margin-inline-start: 0;
} }
ha-textfield { ha-input::part(wa-input) {
--ha-textfield-input-width: 40px; width: 40px;
} }
.single { ha-input.single {
--ha-textfield-input-width: unset;
flex: 1; flex: 1;
} }
`; `;

View File

@@ -22,6 +22,7 @@ export type InputType =
| "tel" | "tel"
| "text" | "text"
| "time" | "time"
| "color"
| "url"; | "url";
@customElement("ha-input") @customElement("ha-input")
@@ -296,7 +297,9 @@ export class HaInput extends LitElement {
.disabled=${this.disabled} .disabled=${this.disabled}
class=${classMap({ class=${classMap({
invalid: this.invalid || this._invalid, invalid: this.invalid || this._invalid,
"label-raised": this.value || (this.label && this.placeholder), "label-raised":
(this.value !== undefined && this.value !== "") ||
(this.label && this.placeholder),
"no-label": !this.label, "no-label": !this.label,
"hint-hidden": "hint-hidden":
!this.hint && !this.hint &&
@@ -512,6 +515,13 @@ export class HaInput extends LitElement {
} }
:host([type="color"]) wa-input::part(input) { :host([type="color"]) wa-input::part(input) {
padding-top: var(--ha-space-6); padding-top: var(--ha-space-6);
cursor: pointer;
}
:host([type="color"]) wa-input.no-label::part(input) {
padding: var(--ha-space-2);
}
:host([type="color"]) wa-input.no-label::part(base) {
padding: 0;
} }
wa-input::part(input)::placeholder { wa-input::part(input)::placeholder {
color: var(--ha-color-neutral-60); color: var(--ha-color-neutral-60);