From 4b51838f428287e40f041420728680c01e051a03 Mon Sep 17 00:00:00 2001
From: Ken Sternberg
Date: Wed, 18 Mar 2026 15:25:11 -0700
Subject: [PATCH 01/10] ## What
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
window.authentik.flow = {
"layout": "{{ flow.layout }}",
+ "background": "{{ flow.background }}",
+ "title": "{{ flow.title }}",
};
Amends the `flow.html` template and `GlobalAuthentik` parser to include new parameters, `background` and `title`, in the flow-specific part of the configuration written to the HTML `` object, and to provide those parameters to client code.
## Why
The `layout` is start-up critical: it tells the Flow interface how the admin wants the Flow page to look, and allows the HTML and CSS to be pre-aligned to that condition. `layout` is determined on a per-Flow bases, not a per-Stage basis; Flows are derived from a tuple of `(Brand, Application?)`, where the opening policy *may* direct a user to a different flow if the user reached authentik via a redirect from a specific application, but will otherwise fall back to the default Flow for the Brand.
The `background` is a field that is required if the `Flow`’s layout is of type `frame_background`; in this case, the part of the viewport not dedicated to the FlowExecutor is reserved for an `
`;
+ return html`
+ ${msg("Enter the email address or username associated with your account.")}
+ `;
}
protected renderUidField(
@@ -290,7 +297,7 @@ export class IdentificationStage extends BaseStage${msg("Select one of the options below to continue.")}`;
}
- const { inputID, defaultUserIdentification: initialUserIdentification, rememberMeController } = this;
+ const {
+ inputID,
+ defaultUserIdentification: initialUserIdentification,
+ rememberMeController,
+ } = this;
const offerRecovery = flowDesignation === FlowDesignationEnum.Recovery;
const type = fields.length === 1 && fields[0] === UserFieldsEnum.Email ? "email" : "text";
@@ -372,7 +384,11 @@ export class IdentificationStage extends BaseStage
+ return html`
${msg("Use a security key")}
`;
}
@@ -411,7 +427,9 @@ export class IdentificationStage extends BaseStage source.name + idx,
- (source) => this.renderLoginSource(source, showLabels)
+ (source) => this.renderLoginSource(source, showLabels),
)}
`;
}
@@ -436,9 +454,10 @@ export class IdentificationStage extends BaseStage
${light(
html``
+ `,
)}
${sources.length ? this.renderLoginSources(sources, showSourceLabels) : nothing}
`;
@@ -461,8 +480,10 @@ export class IdentificationStage extends BaseStage${msg("Need an account?")}
- ${msg("Sign up.")}`
+ ${msg("Sign up.")}`,
)};
`
: nothing}
@@ -471,7 +492,7 @@ export class IdentificationStage extends BaseStage${msg("Forgot username or password?")}`
+ >`,
)}
`
: nothing}
diff --git a/web/src/styles/authentik/flows.global.css b/web/src/styles/authentik/flows.global.css
index 8901fc2a490e..7d936b9628e7 100644
--- a/web/src/styles/authentik/flows.global.css
+++ b/web/src/styles/authentik/flows.global.css
@@ -8,7 +8,7 @@
@import "@patternfly/patternfly/components/Spinner/spinner.css";
@import "@patternfly/patternfly/components/InputGroup/input-group.css";
@import "@patternfly/patternfly/components/FormControl/form-control.css";
-@import "@patternfly/patternfly/components/Form/form.css";
+@import "@patternfly/patternfly/components/Form/form.css";
@import "@patternfly/patternfly/components/Button/button.css";
@import "@patternfly/patternfly/components/Login/login.css";
From 72d42bc32b1bce6deade38c9bdc82d23d3b4224d Mon Sep 17 00:00:00 2001
From: Ken Sternberg
Date: Wed, 6 May 2026 15:40:37 -0700
Subject: [PATCH 07/10] Removed the helper.
---
.../identification/IdentificationStage.ts | 93 -------------------
1 file changed, 93 deletions(-)
diff --git a/web/src/flow/stages/identification/IdentificationStage.ts b/web/src/flow/stages/identification/IdentificationStage.ts
index 91eb49a0d119..517c8922658d 100644
--- a/web/src/flow/stages/identification/IdentificationStage.ts
+++ b/web/src/flow/stages/identification/IdentificationStage.ts
@@ -130,8 +130,6 @@ export class IdentificationStage extends BaseStage<
this.#prepareRememberMeFrame = requestAnimationFrame(() => {
this.prepareRememberMeController();
});
-
- this.#createHelperForm();
}
}
@@ -182,97 +180,6 @@ export class IdentificationStage extends BaseStage<
//#endregion
- //#region Helper Form
-
- #createHelperForm(): void {
- const compatMode = "ShadyDOM" in window;
- this.#form = document.createElement("form");
- document.documentElement.appendChild(this.#form);
- // Only add the additional username input if we're in a shadow dom
- // otherwise it just confuses browsers
- if (!compatMode) {
- // This is a workaround for the fact that we're in a shadow dom
- // adapted from https://github.com/home-assistant/frontend/issues/3133
- const username = document.createElement("input");
- username.setAttribute("type", "text");
- username.setAttribute("name", "username"); // username as name for high compatibility
- username.setAttribute("autocomplete", "username");
- username.onkeyup = (ev: Event) => {
- const el = ev.target as HTMLInputElement;
- (this.shadowRoot || this)
- .querySelectorAll("input[name=uidField]")
- .forEach((input) => {
- input.value = el.value;
- // Because we assume only one input field exists that matches this
- // call focus so the user can press enter
- input.focus();
- });
- };
- this.#form.appendChild(username);
- }
- // Only add the password field when we don't already show a password field
- if (!compatMode && !this.challenge?.passwordFields) {
- const password = document.createElement("input");
- password.setAttribute("type", "password");
- password.setAttribute("name", "password");
- password.setAttribute("autocomplete", "current-password");
- password.onkeyup = (event: KeyboardEvent) => {
- if (event.key === "Enter") {
- event.preventDefault();
- this.submitForm();
- }
-
- const el = event.target as HTMLInputElement;
- // Because the password field is not actually on this page,
- // and we want to 'prefill' the password for the user,
- // save it globally
- PasswordManagerPrefill.password = el.value;
- // Because password managers fill username, then password,
- // we need to re-focus the uid_field here too
- (this.shadowRoot || this)
- .querySelectorAll("input[name=uidField]")
- .forEach((input) => {
- // Because we assume only one input field exists that matches this
- // call focus so the user can press enter
- input.focus();
- });
- };
-
- this.#form.appendChild(password);
- }
-
- const totp = document.createElement("input");
-
- totp.setAttribute("type", "text");
- totp.setAttribute("name", "code");
- totp.setAttribute("autocomplete", "one-time-code");
- totp.onkeyup = (event: KeyboardEvent) => {
- if (event.key === "Enter") {
- event.preventDefault();
- this.submitForm();
- }
-
- const el = event.target as HTMLInputElement;
- // Because the totp field is not actually on this page,
- // and we want to 'prefill' the totp for the user,
- // save it globally
- PasswordManagerPrefill.totp = el.value;
- // Because totp managers fill username, then password, then optionally,
- // we need to re-focus the uid_field here too
- (this.shadowRoot || this)
- .querySelectorAll("input[name=uidField]")
- .forEach((input) => {
- // Because we assume only one input field exists that matches this
- // call focus so the user can press enter
- input.focus();
- });
- };
-
- this.#form.appendChild(totp);
- }
-
- //#endregion
-
protected override onSubmitSuccess(): void {
this.#form?.remove();
}
From e751386d4da1f2c5ab1767ecc411f9f94ca53037 Mon Sep 17 00:00:00 2001
From: Ken Sternberg
Date: Thu, 7 May 2026 09:05:03 -0700
Subject: [PATCH 08/10] This commit puts the Login input sequence into the
lightDOM.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# WHAT
Fixes a number of nitpicks with the code:
1. Removes the sentinel when our node is disconnected while the component remains connected; this happens when a predicated chooses to, and then not to, render a part of the host template.
2. Avoids re-rendering the slot when it’s already present.
3. Fixes a type-checking issue in `light` directive that forbade the DOM elements from being exported correctly.
4. Removes dead code about `#form` from the Executor.
5. Removes a stray semicolon from the footer template.
---
web/src/elements/directives/light.ts | 77 +++++++++++--------
web/src/flow/FlowExecutor.ts | 16 ++--
.../identification/IdentificationStage.ts | 8 +-
3 files changed, 53 insertions(+), 48 deletions(-)
diff --git a/web/src/elements/directives/light.ts b/web/src/elements/directives/light.ts
index 7bb7751463e9..e7094ec9d9fa 100644
--- a/web/src/elements/directives/light.ts
+++ b/web/src/elements/directives/light.ts
@@ -1,4 +1,4 @@
-import { html, nothing, render, TemplateResult } from "lit";
+import { html, noChange, nothing, render, TemplateResult } from "lit";
import { AsyncDirective, DirectiveResult } from "lit/async-directive.js";
import { ChildPart, directive, PartInfo, PartType } from "lit/directive.js";
import { RootPart } from "lit/html.js";
@@ -14,11 +14,14 @@ export interface LightChildOptions {
}
class LightChildDirective extends AsyncDirective {
- #slotName: string | null = null;
- #slot: HTMLSlotElement | null = null;
- #host: Element | null = null;
- #rootPart: RootPart | null = null;
- #sentinel: Comment | null = null;
+ // These must remain public: the dependency tree of all of these leads to their being imported
+ // into parts of the DOM by the host, and TSC complains otherwise.
+
+ public slotName: string | null = null;
+ public slot: HTMLSlotElement | null = null;
+ public host: Element | null = null;
+ public rootPart: RootPart | null = null;
+ public sentinel: Comment | null = null;
constructor(partInfo: PartInfo) {
super(partInfo);
@@ -27,37 +30,38 @@ class LightChildDirective extends AsyncDirective {
}
}
+ // This is for SSR only.
render(_template?: TemplateResult | DirectiveResult, options?: LightChildOptions) {
- this.#slotName ??= options?.slotName ?? `lc-${Math.random().toString(36).slice(2, 8)}`;
- return html``;
+ this.slotName ??= options?.slotName ?? `lc-${Math.random().toString(36).slice(2, 8)}`;
+ return html``;
}
update(
part: ChildPart,
[template, options = {}]: [TemplateResult | DirectiveResult, LightChildOptions],
) {
- this.#slotName ??= options?.slotName ?? `lc-${Math.random().toString(36).slice(2, 8)}`;
+ this.slotName ??= options?.slotName ?? `lc-${Math.random().toString(36).slice(2, 8)}`;
// This places a comment in the LightDom that belongs to this directive. Comments are not
// part of the DOM tree for the purposes of CSS, so it will be possible to style this child
// directly without a wrapper.
- if (!this.#sentinel) {
+ if (!this.sentinel) {
const rootNode = part.parentNode.getRootNode();
- this.#host ??= (part.options?.host ||
+ this.host ??= (part.options?.host ||
(rootNode instanceof ShadowRoot ? rootNode.host : null)) as Element | null;
- if (!this.#host) {
+ if (!this.host) {
throw new Error(
"light() must be used inside a shadow root or a valid options.host",
);
}
- this.#sentinel = document.createComment("");
- this.#host.appendChild(this.#sentinel);
+ this.sentinel = document.createComment("");
+ this.host.appendChild(this.sentinel);
}
- if (!this.#sentinel.parentNode) {
+ if (!this.sentinel.parentNode) {
throw new Error("Could not assign sentinel to element.");
}
@@ -65,38 +69,45 @@ class LightChildDirective extends AsyncDirective {
Object.entries(options).filter(([key]) => ["host"].includes(key)),
);
- this.#rootPart = render(template, this.#sentinel.parentNode as HTMLElement, {
- renderBefore: this.#sentinel,
+ this.rootPart = render(template, this.sentinel.parentNode as HTMLElement, {
+ renderBefore: this.sentinel,
...renderOptions,
});
- const rendered = this.#sentinel.previousSibling;
+ const rendered = this.sentinel.previousSibling;
if (rendered instanceof Element) {
- rendered.slot = this.#slotName;
+ rendered.slot = this.slotName;
}
- return (this.#slot ??= Object.assign(document.createElement("slot"), {
- name: this.#slotName,
- }));
+ if (!this.slot) {
+ this.slot = Object.assign(document.createElement("slot"), {
+ name: this.slotName,
+ });
+ return this.slot;
+ }
+
+ return noChange;
}
disconnected() {
- if (this.#sentinel?.parentNode && this.#host?.isConnected) {
- // The content being rendered this way, with the `render()` *function*, has its own Lit
- // VDOM comment nodes in the HTML unrelated to the `host` context. Rendering `nothing`
- // here ensures that any children of the lightDOM component receive clean-up signals and
- // correctly disconnect (including listeners, etc.) from the current display as well.
- // This is what lets us receive other DirectiveResults as template content.
-
- render(nothing, this.#sentinel.parentNode as HTMLElement, {
- renderBefore: this.#sentinel,
+ if (this.sentinel?.parentNode && this.host?.isConnected) {
+ // The node that contains the directive has been disconnected, *not* the host. We need
+ // to clean up the associated lightDOM element.
+ render(nothing, this.sentinel.parentNode as HTMLElement, {
+ renderBefore: this.sentinel,
});
+ this.sentinel.remove();
+ this.sentinel = null;
+ this.rootPart = null;
+ return;
}
- this.#rootPart?.setConnected(false);
+
+ // The host has been disconnected. Inform any child components.
+ this.rootPart?.setConnected(false);
}
reconnected() {
- this.#rootPart?.setConnected(true);
+ this.rootPart?.setConnected(true);
}
}
diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts
index 64789bc2653e..a24e8382b6c2 100644
--- a/web/src/flow/FlowExecutor.ts
+++ b/web/src/flow/FlowExecutor.ts
@@ -43,7 +43,7 @@ import { spread } from "@open-wc/lit-helpers";
import { observed } from "@patternfly/pfe-core/decorators/observed.js";
import { match } from "ts-pattern";
-import { html } from "lit";
+import { html, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { guard } from "lit/directives/guard.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
@@ -236,13 +236,6 @@ export class FlowExecutor extends WithBrandConfig(Interface) implements StageHos
});
};
- public override connectedCallback() {
- super.connectedCallback();
- this.refresh().then(() => {
- window.dispatchEvent(new AKFlowAdvanceEvent());
- });
- }
-
//#region Render Challenge
protected async renderChallengeSpecialCases(challenge: ChallengeTypes) {
@@ -320,6 +313,13 @@ export class FlowExecutor extends WithBrandConfig(Interface) implements StageHos
}
//#endregion
+
+ public override firstUpdated(changed: PropertyValues) {
+ super.firstUpdated(changed);
+ this.refresh().then(() => {
+ window.dispatchEvent(new AKFlowAdvanceEvent());
+ });
+ }
}
declare global {
diff --git a/web/src/flow/stages/identification/IdentificationStage.ts b/web/src/flow/stages/identification/IdentificationStage.ts
index 517c8922658d..6714fa5d7f6c 100644
--- a/web/src/flow/stages/identification/IdentificationStage.ts
+++ b/web/src/flow/stages/identification/IdentificationStage.ts
@@ -96,8 +96,6 @@ export class IdentificationStage extends BaseStage<
protected passwordFieldRef = createRef();
- #form?: HTMLFormElement;
-
public defaultUserIdentification: string | null = null;
protected rememberMeController: RememberMeController | null = null;
@@ -180,10 +178,6 @@ export class IdentificationStage extends BaseStage<
//#endregion
- protected override onSubmitSuccess(): void {
- this.#form?.remove();
- }
-
protected override onSubmitFailure(): void {
this.#captcha.onFailure();
}
@@ -391,7 +385,7 @@ export class IdentificationStage extends BaseStage<
>${msg("Sign up.")}`,
- )};
+ )}
`
: nothing}
${recoveryUrl
From 3d38c5b42817873055285faf9893b9735d74eff7 Mon Sep 17 00:00:00 2001
From: Ken Sternberg
Date: Thu, 7 May 2026 15:16:44 -0700
Subject: [PATCH 09/10] Some minor cleanup of Flow
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# What
1. Rename `ak-brand-footer` to `ak-brand-links`; this brings the filename in-line with the component name.
2. Clean up the imports; the initial load does not need both ak-flow and ak-flow-executor, since the first imports the second. ak-flow does not need ak-flow-card; in fact, ak-flow-executor should not need it either, since that’s a stage thing. On the other hand, `ak-brand-links` *does* need to be in the `index.entrypoint.ts` file, since it will be needed by the Django template.
---
web/src/flow/Flow.css | 88 -------------------
web/src/flow/Flow.ts | 3 +-
web/src/flow/FlowExecutor.ts | 7 +-
.../{ak-brand-footer.ts => ak-brand-links.ts} | 0
web/src/flow/index.entrypoint.ts | 2 +-
5 files changed, 3 insertions(+), 97 deletions(-)
delete mode 100644 web/src/flow/Flow.css
rename web/src/flow/components/{ak-brand-footer.ts => ak-brand-links.ts} (100%)
diff --git a/web/src/flow/Flow.css b/web/src/flow/Flow.css
deleted file mode 100644
index 95f7ac7dea43..000000000000
--- a/web/src/flow/Flow.css
+++ /dev/null
@@ -1,88 +0,0 @@
-@import "../styles/authentik/components/Login/login.css";
-
-:host,
-ak-flow-executor.style-scope {
- display: flex;
- min-height: 100dvh;
- flex-flow: column nowrap;
-}
-
-ak-flow-inspector-button {
- position: absolute;
- inset-inline-end: var(--pf-global--spacer--md);
- inset-block-start: var(--pf-global--spacer--md);
- z-index: 100;
-}
-
-[part="locale-select"],
-[part="locale-select"].style-scope {
- --pf-global--Color--100: var(--pf-global--Color--light-100) !important;
- --ak-c-flow-executor__locale-select--Padding: var(--pf-global--spacer--md);
- --ak-c-flow-executor__locale-select--Color: var(--pf-global--Color--100);
- --ak-c-locale-select--label--Color: var(--ak-c-flow-executor__locale-select--Color);
-
- /* Compatibility mode */
- color: var(--ak-c-flow-executor__locale-select--Color);
- position: absolute;
- inset-block-start: var(--ak-c-flow-executor__locale-select--Padding);
- inset-inline-start: var(--ak-c-flow-executor__locale-select--Padding);
- font-weight: 500;
- z-index: 100;
-
- /* Slight differences in browser hover states. */
- &:has(select:hover),
- &:hover {
- --ak-c-locale-select--label--Color: var(
- --ak-c-flow-executor__locale-select--Color--hover,
- var(--ak-c-flow-executor__locale-select--Color)
- );
- --ak-c-locale-select--BackgroundColor: var(
- --ak-c-flow-executor__locale-select--BackgroundColor--hover
- );
- --ak-c-locale-select--TextDecorationColor: var(--ak-c-locale-select--label--Color);
- --ak-c-locale-select__after--Opacity: 1;
-
- --ak-c-locale-select--Color: var(--ak-c-flow-executor__locale-select--Color--hover);
-
- @media (prefers-contrast: more) {
- --ak-c-locale--select--OutlineColor: var(--pf-global--primary-color--dark-100);
- }
- }
-
- filter: var(--ak-global--BackgroundContrastFilter);
-
- grid-area: header;
-
- /* At least a third of the card cut-off is available. */
- @media (width <= 61.25rem) and (height <= 61.25rem) {
- --ak-global--BackgroundContrastFilter: none;
- --ak-c-flow-executor__locale-select--Color: var(--ak-c-login__main--Color);
-
- grid-area: main;
- }
-
- @media (width <= 61.25rem) and (height <= 61.25rem) and (not (prefers-contrast: more)) {
- --ak-c-locale-select--Opacity: 0;
-
- &:hover {
- --ak-c-locale-select--Opacity: 1;
- --ak-c-locale-select__after--Opacity: 1;
- }
- }
-
- /* Card is fully masked to mobile background. */
- @media (width <= 35rem) {
- grid-row: header;
- }
-}
-
-@media (min-width: 70rem) and (min-height: 17.5rem) {
- :host([data-layout^="sidebar"]),
- [data-layout^="sidebar"] /* Compatibility mode */ {
- --ak-global--BackgroundContrastFilter: none !important;
-
- [part="locale-select"] {
- --ak-c-flow-executor__locale-select--Color: inherit !important;
- }
- }
-}
diff --git a/web/src/flow/Flow.ts b/web/src/flow/Flow.ts
index bd0611c4a603..8f98aee77d25 100644
--- a/web/src/flow/Flow.ts
+++ b/web/src/flow/Flow.ts
@@ -1,8 +1,7 @@
import "#elements/LoadingOverlay";
import "#elements/locale/ak-locale-select";
-import "#flow/components/ak-brand-footer";
-import "#flow/components/ak-flow-card";
import "#flow/inspector/FlowInspectorButton";
+import "#flow/FlowExecutor";
import "#flow/tabs/broadcast";
import { FlowWebsocketClientController } from "./controllers/FlowWebsocketClientController";
diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts
index a24e8382b6c2..b7299c61c73a 100644
--- a/web/src/flow/FlowExecutor.ts
+++ b/web/src/flow/FlowExecutor.ts
@@ -1,8 +1,4 @@
-import "#elements/LoadingOverlay";
-import "#elements/locale/ak-locale-select";
-import "#flow/components/ak-brand-footer";
-import "#flow/components/ak-flow-card";
-import "#flow/inspector/FlowInspectorButton";
+import "#flow/stages/FlowErrorStage";
import "#flow/tabs/broadcast";
import { FlowIframeMessageController } from "./controllers/FlowIframeMessageController";
@@ -79,7 +75,6 @@ type ChallengeProps = LitPropertyRecord, o
* @part footer - The footer container.
* @part locale-select - The locale select component.
* @part branding - The branding element, used for the background image in some layouts.
- * @part loading-overlay - The loading overlay element.
* @part challenge-additional-actions - Container in stages which have additional actions.
* @part challenge-footer-band - Container for the stage footer, used for additional actions in some stages.
* @part locale-select-label - The label of the locale select component.
diff --git a/web/src/flow/components/ak-brand-footer.ts b/web/src/flow/components/ak-brand-links.ts
similarity index 100%
rename from web/src/flow/components/ak-brand-footer.ts
rename to web/src/flow/components/ak-brand-links.ts
diff --git a/web/src/flow/index.entrypoint.ts b/web/src/flow/index.entrypoint.ts
index 8faee3a414c7..45390cbc8593 100644
--- a/web/src/flow/index.entrypoint.ts
+++ b/web/src/flow/index.entrypoint.ts
@@ -1,7 +1,7 @@
import "#elements/messages/MessageContainer";
import "#elements/ak-drawer/ak-drawer";
import "#flow/Flow";
-import "#flow/FlowExecutor";
+import "#flow/components/ak-brand-links";
// Statically import some stages to speed up load speed
import "#flow/stages/access_denied/AccessDeniedStage";
// Import webauthn-related stages to prevent issues on safari
From 8a1563c8058e4625365711e2409c7a2f158dce82 Mon Sep 17 00:00:00 2001
From: Ken Sternberg
Date: Thu, 7 May 2026 15:41:54 -0700
Subject: [PATCH 10/10] Add the `part` detail to the overlay, so that it can be
addressed by customizing CSS.
Updated the documenting comment to show the changes and call out the use of `light()` as an
idiom.
\# What
\# Why
\# How
\# Designs
\# Test Steps
\# Other Notes
---
web/src/flow/Flow.ts | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/web/src/flow/Flow.ts b/web/src/flow/Flow.ts
index 8f98aee77d25..446f119cac2c 100644
--- a/web/src/flow/Flow.ts
+++ b/web/src/flow/Flow.ts
@@ -42,24 +42,35 @@ const isContextualFlowInfo = (v: unknown): v is ContextualFlowInfo =>
typeof v === "object" && v !== null;
/**
- * An executor for authentik flows.
+ * The application shell for authentik flows and the Flow Executor.
*
- * @attr {string} slug - The slug of the flow to execute.
- * @prop {ChallengeTypes | null} challenge - The current challenge to render.
+ * Provides the decorations and features that go around the executor: background, layout, locale
+ * selector, flow inspector button, headers, footers, and the iframe if provided.
+ *
+ * @attr {string} slug - The slug of the flow to execute. Prop-drilled to the executor.
+ * @attr {FlowLayoutEnum} data-layout - Page layout variant. Defaults to `globalAK().flow.layout` or
+ * just `stacked`
+ *
+ * @slot footer - The page-level footer content. Currently filled by `ak-brand-links`.
*
* @part main - The main container for the flow content.
+ * @part flow-executor - Wrapper around ak-flow-executor
* @part content - The container for the stage content.
* @part content-iframe - The iframe element when using a frame background layout.
* @part footer - The footer container.
* @part locale-select - The locale select component.
* @part branding - The branding element, used for the background image in some layouts.
* @part loading-overlay - The loading overlay element.
- * @part challenge-additional-actions - Container in stages which have additional actions.
- * @part challenge-footer-band - Container for the stage footer, used for additional actions in some stages.
* @part locale-select-label - The label of the locale select component.
* @part locale-select-select - The select element of the locale select component.
+ *
+ * NOTE: This is the application shell, the top-level component. From here, we invoke the
+ * flow-executor in-line in the template rendered, but use the `light()` directive to inject it into
+ * the Flow element's lightDOM, and a slot is emplaced where the flow-executor's part of the
+ * template would go. This enables password managers to traverse down into the flow and its stages
+ * without having to cross or know about shadowDOM boundaries.
+ *
*/
-
@customElement("ak-flow")
export class Flow extends WithBrandConfig(Interface) {
//#region Static
@@ -264,7 +275,9 @@ export class Flow extends WithBrandConfig(Interface) {
${this.renderHeader()}
- ${loading ? html`` : nothing}
+ ${loading
+ ? html``
+ : nothing}