Add password field element (#22121)

* Add password field element

* Update ha-password-field.ts
This commit is contained in:
Bram Kragten 2024-09-27 12:34:28 +02:00 committed by GitHub
parent a92dab46c2
commit 4e8b58cd6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 219 additions and 51 deletions

View File

@ -15,6 +15,7 @@ import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-textfield";
import "../../../src/components/ha-password-field";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
@ -261,23 +262,21 @@ export class SupervisorBackupContent extends LitElement {
: ""}
${this.backupHasPassword
? html`
<ha-textfield
<ha-password-field
.label=${this._localize("password")}
type="password"
name="backupPassword"
.value=${this.backupPassword}
@change=${this._handleTextValueChanged}
>
</ha-textfield>
</ha-password-field>
${!this.backup
? html`<ha-textfield
? html`<ha-password-field
.label=${this._localize("confirm_password")}
type="password"
name="confirmBackupPassword"
.value=${this.confirmBackupPassword}
@change=${this._handleTextValueChanged}
>
</ha-textfield>`
</ha-password-field>`
: ""}
`
: ""}

View File

@ -13,10 +13,12 @@ import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-textfield";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-password-field";
import "../../../../src/components/ha-radio";
import "../../../../src/components/ha-textfield";
import type { HaTextField } from "../../../../src/components/ha-textfield";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
AccessPoints,
@ -34,7 +36,6 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { HassioNetworkDialogParams } from "./show-dialog-network";
import type { HaTextField } from "../../../../src/components/ha-textfield";
const IP_VERSIONS = ["ipv4", "ipv6"];
@ -246,9 +247,8 @@ export class DialogHassioNetwork
${this._wifiConfiguration.auth === "wpa-psk" ||
this._wifiConfiguration.auth === "wep"
? html`
<ha-textfield
<ha-password-field
class="flex-auto"
type="password"
id="psk"
.label=${this.supervisor.localize(
"dialog.network.wifi_password"
@ -256,7 +256,7 @@ export class DialogHassioNetwork
version="wifi"
@change=${this._handleInputValueChangedWifi}
>
</ha-textfield>
</ha-password-field>
`
: ""}
`

View File

@ -0,0 +1,160 @@
import { TextAreaCharCounter } from "@material/mwc-textfield/mwc-textfield-base";
import { mdiEye, mdiEyeOff } from "@mdi/js";
import { LitElement, css, html } from "lit";
import { customElement, eventOptions, property, state } from "lit/decorators";
import { HomeAssistant } from "../types";
import "./ha-icon-button";
import "./ha-textfield";
@customElement("ha-password-field")
export class HaPasswordField extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public invalid?: boolean;
@property({ attribute: "error-message" }) public errorMessage?: string;
@property({ type: Boolean }) public icon = false;
@property({ type: Boolean }) public iconTrailing = false;
@property() public autocomplete?: string;
@property() public autocorrect?: string;
@property({ attribute: "input-spellcheck" })
public inputSpellcheck?: string;
@property({ type: String }) value = "";
@property({ type: String }) placeholder = "";
@property({ type: String }) label = "";
@property({ type: Boolean, reflect: true }) disabled = false;
@property({ type: Boolean }) required = false;
@property({ type: Number }) minLength = -1;
@property({ type: Number }) maxLength = -1;
@property({ type: Boolean, reflect: true }) outlined = false;
@property({ type: String }) helper = "";
@property({ type: Boolean }) validateOnInitialRender = false;
@property({ type: String }) validationMessage = "";
@property({ type: Boolean }) autoValidate = false;
@property({ type: String }) pattern = "";
@property({ type: Number }) size: number | null = null;
@property({ type: Boolean }) helperPersistent = false;
@property({ type: Boolean }) charCounter: boolean | TextAreaCharCounter =
false;
@property({ type: Boolean }) endAligned = false;
@property({ type: String }) prefix = "";
@property({ type: String }) suffix = "";
@property({ type: String }) name = "";
@property({ type: String, attribute: "input-mode" })
inputMode!: string;
@property({ type: Boolean }) readOnly = false;
@property({ type: String }) autocapitalize = "";
@state() private _unmaskedPassword = false;
protected render() {
return html`<ha-textfield
.invalid=${this.invalid}
.errorMessage=${this.errorMessage}
.icon=${this.icon}
.iconTrailing=${this.iconTrailing}
.autocomplete=${this.autocomplete}
.autocorrect=${this.autocorrect}
.inputSpellcheck=${this.inputSpellcheck}
.value=${this.value}
.placeholder=${this.placeholder}
.label=${this.label}
.disabled=${this.disabled}
.required=${this.required}
.minLength=${this.minLength}
.maxLength=${this.maxLength}
.outlined=${this.outlined}
.helper=${this.helper}
.validateOnInitialRender=${this.validateOnInitialRender}
.validationMessage=${this.validationMessage}
.autoValidate=${this.autoValidate}
.pattern=${this.pattern}
.size=${this.size}
.helperPersistent=${this.helperPersistent}
.charCounter=${this.charCounter}
.endAligned=${this.endAligned}
.prefix=${this.prefix}
.name=${this.name}
.inputMode=${this.inputMode}
.readOnly=${this.readOnly}
.autocapitalize=${this.autocapitalize}
.type=${this._unmaskedPassword ? "text" : "password"}
.suffix=${html`<div style="width: 24px"></div>`}
@input=${this._handleInputChange}
></ha-textfield>
<ha-icon-button
toggles
.label=${this.hass?.localize(
this._unmaskedPassword
? "ui.components.selectors.text.hide_password"
: "ui.components.selectors.text.show_password"
) || (this._unmaskedPassword ? "Hide password" : "Show password")}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>`;
}
private _toggleUnmaskedPassword(): void {
this._unmaskedPassword = !this._unmaskedPassword;
}
@eventOptions({ passive: true })
private _handleInputChange(ev) {
this.value = ev.target.value;
}
static styles = css`
:host {
display: block;
position: relative;
}
ha-textfield {
width: 100%;
}
ha-icon-button {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-password-field": HaPasswordField;
}
}

