diff --git a/web/src/elements/Interface.ts b/web/src/elements/Interface.ts index d74e0ca94f..481918dd57 100644 --- a/web/src/elements/Interface.ts +++ b/web/src/elements/Interface.ts @@ -14,6 +14,7 @@ import { ModalOrchestrationController } from "#elements/controllers/ModalOrchest import { ReactiveContextController } from "#elements/controllers/ReactiveContextController"; import { BrandingContext } from "#elements/mixins/branding"; import { AuthentikConfigContext } from "#elements/mixins/config"; +import { applyScrollbarClass } from "#elements/utils/scrollbars"; import { ConsoleLogger, Logger } from "#logger/browser"; @@ -43,11 +44,12 @@ export abstract class Interface extends AKElement { constructor() { super(); - this.logger = ConsoleLogger.prefix(this.tagName.toLowerCase()); + this.logger = ConsoleLogger.prefix(this.localName); const { config, brand, locale } = globalAK(); createUIThemeEffect(applyDocumentTheme); + applyScrollbarClass(this.ownerDocument); this.addController(new LocaleContextController(this, locale)); this.addController(new ConfigContextController(this, config), AuthentikConfigContext); diff --git a/web/src/elements/utils/isVisible.ts b/web/src/elements/utils/isVisible.ts deleted file mode 100644 index b2b78ea0fa..0000000000 --- a/web/src/elements/utils/isVisible.ts +++ /dev/null @@ -1,22 +0,0 @@ -const isStyledVisible = ({ visibility, display }: CSSStyleDeclaration) => - visibility !== "hidden" && display !== "none"; - -const isDisplayContents = ({ display }: CSSStyleDeclaration) => display === "contents"; - -function computedStyleIsVisible(element: HTMLElement) { - const computedStyle = window.getComputedStyle(element); - return ( - isStyledVisible(computedStyle) && - (isDisplayContents(computedStyle) || - !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)) - ); -} - -export function isVisible(element: HTMLElement) { - return ( - element && - element.isConnected && - isStyledVisible(element.style) && - computedStyleIsVisible(element) - ); -} diff --git a/web/src/elements/utils/scrollbars.ts b/web/src/elements/utils/scrollbars.ts new file mode 100644 index 0000000000..c2270e3e09 --- /dev/null +++ b/web/src/elements/utils/scrollbars.ts @@ -0,0 +1,47 @@ +/** + * @file Scrollbar utilities. + */ + +/** + * @returns The width of the scrollbar in pixels, or 0 if the browser uses overlay scrollbars. + */ +export function measureScrollbarWidth(container: HTMLElement = document.body): number { + const outer = container.ownerDocument.createElement("div"); + + outer.style.overflow = "scroll"; + outer.style.width = "100px"; + outer.style.visibility = "hidden"; + + container.appendChild(outer); + + const width = outer.offsetWidth - outer.clientWidth; + + container.removeChild(outer); + + return width; +} + +export const ScrollbarClassName = { + Visible: "ak-m-visible-scrollbars", + Overlay: "ak-m-overlay-scrollbars", +} as const; + +export type ScrollbarClassName = (typeof ScrollbarClassName)[keyof typeof ScrollbarClassName]; + +/** + * Applies the appropriate scrollbar class to the given container element + * based on whether the browser uses visible or overlay scrollbars. + * + * @param ownerDocument The document to apply the scrollbar class to. + */ +export function applyScrollbarClass(ownerDocument: Document = document): void { + const scrollbarWidth = measureScrollbarWidth(ownerDocument.body); + + if (scrollbarWidth) { + ownerDocument.documentElement.classList.add(ScrollbarClassName.Visible); + ownerDocument.documentElement.classList.remove(ScrollbarClassName.Overlay); + } else { + ownerDocument.documentElement.classList.add(ScrollbarClassName.Overlay); + ownerDocument.documentElement.classList.remove(ScrollbarClassName.Visible); + } +} diff --git a/web/src/styles/authentik/base/scrollbars.css b/web/src/styles/authentik/base/scrollbars.css index ba16c7876c..e702e02b8b 100644 --- a/web/src/styles/authentik/base/scrollbars.css +++ b/web/src/styles/authentik/base/scrollbars.css @@ -89,6 +89,7 @@ html[data-theme="dark"], } @supports (scrollbar-width: thin) { + .ak-m-thin-scrollbar, .pf-c-page__main, .pf-c-nav__list, .pf-c-card__body {