Compare commits
1 Commits
docs-event
...
safari-fol
Author | SHA1 | Date | |
---|---|---|---|
d8474cf36d |
@ -92,6 +92,9 @@ export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `CSSStyleSheet` from the given input.
|
||||
*/
|
||||
export function createStyleSheetUnsafe(input: StyleSheetInit): CSSStyleSheet {
|
||||
const result = normalizeCSSSource(input);
|
||||
if (result instanceof CSSStyleSheet) return result;
|
||||
@ -108,40 +111,81 @@ export function createStyleSheetUnsafe(input: StyleSheetInit): CSSStyleSheet {
|
||||
return result.styleSheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* A symbol to indicate that a stylesheet has been adopted by a style parent.
|
||||
*
|
||||
* @remarks
|
||||
* Safari considers stylesheet removed from the `adoptedStyleSheets` array
|
||||
* ready for garbage collection. Reuse of the stylesheet will result in tab-crash.
|
||||
*
|
||||
* Always discard the stylesheet after use.
|
||||
*/
|
||||
const StyleSheetAdoptedParent = Symbol("stylesheet-adopted");
|
||||
|
||||
/**
|
||||
* A CSS style sheet that has been adopted by a style parent.
|
||||
*/
|
||||
export interface AdoptedStyleSheet extends CSSStyleSheet {
|
||||
[StyleSheetAdoptedParent]: WeakRef<StyleSheetParent>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-predicate to determine if a given stylesheet has been adopted.
|
||||
*/
|
||||
export function isAdoptedStyleSheet(styleSheet: CSSStyleSheet): styleSheet is AdoptedStyleSheet {
|
||||
if (!(StyleSheetAdoptedParent in styleSheet)) return false;
|
||||
|
||||
return styleSheet[StyleSheetAdoptedParent] instanceof WeakRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append stylesheet(s) to the given roots.
|
||||
*
|
||||
* @see {@linkcode removeStyleSheet} to remove a stylesheet from a given roots.
|
||||
*/
|
||||
export function appendStyleSheet(
|
||||
insertions: CSSStyleSheet | Iterable<CSSStyleSheet>,
|
||||
...styleParents: StyleSheetParent[]
|
||||
styleParent: StyleSheetParent,
|
||||
...insertions: CSSStyleSheet[]
|
||||
): void {
|
||||
insertions = Array.isArray(insertions) ? insertions : [insertions];
|
||||
|
||||
for (const nextStyleSheet of insertions) {
|
||||
for (const styleParent of styleParents) {
|
||||
if (styleParent.adoptedStyleSheets.includes(nextStyleSheet)) return;
|
||||
for (const styleSheetInsertion of insertions) {
|
||||
if (isAdoptedStyleSheet(styleSheetInsertion)) {
|
||||
console.warn("Attempted to append adopted stylesheet", {
|
||||
styleSheetInsertion,
|
||||
currentParent: styleSheetInsertion[StyleSheetAdoptedParent]?.deref(),
|
||||
rules: serializeStyleSheet(styleSheetInsertion),
|
||||
});
|
||||
|
||||
styleParent.adoptedStyleSheets = [...styleParent.adoptedStyleSheets, nextStyleSheet];
|
||||
throw new TypeError("Attempted to append a previously adopted stylesheet");
|
||||
}
|
||||
|
||||
if (styleParent.adoptedStyleSheets.includes(styleSheetInsertion)) return;
|
||||
|
||||
styleParent.adoptedStyleSheets = [...styleParent.adoptedStyleSheets, styleSheetInsertion];
|
||||
|
||||
Object.assign(styleSheetInsertion, {
|
||||
[StyleSheetAdoptedParent]: new WeakRef(styleParent),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a stylesheet from the given roots, matching by referential equality.
|
||||
*
|
||||
* @see {@linkcode appendStyleSheet} to append a stylesheet to a given roots.
|
||||
*/
|
||||
export function removeStyleSheet(
|
||||
currentStyleSheet: CSSStyleSheet,
|
||||
...styleParents: StyleSheetParent[]
|
||||
styleParent: StyleSheetParent,
|
||||
...removals: CSSStyleSheet[]
|
||||
): void {
|
||||
for (const styleParent of styleParents) {
|
||||
const nextAdoptedStyleSheets = styleParent.adoptedStyleSheets.filter(
|
||||
(styleSheet) => styleSheet !== currentStyleSheet,
|
||||
);
|
||||
const nextAdoptedStyleSheets = styleParent.adoptedStyleSheets.filter(
|
||||
(styleSheet) => !removals.includes(styleSheet),
|
||||
);
|
||||
|
||||
if (nextAdoptedStyleSheets.length === styleParent.adoptedStyleSheets.length) return;
|
||||
if (nextAdoptedStyleSheets.length === styleParent.adoptedStyleSheets.length) return;
|
||||
|
||||
styleParent.adoptedStyleSheets = nextAdoptedStyleSheets;
|
||||
}
|
||||
styleParent.adoptedStyleSheets = nextAdoptedStyleSheets;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,14 @@ import {
|
||||
removeStyleSheet,
|
||||
resolveStyleSheetParent,
|
||||
} from "@goauthentik/common/stylesheets";
|
||||
import { ResolvedUITheme, createUIThemeEffect, resolveUITheme } from "@goauthentik/common/theme";
|
||||
import {
|
||||
CSSColorSchemeValue,
|
||||
ResolvedUITheme,
|
||||
UIThemeListener,
|
||||
createUIThemeEffect,
|
||||
formatColorScheme,
|
||||
resolveUITheme,
|
||||
} from "@goauthentik/common/theme";
|
||||
import { type ThemedElement } from "@goauthentik/common/theme";
|
||||
|
||||
import { localized } from "@lit/localize";
|
||||
@ -18,18 +25,15 @@ import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import OneDark from "@goauthentik/common/styles/one-dark.css";
|
||||
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
|
||||
|
||||
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||
import { UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
// Re-export the theme helpers
|
||||
export { rootInterface } from "@goauthentik/common/theme";
|
||||
|
||||
export interface AKElementInit {
|
||||
brand?: Partial<CurrentBrand>;
|
||||
styleParents?: StyleSheetParent[];
|
||||
}
|
||||
|
||||
@localized()
|
||||
export class AKElement extends LitElement implements ThemedElement {
|
||||
//#region Properties
|
||||
|
||||
/**
|
||||
* The resolved theme of the current element.
|
||||
*
|
||||
@ -45,7 +49,19 @@ export class AKElement extends LitElement implements ThemedElement {
|
||||
})
|
||||
public activeTheme: ResolvedUITheme;
|
||||
|
||||
protected static readonly DarkColorSchemeStyleSheet = createStyleSheetUnsafe(ThemeDark);
|
||||
//#endregion
|
||||
|
||||
//#region Private Properties
|
||||
|
||||
readonly #preferredColorScheme: CSSColorSchemeValue;
|
||||
|
||||
#customCSSStyleSheet: CSSStyleSheet | null;
|
||||
#darkThemeStyleSheet: CSSStyleSheet | null = null;
|
||||
#themeAbortController: AbortController | null = null;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
protected static finalizeStyles(styles?: CSSResultGroup): CSSResultOrNative[] {
|
||||
// Ensure all style sheets being passed are really style sheets.
|
||||
@ -63,63 +79,62 @@ export class AKElement extends LitElement implements ThemedElement {
|
||||
return [styles, ...baseStyles].map(createStyleSheetUnsafe);
|
||||
}
|
||||
|
||||
constructor(init?: AKElementInit) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const config = globalAK();
|
||||
const { brand = config.brand, styleParents = [] } = init || {};
|
||||
const { brand } = globalAK();
|
||||
|
||||
this.#preferredColorScheme = formatColorScheme(brand.uiTheme);
|
||||
this.activeTheme = resolveUITheme(brand?.uiTheme);
|
||||
this.#styleParents = styleParents;
|
||||
|
||||
this.#customCSSStyleSheet = brand?.brandingCustomCss
|
||||
? createStyleSheetUnsafe(brand.brandingCustomCss)
|
||||
: null;
|
||||
}
|
||||
|
||||
#styleParents: StyleSheetParent[] = [];
|
||||
#customCSSStyleSheet: CSSStyleSheet | null;
|
||||
|
||||
#themeAbortController: AbortController | null = null;
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.#themeAbortController?.abort();
|
||||
}
|
||||
|
||||
#styleRoot?: StyleSheetParent;
|
||||
|
||||
#dispatchTheme: UIThemeListener = (nextUITheme) => {
|
||||
if (!this.#styleRoot) return;
|
||||
|
||||
if (nextUITheme === UiThemeEnum.Dark) {
|
||||
this.#darkThemeStyleSheet ||= createStyleSheetUnsafe(ThemeDark);
|
||||
appendStyleSheet(this.#styleRoot, this.#darkThemeStyleSheet);
|
||||
this.activeTheme = UiThemeEnum.Dark;
|
||||
} else if (this.#darkThemeStyleSheet) {
|
||||
removeStyleSheet(this.#styleRoot, this.#darkThemeStyleSheet);
|
||||
this.#darkThemeStyleSheet = null;
|
||||
this.activeTheme = UiThemeEnum.Light;
|
||||
}
|
||||
};
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
const renderRoot = super.createRenderRoot();
|
||||
|
||||
const styleRoot = resolveStyleSheetParent(renderRoot);
|
||||
const styleParents = Array.from(
|
||||
new Set<StyleSheetParent>([styleRoot, ...this.#styleParents]),
|
||||
);
|
||||
this.#styleRoot = resolveStyleSheetParent(renderRoot);
|
||||
|
||||
if (this.#customCSSStyleSheet) {
|
||||
console.debug(`authentik/element[${this.tagName.toLowerCase()}]: Adding custom CSS`);
|
||||
|
||||
styleRoot.adoptedStyleSheets = [
|
||||
...styleRoot.adoptedStyleSheets,
|
||||
this.#customCSSStyleSheet,
|
||||
];
|
||||
appendStyleSheet(this.#styleRoot, this.#customCSSStyleSheet);
|
||||
}
|
||||
|
||||
this.#themeAbortController = new AbortController();
|
||||
|
||||
createUIThemeEffect(
|
||||
(currentUITheme) => {
|
||||
if (currentUITheme === UiThemeEnum.Dark) {
|
||||
appendStyleSheet(AKElement.DarkColorSchemeStyleSheet, ...styleParents);
|
||||
} else {
|
||||
removeStyleSheet(AKElement.DarkColorSchemeStyleSheet, ...styleParents);
|
||||
}
|
||||
this.activeTheme = currentUITheme;
|
||||
},
|
||||
{
|
||||
if (this.#preferredColorScheme === "dark") {
|
||||
this.#dispatchTheme(UiThemeEnum.Dark);
|
||||
} else if (this.#preferredColorScheme === "auto") {
|
||||
createUIThemeEffect(this.#dispatchTheme, {
|
||||
signal: this.#themeAbortController.signal,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return renderRoot;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
} from "@goauthentik/common/stylesheets";
|
||||
import { ThemedElement } from "@goauthentik/common/theme";
|
||||
import { UIConfig } from "@goauthentik/common/ui/config";
|
||||
import { AKElement, AKElementInit } from "@goauthentik/elements/Base";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { VersionContextController } from "@goauthentik/elements/Interface/VersionContextController";
|
||||
import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js";
|
||||
|
||||
@ -19,7 +19,6 @@ import { BrandContextController } from "./BrandContextController";
|
||||
import { ConfigContextController } from "./ConfigContextController";
|
||||
import { EnterpriseContextController } from "./EnterpriseContextController";
|
||||
|
||||
const brandContext = Symbol("brandContext");
|
||||
const configContext = Symbol("configContext");
|
||||
const modalController = Symbol("modalController");
|
||||
const versionContext = Symbol("versionContext");
|
||||
@ -27,8 +26,6 @@ const versionContext = Symbol("versionContext");
|
||||
export abstract class Interface extends AKElement implements ThemedElement {
|
||||
protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
|
||||
|
||||
[brandContext]: BrandContextController;
|
||||
|
||||
[configContext]: ConfigContextController;
|
||||
|
||||
[modalController]: ModalOrchestrationController;
|
||||
@ -39,19 +36,15 @@ export abstract class Interface extends AKElement implements ThemedElement {
|
||||
@state()
|
||||
public brand?: CurrentBrand;
|
||||
|
||||
constructor({ styleParents = [], ...init }: AKElementInit = {}) {
|
||||
constructor() {
|
||||
super();
|
||||
const styleParent = resolveStyleSheetParent(document);
|
||||
|
||||
super({
|
||||
...init,
|
||||
styleParents: [styleParent, ...styleParents],
|
||||
});
|
||||
|
||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||
|
||||
appendStyleSheet(Interface.PFBaseStyleSheet, styleParent);
|
||||
appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
|
||||
|
||||
this[brandContext] = new BrandContextController(this);
|
||||
this.addController(new BrandContextController(this));
|
||||
this[configContext] = new ConfigContextController(this);
|
||||
this[modalController] = new ModalOrchestrationController(this);
|
||||
}
|
||||
@ -77,8 +70,8 @@ export class AuthenticatedInterface extends Interface implements AkAuthenticated
|
||||
@state()
|
||||
public version?: Version;
|
||||
|
||||
constructor(init?: AKElementInit) {
|
||||
super(init);
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this[enterpriseContext] = new EnterpriseContextController(this);
|
||||
this[versionContext] = new VersionContextController(this);
|
||||
|
@ -89,6 +89,7 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
--ak-brand-background-color: var(
|
||||
--pf-c-page__sidebar--m-light--BackgroundColor
|
||||
);
|
||||
--host-navbar-height: var(--ak-c-page-header--height, 7.5rem);
|
||||
}
|
||||
|
||||
:host([theme="dark"]) {
|
||||
@ -105,7 +106,6 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 6rem;
|
||||
|
||||
display: grid;
|
||||
row-gap: var(--pf-global--spacer--sm);
|
||||
@ -116,6 +116,10 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
"brand toggle primary secondary"
|
||||
"brand toggle description secondary";
|
||||
|
||||
@media (min-width: 426px) {
|
||||
height: var(--host-navbar-height);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
row-gap: var(--pf-global--spacer--xs);
|
||||
|
||||
@ -161,7 +165,15 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
||||
|
||||
&.page-description {
|
||||
grid-area: description;
|
||||
padding-block-end: var(--pf-global--spacer--md);
|
||||
margin-block-end: var(--pf-global--spacer--md);
|
||||
|
||||
display: box;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
box-orient: vertical;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: 425px) {
|
||||
display: none;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
appendStyleSheet,
|
||||
assertAdoptableStyleSheetParent,
|
||||
createStyleSheetUnsafe,
|
||||
} from "@goauthentik/common/stylesheets.js";
|
||||
|
||||
import { TemplateResult, render as litRender } from "lit";
|
||||
@ -15,6 +16,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
export const render = (body: TemplateResult) => {
|
||||
assertAdoptableStyleSheetParent(document);
|
||||
|
||||
appendStyleSheet([PFBase, AKGlobal], document);
|
||||
appendStyleSheet(document, ...[PFBase, AKGlobal].map(createStyleSheetUnsafe));
|
||||
return litRender(body, document.body);
|
||||
};
|
||||
|
Reference in New Issue
Block a user