/* Copyright 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { WidgetApiToWidgetAction } from "matrix-widget-api"; import { type IThemeChangeActionRequest } from "matrix-widget-api"; import { logger } from "matrix-js-sdk/lib/logger"; import { getUrlParams } from "./UrlParams"; import { widget } from "./widget"; import { lotusFlag } from "./lotus/lotusWidget"; export const useTheme = (): void => { const [requestedTheme, setRequestedTheme] = useState( () => getUrlParams().theme, ); const previousTheme = useRef(document.body.classList.item(0)); useEffect(() => { if (widget) { const onThemeChange = ( ev: CustomEvent, ): void => { ev.preventDefault(); if ("name" in ev.detail.data && typeof ev.detail.data.name === "string") setRequestedTheme(ev.detail.data.name); widget!.api.transport.reply(ev.detail, {}); }; widget.lazyActions.on(WidgetApiToWidgetAction.ThemeChange, onThemeChange); return (): void => { widget!.lazyActions.off( WidgetApiToWidgetAction.ThemeChange, onThemeChange, ); }; } }, []); useLayoutEffect(() => { // If no theme has been explicitly requested we default to dark const theme = requestedTheme?.includes("light") ? "light" : "dark"; const themeHighContrast = requestedTheme?.includes("high-contrast") ? "-hc" : ""; const themeString = "cpd-theme-" + theme + themeHighContrast; if (themeString !== previousTheme.current) { document.body.classList.remove( "cpd-theme-light", "cpd-theme-dark", "cpd-theme-light-hc", "cpd-theme-dark-hc", ); document.body.classList.add(themeString); previousTheme.current = themeString; } document.body.classList.remove("no-theme"); // [lotus #5] Native theming hooks, opted in by the host via URL flags, so // it no longer has to inject CSS into the iframe after load. const lotusTransparent = lotusFlag("lotusTransparent"); const lotusTheme = lotusFlag("lotusTheme"); if (lotusTransparent) document.body.classList.add("lotus-transparent"); // [lotus #21] The two flags are only meaningful together: a transparent // canvas with no matching theme leaves text/icon colours computed against // the default dark canvas token while the real pixels behind them are // whatever the host painted. If a host sets lotusTransparent without // lotusTheme, warn and apply lotusTheme anyway rather than silently // risking unreadable UI. if (lotusTransparent && !lotusTheme) { logger.warn( "[lotus] lotusTransparent is set without lotusTheme; applying " + "lotusTheme anyway since the two flags are only meaningful together.", ); } if (lotusTheme || lotusTransparent) { document.body.classList.add("lotus-theme"); // [cinny #43] The frame's root colour scheme must match the host's, or // the browser paints an opaque backdrop behind the transparent frame. // (The host used to inject `:root { color-scheme }` for this.) document.documentElement.style.colorScheme = theme; } // [cinny #43] The host draws its own call bar: hide EC's footer. Replaces // the host's injected styles. Where the host can't start a share for us, // LotusFrameScreenshare shows EC's screenshare button on its own. if (lotusFlag("lotusHostControls")) document.body.classList.add("lotus-host-controls"); }, [previousTheme, requestedTheme]); };