web/flows: use aria-invalid attribute to better show invalid input fields (#7661)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-11-21 15:07:30 +01:00 committed by GitHub
parent 78f47a8726
commit 2c6ac73e0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 5 deletions

View File

@ -6,19 +6,20 @@ import { customElement, property } from "lit/decorators.js";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { ErrorDetail } from "@goauthentik/api"; import { ErrorDetail } from "@goauthentik/api";
/** /**
* This is used in two places outside of Flow, and in both cases is used primarily to * This is used in two places outside of Flow, and in both cases is used primarily to
* display content, not take input. It displays the TOPT QR code, and the static * display content, not take input. It displays the TOTP QR code, and the static
* recovery tokens. But it's used a lot in Flow. * recovery tokens. But it's used a lot in Flow.
*/ */
@customElement("ak-form-element") @customElement("ak-form-element")
export class FormElement extends AKElement { export class FormElement extends AKElement {
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFForm, PFFormControl]; return [PFBase, PFForm, PFFormControl];
} }
@property() @property()
@ -28,7 +29,16 @@ export class FormElement extends AKElement {
required = false; required = false;
@property({ attribute: false }) @property({ attribute: false })
errors?: ErrorDetail[]; set errors(value: ErrorDetail[] | undefined) {
this._errors = value;
const hasError = (value || []).length > 0;
this.querySelectorAll("input").forEach((input) => {
input.setAttribute("aria-invalid", hasError.toString());
});
this.requestUpdate();
}
_errors?: ErrorDetail[];
updated(): void { updated(): void {
this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => {
@ -45,8 +55,12 @@ export class FormElement extends AKElement {
: html``} : html``}
</label> </label>
<slot></slot> <slot></slot>
${(this.errors || []).map((error) => { ${(this._errors || []).map((error) => {
return html`<p class="pf-c-form__helper-text pf-m-error">${error.string}</p>`; return html`<p class="pf-c-form__helper-text pf-m-error">
<span class="pf-c-form__helper-text-icon">
<i class="fas fa-exclamation-circle" aria-hidden="true"></i> </span
>${error.string}
</p>`;
})} })}
</div>`; </div>`;
} }

View File

@ -28,6 +28,11 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
timer?: number; timer?: number;
hasError(field: string): boolean {
const errors = (this.challenge?.responseErrors || {})[field];
return (errors || []).length > 0;
}
renderInput(): HTMLInputElement { renderInput(): HTMLInputElement {
this.input = document.createElement("input"); this.input = document.createElement("input");
this.input.type = "password"; this.input.type = "password";
@ -38,6 +43,7 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
this.input.classList.add("pf-c-form-control"); this.input.classList.add("pf-c-form-control");
this.input.required = true; this.input.required = true;
this.input.value = PasswordManagerPrefill.password || ""; this.input.value = PasswordManagerPrefill.password || "";
this.input.setAttribute("aria-invalid", this.hasError("password").toString());
// This is somewhat of a crude way to get autofocus, but in most cases the `autofocus` attribute // This is somewhat of a crude way to get autofocus, but in most cases the `autofocus` attribute
// isn't enough, due to timing within shadow doms and such. // isn't enough, due to timing within shadow doms and such.
this.timer = window.setInterval(() => { this.timer = window.setInterval(() => {