Compare commits
	
		
			1 Commits
		
	
	
		
			version-20
			...
			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
	