diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts
index f23733bb79..b64cd092dd 100644
--- a/src/auth/ha-auth-flow.ts
+++ b/src/auth/ha-auth-flow.ts
@@ -1,19 +1,12 @@
+/* eslint-disable lit/prefer-static-styles */
import "@material/mwc-button";
import { genClientId } from "home-assistant-js-websocket";
-import {
- css,
- CSSResultGroup,
- html,
- LitElement,
- nothing,
- PropertyValues,
-} from "lit";
+import { html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-alert";
import "../components/ha-checkbox";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
-import "../components/ha-form/ha-form";
import "../components/ha-formfield";
import "../components/ha-markdown";
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
@@ -21,7 +14,7 @@ import {
DataEntryFlowStep,
DataEntryFlowStepForm,
} from "../data/data_entry_flow";
-import "./ha-password-manager-polyfill";
+import "./ha-auth-form";
type State = "loading" | "error" | "step";
@@ -49,6 +42,10 @@ export class HaAuthFlow extends LitElement {
@state() private _storeToken = false;
+ createRenderRoot() {
+ return this;
+ }
+
willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
@@ -79,13 +76,17 @@ export class HaAuthFlow extends LitElement {
protected render() {
return html`
+
-
`;
}
@@ -128,12 +129,6 @@ export class HaAuthFlow extends LitElement {
(form as any).focus();
}
}, 100);
-
- setTimeout(() => {
- this.renderRoot.querySelector(
- "ha-password-manager-polyfill"
- )!.boundingRect = this.getBoundingClientRect();
- }, 500);
}
private _renderForm() {
@@ -205,7 +200,7 @@ export class HaAuthFlow extends LitElement {
>
`
: nothing}
-
+ >
${this.clientId === genClientId() &&
!["select_mfa_module", "mfa"].includes(step.step_id)
? html`
@@ -395,20 +390,6 @@ export class HaAuthFlow extends LitElement {
this._submitting = false;
}
}
-
- static get styles(): CSSResultGroup {
- return css`
- .action {
- margin: 24px 0 8px;
- text-align: center;
- }
- /* Align with the rest of the form. */
- .store-token {
- margin-top: 10px;
- margin-left: -16px;
- }
- `;
- }
}
declare global {
diff --git a/src/auth/ha-auth-form-string.ts b/src/auth/ha-auth-form-string.ts
new file mode 100644
index 0000000000..89fa713e42
--- /dev/null
+++ b/src/auth/ha-auth-form-string.ts
@@ -0,0 +1,69 @@
+/* eslint-disable lit/prefer-static-styles */
+import { TemplateResult, html } from "lit";
+import { customElement } from "lit/decorators";
+import { HaFormString } from "../components/ha-form/ha-form-string";
+import "../components/ha-icon-button";
+import "./ha-auth-textfield";
+
+@customElement("ha-auth-form-string")
+export class HaAuthFormString extends HaFormString {
+ protected createRenderRoot() {
+ // add parent style to light dom
+ const style = document.createElement("style");
+ style.innerHTML = HaFormString.elementStyles as unknown as string;
+ this.append(style);
+ return this;
+ }
+
+ protected render(): TemplateResult {
+ return html`
+
+ `
+ : this.schema.description?.suffix
+ }
+ .validationMessage=${this.schema.required ? "Required" : undefined}
+ @input=${this._valueChanged}
+ @change=${this._valueChanged}
+ >
+ ${this.renderIcon()}
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-auth-form-string": HaAuthFormString;
+ }
+}
diff --git a/src/auth/ha-auth-form.ts b/src/auth/ha-auth-form.ts
new file mode 100644
index 0000000000..1a098caf0d
--- /dev/null
+++ b/src/auth/ha-auth-form.ts
@@ -0,0 +1,30 @@
+/* eslint-disable lit/prefer-static-styles */
+import { customElement } from "lit/decorators";
+import { HaForm } from "../components/ha-form/ha-form";
+import "./ha-auth-form-string";
+
+@customElement("ha-auth-form")
+export class HaAuthForm extends HaForm {
+ protected fieldElementName(type: string): string {
+ if (type === "string") {
+ return `ha-auth-form-${type}`;
+ }
+ return super.fieldElementName(type);
+ }
+
+ protected createRenderRoot() {
+ // add parent style to light dom
+ const style = document.createElement("style");
+ style.innerHTML = HaForm.elementStyles as unknown as string;
+ this.append(style);
+ // attach it as soon as possible to make sure we fetch all events.
+ this.addValueChangedListener(this);
+ return this;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-auth-form": HaAuthForm;
+ }
+}
diff --git a/src/auth/ha-auth-textfield.ts b/src/auth/ha-auth-textfield.ts
new file mode 100644
index 0000000000..7c4bb2c6cc
--- /dev/null
+++ b/src/auth/ha-auth-textfield.ts
@@ -0,0 +1,186 @@
+/* eslint-disable lit/prefer-static-styles */
+import { html } from "lit";
+import { customElement } from "lit/decorators";
+import { HaTextField } from "../components/ha-textfield";
+import "@material/mwc-textfield/mwc-textfield.css";
+
+@customElement("ha-auth-textfield")
+export class HaAuthTextField extends HaTextField {
+ public render() {
+ return html`
+
+ ${super.render()}
+ `;
+ }
+
+ protected createRenderRoot() {
+ // add parent style to light dom
+ const style = document.createElement("style");
+ style.innerHTML = HaTextField.elementStyles as unknown as string;
+ this.append(style);
+ return this;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-auth-textfield": HaAuthTextField;
+ }
+}
diff --git a/src/auth/ha-authorize.ts b/src/auth/ha-authorize.ts
index 9778edfc0a..54d59bd8e9 100644
--- a/src/auth/ha-authorize.ts
+++ b/src/auth/ha-authorize.ts
@@ -1,13 +1,7 @@
-import punycode from "punycode";
-import {
- css,
- CSSResultGroup,
- html,
- LitElement,
- nothing,
- PropertyValues,
-} from "lit";
+/* eslint-disable lit/prefer-static-styles */
+import { html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
+import punycode from "punycode";
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
import { extractSearchParamsObject } from "../common/url/search-params";
import "../components/ha-alert";
@@ -61,13 +55,27 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
protected render() {
if (this._error) {
- return html`${this._error} ${this.redirectUri}`;
+ return html`
+
+ ${this._error} ${this.redirectUri}
+ `;
}
if (!this._authProviders) {
return html`
+
${this.localize("ui.panel.page-authorize.initializing")}
`;
}
@@ -79,6 +87,25 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
const app = this.clientId && this.clientId in appNames;
return html`
+
+
${!this._ownInstance
? html`
${app
@@ -123,6 +150,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
`;
}
+ createRenderRoot() {
+ return this;
+ }
+
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
@@ -217,25 +248,4 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
private async _handleAuthProviderPick(ev) {
this._authProvider = ev.detail;
}
-
- static get styles(): CSSResultGroup {
- return css`
- ha-pick-auth-provider {
- display: block;
- margin-top: 48px;
- }
- ha-auth-flow {
- display: block;
- margin-top: 24px;
- }
- ha-alert {
- display: block;
- margin: 16px 0;
- }
- p {
- font-size: 14px;
- line-height: 20px;
- }
- `;
- }
}
diff --git a/src/auth/ha-password-manager-polyfill.ts b/src/auth/ha-password-manager-polyfill.ts
deleted file mode 100644
index 7c66d11fe3..0000000000
--- a/src/auth/ha-password-manager-polyfill.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import { css, html, LitElement, nothing } from "lit";
-import { customElement, property } from "lit/decorators";
-import { styleMap } from "lit/directives/style-map";
-import { fireEvent } from "../common/dom/fire_event";
-import type { HaFormSchema } from "../components/ha-form/types";
-import { autocompleteLoginFields } from "../data/auth";
-import type { DataEntryFlowStep } from "../data/data_entry_flow";
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-password-manager-polyfill": HaPasswordManagerPolyfill;
- }
- interface HASSDomEvents {
- "form-submitted": undefined;
- }
-}
-
-const ENABLED_HANDLERS = [
- "homeassistant",
- "legacy_api_password",
- "command_line",
-];
-
-@customElement("ha-password-manager-polyfill")
-export class HaPasswordManagerPolyfill extends LitElement {
- @property({ attribute: false }) public step?: DataEntryFlowStep;
-
- @property({ attribute: false }) public stepData: any;
-
- @property({ attribute: false }) public boundingRect?: DOMRect;
-
- private _styleElement?: HTMLStyleElement;
-
- public connectedCallback() {
- super.connectedCallback();
- this._styleElement = document.createElement("style");
- this._styleElement.textContent = css`
- /* Polyfill form is sized and vertically aligned with true form, then positioned offscreen
- rather than hiding so it does not create a new stacking context */
- .password-manager-polyfill {
- position: absolute;
- box-sizing: border-box;
- }
- /* Excluding our wrapper, move any children back on screen, including anything injected that might not already be positioned */
- .password-manager-polyfill > *:not(.wrapper),
- .password-manager-polyfill > .wrapper > * {
- position: relative;
- left: 10000px;
- }
- /* Size and hide our polyfill fields */
- .password-manager-polyfill .underneath {
- display: block;
- box-sizing: border-box;
- width: 100%;
- padding: 0 16px;
- border: 0;
- z-index: -1;
- height: 21px;
- /* Transparency is only needed to hide during paint or in case of misalignment,
- but LastPass will fail if it's 0, so we use 1% */
- opacity: 0.01;
- }
- .password-manager-polyfill input.underneath {
- height: 28px;
- margin-bottom: 30.5px;
- }
- /* Button position is not important, but size should not be zero */
- .password-manager-polyfill > input.underneath[type="submit"] {
- width: 1px;
- height: 1px;
- margin: 0 auto;
- overflow: hidden;
- }
- /* Ensure injected elements will be on top */
- .password-manager-polyfill > *:not(.underneath, .wrapper),
- .password-manager-polyfill > .wrapper > *:not(.underneath) {
- isolation: isolate;
- z-index: auto;
- }
- `.toString();
- document.head.append(this._styleElement);
- }
-
- public disconnectedCallback() {
- super.disconnectedCallback();
- this._styleElement?.remove();
- delete this._styleElement;
- }
-
- protected createRenderRoot() {
- // Add under document body so the element isn't placed inside any shadow roots
- return document.body;
- }
-
- protected render() {
- if (
- this.step &&
- this.step.type === "form" &&
- this.step.step_id === "init" &&
- ENABLED_HANDLERS.includes(this.step.handler[0])
- ) {
- return html`
-
- `;
- }
- return nothing;
- }
-
- private render_input(schema: HaFormSchema) {
- const inputType = schema.name.includes("password") ? "password" : "text";
- if (schema.type !== "string") {
- return "";
- }
- return html`
-
-
-
-
-
-
-
- `;
- }
-
- private _handleSubmit(ev: SubmitEvent) {
- ev.preventDefault();
- fireEvent(this, "form-submitted");
- }
-
- private _valueChanged(ev: Event) {
- const target = ev.target as HTMLInputElement;
- this.stepData = { ...this.stepData, [target.id]: target.value };
- fireEvent(this, "value-changed", {
- value: this.stepData,
- });
- }
-}
diff --git a/src/components/ha-form/ha-form-string.ts b/src/components/ha-form/ha-form-string.ts
index 5cc63ce1d4..3118fcf948 100644
--- a/src/components/ha-form/ha-form-string.ts
+++ b/src/components/ha-form/ha-form-string.ts
@@ -1,11 +1,13 @@
+/* eslint-disable lit/prefer-static-styles */
import { mdiEye, mdiEyeOff } from "@mdi/js";
import {
- css,
CSSResultGroup,
- html,
LitElement,
PropertyValues,
TemplateResult,
+ css,
+ html,
+ nothing,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
@@ -32,7 +34,7 @@ export class HaFormString extends LitElement implements HaFormElement {
@property({ type: Boolean }) public disabled = false;
- @state() private _unmaskedPassword = false;
+ @state() protected unmaskedPassword = false;
@query("ha-textfield") private _input?: HaTextField;
@@ -43,14 +45,11 @@ export class HaFormString extends LitElement implements HaFormElement {
}
protected render(): TemplateResult {
- const isPassword = MASKED_FIELDS.some((field) =>
- this.schema.name.includes(field)
- );
return html`
`
: this.schema.description?.suffix}
@@ -70,14 +69,19 @@ export class HaFormString extends LitElement implements HaFormElement {
@input=${this._valueChanged}
@change=${this._valueChanged}
>
- ${isPassword
- ? html``
- : ""}
+ ${this.renderIcon()}
+ `;
+ }
+
+ protected renderIcon() {
+ if (!this.isPassword) return nothing;
+ return html`
+
`;
}
@@ -87,11 +91,11 @@ export class HaFormString extends LitElement implements HaFormElement {
}
}
- private _toggleUnmaskedPassword(): void {
- this._unmaskedPassword = !this._unmaskedPassword;
+ protected toggleUnmaskedPassword(): void {
+ this.unmaskedPassword = !this.unmaskedPassword;
}
- private _valueChanged(ev: Event): void {
+ protected _valueChanged(ev: Event): void {
let value: string | undefined = (ev.target as HaTextField).value;
if (this.data === value) {
return;
@@ -104,7 +108,7 @@ export class HaFormString extends LitElement implements HaFormElement {
});
}
- private get _stringType(): string {
+ protected get stringType(): string {
if (this.schema.format) {
if (["email", "url"].includes(this.schema.format)) {
return this.schema.format;
@@ -116,6 +120,10 @@ export class HaFormString extends LitElement implements HaFormElement {
return "text";
}
+ protected get isPassword(): boolean {
+ return MASKED_FIELDS.some((field) => this.schema.name.includes(field));
+ }
+
static get styles(): CSSResultGroup {
return css`
:host {
diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts
index 98c177f482..b02f7f5b0d 100644
--- a/src/components/ha-form/ha-form.ts
+++ b/src/components/ha-form/ha-form.ts
@@ -1,3 +1,4 @@
+/* eslint-disable lit/prefer-static-styles */
import {
css,
CSSResultGroup,
@@ -135,7 +136,7 @@ export class HaForm extends LitElement implements HaFormElement {
.required=${item.required || false}
.context=${this._generateContext(item)}
>`
- : dynamicElement(`ha-form-${item.type}`, {
+ : dynamicElement(this.fieldElementName(item.type), {
schema: item,
data: getValue(this.data, item),
label: this._computeLabel(item, this.data),
@@ -152,6 +153,10 @@ export class HaForm extends LitElement implements HaFormElement {
`;
}
+ protected fieldElementName(type: string): string {
+ return `ha-form-${type}`;
+ }
+
private _generateContext(
schema: HaFormSchema
): Record | undefined {
@@ -169,10 +174,17 @@ export class HaForm extends LitElement implements HaFormElement {
protected createRenderRoot() {
const root = super.createRenderRoot();
// attach it as soon as possible to make sure we fetch all events.
- root.addEventListener("value-changed", (ev) => {
+ this.addValueChangedListener(root);
+ return root;
+ }
+
+ protected addValueChangedListener(element: Element | ShadowRoot) {
+ element.addEventListener("value-changed", (ev) => {
ev.stopPropagation();
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
+ if (ev.target === this) return;
+
const newValue = !schema.name
? ev.detail.value
: { [schema.name]: ev.detail.value };
@@ -181,7 +193,6 @@ export class HaForm extends LitElement implements HaFormElement {
value: { ...this.data, ...newValue },
});
});
- return root;
}
private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) {
diff --git a/src/html/authorize.html.template b/src/html/authorize.html.template
index 740d2b2c79..b5fd0259f4 100644
--- a/src/html/authorize.html.template
+++ b/src/html/authorize.html.template
@@ -53,7 +53,7 @@
- Initializing
+
<%= renderTemplate("_js_base.html.template") %>
<%= renderTemplate("_preload_roboto.html.template") %>