Files
authentik/web/src/elements/Base.ts
Teffen Ellis c28b65a3f2 Web: Controllers cleanup (#14616)
* web: Fix issues surrounding availability of controllers during init.

web: Fix edgecase where flow does not have brand.

* web: Fix import path.

* web: Clean up mixin/controller paths.

* web: Prepare for consistent import styling.

- Prep for Storybook fixes.

* web: Update MDX types.

* web: Fix issues surrounding async imports, MDX typing, relative paths.

* web: Format. Clarify.

* web: Group module types.
2025-05-26 07:06:14 -04:00

146 lines
3.8 KiB
TypeScript

import { globalAK } from "#common/global";
import { StyleRoot, createCSSResult, createStyleSheetUnsafe } from "#common/stylesheets";
import {
$AKBase,
CSSColorSchemeValue,
ResolvedUITheme,
applyUITheme,
createUIThemeEffect,
formatColorScheme,
resolveUITheme,
} from "#common/theme";
import { localized } from "@lit/localize";
import { CSSResult, CSSResultGroup, CSSResultOrNative, LitElement } from "lit";
import { property } from "lit/decorators.js";
import { UiThemeEnum } from "@goauthentik/api";
@localized()
export class AKElement extends LitElement {
//#region Static Properties
public static styles?: Array<CSSResult | CSSModule>;
protected static override finalizeStyles(styles?: CSSResultGroup): CSSResultOrNative[] {
if (!styles) return [$AKBase];
if (!Array.isArray(styles)) return [createCSSResult(styles), $AKBase];
return [
// ---
...(styles.flat() as CSSResultOrNative[]).map(createCSSResult),
$AKBase,
];
}
//#endregion
//#region Lifecycle
constructor() {
super();
const { brand } = globalAK();
this.preferredColorScheme = formatColorScheme(brand.uiTheme);
this.activeTheme = resolveUITheme(brand?.uiTheme);
this.#customCSSStyleSheet = brand?.brandingCustomCss
? createStyleSheetUnsafe(brand.brandingCustomCss)
: null;
}
public override disconnectedCallback(): void {
this.#themeAbortController?.abort();
super.disconnectedCallback();
}
/**
* Returns the node into which the element should render.
*
* @see {LitElement.createRenderRoot} for more information.
*/
protected override createRenderRoot(): HTMLElement | DocumentFragment {
const renderRoot = super.createRenderRoot();
this.styleRoot ??= renderRoot;
return renderRoot;
}
//#endregion
//#region Properties
/**
* The resolved theme of the current element.
*
* @remarks
*
* Unlike the browser's current color scheme, this is a value that can be
* resolved to a specific theme, i.e. dark or light.
*/
@property({
attribute: "theme",
type: String,
reflect: true,
})
public activeTheme: ResolvedUITheme;
//#endregion
//#region Private Properties
/**
* The preferred color scheme used to look up the UI theme.
*/
protected readonly preferredColorScheme: CSSColorSchemeValue;
/**
* A custom CSS style sheet to apply to the element.
*/
readonly #customCSSStyleSheet: CSSStyleSheet | null;
/**
* A controller to abort theme updates, such as when the element is disconnected.
*/
#themeAbortController: AbortController | null = null;
/**
* The style root to which the theme is applied.
*/
#styleRoot?: StyleRoot;
protected set styleRoot(nextStyleRoot: StyleRoot | undefined) {
this.#themeAbortController?.abort();
this.#styleRoot = nextStyleRoot;
if (!nextStyleRoot) return;
this.#themeAbortController = new AbortController();
if (this.preferredColorScheme === "dark") {
applyUITheme(nextStyleRoot, UiThemeEnum.Dark, this.#customCSSStyleSheet);
this.activeTheme = UiThemeEnum.Dark;
} else if (this.preferredColorScheme === "auto") {
createUIThemeEffect(
(nextUITheme) => {
applyUITheme(nextStyleRoot, nextUITheme, this.#customCSSStyleSheet);
this.activeTheme = nextUITheme;
},
{
signal: this.#themeAbortController.signal,
},
);
}
}
protected get styleRoot(): StyleRoot | undefined {
return this.#styleRoot;
}
//#endregion
}