View File

@ -6,7 +6,7 @@ import { mainWindow } from "../common/dom/get_main_window";
@customElement("ha-textfield")
export class HaTextField extends TextFieldBase {
@property({ type: Boolean }) public invalid = false;
@property({ type: Boolean }) public invalid?: boolean;
@property({ attribute: "error-message" }) public errorMessage?: string;
@ -28,15 +28,25 @@ export class HaTextField extends TextFieldBase {
override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (
(changedProperties.has("invalid") &&
(this.invalid || changedProperties.get("invalid") !== undefined)) ||
changedProperties.has("invalid") ||
changedProperties.has("errorMessage")
) {
this.setCustomValidity(
this.invalid ? this.errorMessage || "Invalid" : ""
this.invalid
? this.errorMessage || this.validationMessage || "Invalid"
: ""
);
if (
this.invalid ||
this.validateOnInitialRender ||
(changedProperties.has("invalid") &&
changedProperties.get("invalid") !== undefined)
) {
// Only report validity if the field is invalid or the invalid state has changed from
// true to false to prevent setting empty required fields to invalid on first render
this.reportValidity();
}
}
if (changedProperties.has("autocomplete")) {
if (this.autocomplete) {
this.formElement.setAttribute("autocomplete", this.autocomplete);

View File

@ -5,12 +5,13 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-circular-progress";
import "../../../components/ha-combo-box";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-markdown";
import "../../../components/ha-password-field";
import "../../../components/ha-textfield";
import "../../../components/ha-button";
import {
ApplicationCredential,
ApplicationCredentialsConfig,
@ -208,11 +209,10 @@ export class DialogAddApplicationCredential extends LitElement {
)}
helperPersistent
></ha-textfield>
<ha-textfield
<ha-password-field
.label=${this.hass.localize(
"ui.panel.config.application_credentials.editor.client_secret"
)}
type="password"
name="clientSecret"
.value=${this._clientSecret}
required
@ -222,7 +222,7 @@ export class DialogAddApplicationCredential extends LitElement {
"ui.panel.config.application_credentials.editor.client_secret_helper"
)}
helperPersistent
></ha-textfield>
></ha-password-field>
</div>
${this._loading
? html`

View File

@ -22,6 +22,7 @@ import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import "../../ha-config-section";
import { setAssistPipelinePreferred } from "../../../../data/assist_pipeline";
import "../../../../components/ha-password-field";
@customElement("cloud-login")
export class CloudLogin extends LitElement {
@ -142,14 +143,13 @@ export class CloudLogin extends LitElement {
"ui.panel.config.cloud.login.email_error_msg"
)}
></ha-textfield>
<ha-textfield
<ha-password-field
id="password"
name="password"
.label=${this.hass.localize(
"ui.panel.config.cloud.login.password"
)}
.value=${this._password || ""}
type="password"
autocomplete="current-password"
required
minlength="8"
@ -158,7 +158,7 @@ export class CloudLogin extends LitElement {
.validationMessage=${this.hass.localize(
"ui.panel.config.cloud.login.password_error_msg"
)}
></ha-textfield>
></ha-password-field>
</div>
<div class="card-actions">
<ha-progress-button

View File

@ -11,6 +11,7 @@ import "../../../../layouts/hass-subpage";
import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import "../../ha-config-section";
import "../../../../components/ha-password-field";
@customElement("cloud-register")
export class CloudRegister extends LitElement {
@ -145,14 +146,13 @@ export class CloudRegister extends LitElement {
"ui.panel.config.cloud.register.email_error_msg"
)}
></ha-textfield>
<ha-textfield
<ha-password-field
id="password"
name="password"
.label=${this.hass.localize(
"ui.panel.config.cloud.register.password"
)}
.value=${this._password}
type="password"
autocomplete="new-password"
minlength="8"
required
@ -160,7 +160,7 @@ export class CloudRegister extends LitElement {
validationMessage=${this.hass.localize(
"ui.panel.config.cloud.register.password_error_msg"
)}
></ha-textfield>
></ha-password-field>
</div>
<div class="card-actions">
<ha-progress-button

View File

@ -14,6 +14,11 @@ import "../../../components/ha-circular-progress";
import "../../../components/ha-expansion-panel";
import "../../../components/ha-formfield";
import "../../../components/ha-icon-button";
import "../../../components/ha-password-field";
import "../../../components/ha-radio";
import type { HaRadio } from "../../../components/ha-radio";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import {
AccessPoints,
@ -29,10 +34,6 @@ import {
} from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
import { showIPDetailDialog } from "./show-ip-detail-dialog";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import "../../../components/ha-radio";
import type { HaRadio } from "../../../components/ha-radio";
const IP_VERSIONS = ["ipv4", "ipv6"];
@ -214,8 +215,7 @@ export class HassioNetwork extends LitElement {
${this._wifiConfiguration.auth === "wpa-psk" ||
this._wifiConfiguration.auth === "wep"
? html`
<ha-textfield
type="password"
<ha-password-field
id="psk"
.label=${this.hass.localize(
"ui.panel.config.network.supervisor.wifi_password"
@ -223,7 +223,7 @@ export class HassioNetwork extends LitElement {
.version=${"wifi"}
@change=${this._handleInputValueChangedWifi}
>
</ha-textfield>
</ha-password-field>
`
: ""}
`

View File

@ -29,6 +29,7 @@ import {
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant, ValueChangedEvent } from "../../../types";
import { AddUserDialogParams } from "./show-dialog-add-user";
import "../../../components/ha-password-field";
@customElement("dialog-add-user")
export class DialogAddUser extends LitElement {
@ -87,6 +88,7 @@ export class DialogAddUser extends LitElement {
if (!this._params) {
return nothing;
}
return html`
<ha-dialog
open
@ -130,34 +132,32 @@ export class DialogAddUser extends LitElement {
dialogInitialFocus
></ha-textfield>
<ha-textfield
<ha-password-field
.label=${this.hass.localize(
"ui.panel.config.users.add_user.password"
)}
type="password"
name="password"
.value=${this._password}
required
@input=${this._handleValueChanged}
.validationMessage=${this.hass.localize("ui.common.error_required")}
></ha-textfield>
></ha-password-field>
<ha-textfield
label=${this.hass.localize(
<ha-password-field
.label=${this.hass.localize(
"ui.panel.config.users.add_user.password_confirm"
)}
name="passwordConfirm"
.value=${this._passwordConfirm}
@input=${this._handleValueChanged}
required
type="password"
.invalid=${this._password !== "" &&
this._passwordConfirm !== "" &&
this._passwordConfirm !== this._password}
.validationMessage=${this.hass.localize(
.errorMessage=${this.hass.localize(
"ui.panel.config.users.add_user.password_not_match"
)}
></ha-textfield>
></ha-password-field>
<ha-settings-row>
<span slot="heading">
${this.hass.localize(
@ -311,7 +311,8 @@ export class DialogAddUser extends LitElement {
display: flex;
padding: 8px 0;
}
ha-textfield {
ha-textfield,
ha-password-field {
display: block;
margin-bottom: 8px;
}

View File

@ -11,6 +11,7 @@ import { customElement, property, state } from "lit/decorators";
import "../../components/ha-card";
import "../../components/ha-circular-progress";
import "../../components/ha-textfield";
import "../../components/ha-password-field";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import "../../components/ha-alert";
@ -52,47 +53,44 @@ class HaChangePasswordCard extends LitElement {
? html`<ha-alert alert-type="success">${this._statusMsg}</ha-alert>`
: ""}
<ha-textfield
<ha-password-field
id="currentPassword"
name="currentPassword"
.label=${this.hass.localize(
"ui.panel.profile.change_password.current_password"
)}
type="password"
autocomplete="current-password"
.value=${this._currentPassword}
@input=${this._currentPasswordChanged}
@change=${this._currentPasswordChanged}
required
></ha-textfield>
></ha-password-field>
${this._currentPassword
? html`<ha-textfield
? html`<ha-password-field
.label=${this.hass.localize(
"ui.panel.profile.change_password.new_password"
)}
name="password"
type="password"
autocomplete="new-password"
.value=${this._password}
@input=${this._newPasswordChanged}
@change=${this._newPasswordChanged}
required
auto-validate
></ha-textfield>
<ha-textfield
></ha-password-field>
<ha-password-field
.label=${this.hass.localize(
"ui.panel.profile.change_password.confirm_new_password"
)}
name="passwordConfirm"
type="password"
autocomplete="new-password"
.value=${this._passwordConfirm}
@input=${this._newPasswordConfirmChanged}
@change=${this._newPasswordConfirmChanged}
required
auto-validate
></ha-textfield>`
></ha-password-field>`
: ""}
</div>