diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts index 85dcf053e9..c04f91acc9 100644 --- a/src/auth/ha-auth-flow.ts +++ b/src/auth/ha-auth-flow.ts @@ -7,6 +7,7 @@ import { PropertyValues, TemplateResult, } from "lit"; +import "./ha-password-manager-polyfill"; import { property, state } from "lit/decorators"; import "../components/ha-form/ha-form"; import "../components/ha-markdown"; @@ -20,7 +21,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin"; type State = "loading" | "error" | "step"; class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { - @property() public authProvider?: AuthProvider; + @property({ attribute: false }) public authProvider?: AuthProvider; @property() public clientId?: string; @@ -37,7 +38,15 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { @state() private _errorMessage?: string; protected render() { - return html`
${this._renderForm()}
`; + return html` +
${this._renderForm()}
+ + `; } protected firstUpdated(changedProps: PropertyValues) { @@ -231,11 +240,17 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { await this.updateComplete; // 100ms to give all the form elements time to initialize. setTimeout(() => { - const form = this.shadowRoot!.querySelector("ha-form"); + const form = this.renderRoot.querySelector("ha-form"); if (form) { (form as any).focus(); } }, 100); + + setTimeout(() => { + this.renderRoot.querySelector( + "ha-password-manager-polyfill" + )!.boundingRect = this.getBoundingClientRect(); + }, 500); } private _stepDataChanged(ev: CustomEvent) { @@ -329,3 +344,9 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { } } customElements.define("ha-auth-flow", HaAuthFlow); + +declare global { + interface HTMLElementTagNameMap { + "ha-auth-flow": HaAuthFlow; + } +} diff --git a/src/auth/ha-password-manager-polyfill.ts b/src/auth/ha-password-manager-polyfill.ts new file mode 100644 index 0000000000..1a2e765e05 --- /dev/null +++ b/src/auth/ha-password-manager-polyfill.ts @@ -0,0 +1,110 @@ +import { html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../common/dom/fire_event"; +import { HaFormSchema } from "../components/ha-form/ha-form"; +import { 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; + + protected createRenderRoot() { + // Add under document body so the element isn't placed inside any shadow roots + return document.body; + } + + private get styles() { + return ` + .password-manager-polyfill { + position: absolute; + top: ${this.boundingRect?.y || 148}px; + left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px); + width: ${this.boundingRect?.width || 360}px; + opacity: 0; + z-index: -1; + } + .password-manager-polyfill input { + width: 100%; + height: 62px; + padding: 0; + border: 0; + } + .password-manager-polyfill input[type="submit"] { + width: 0; + height: 0; + } + `; + } + + protected render(): TemplateResult { + if ( + this.step && + this.step.type === "form" && + this.step.step_id === "init" && + ENABLED_HANDLERS.includes(this.step.handler[0]) + ) { + return html` + + `; + } + return html``; + } + + private render_input(schema: HaFormSchema): TemplateResult | string { + const inputType = schema.name.includes("password") ? "password" : "text"; + if (schema.type !== "string") { + return ""; + } + return html` + + `; + } + + private _handleSubmit(ev: Event) { + 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, + }); + } +}