mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Add password field element (#22121)
* Add password field element * Update ha-password-field.ts
This commit is contained in:
parent
a92dab46c2
commit
4e8b58cd6c
@ -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>`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
|
@ -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>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
|
160
src/components/ha-password-field.ts
Normal file
160
src/components/ha-password-field.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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,14 +28,24 @@ 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"
|
||||
: ""
|
||||
);
|
||||
this.reportValidity();
|
||||
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) {
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user