mirror of
https://github.com/element-hq/element-call.git
synced 2026-09-14 19:35:50 +00:00
Six selectors named `body` directly — the gradient backdrop and the platform font overrides in index.css, and the iOS adjustments in AppBar.module.css and Modal.module.css — so they only applied when Element Call owned the page. A host mounting it into a container would have got an interface decorated correctly and styled incorrectly, with nothing to show that anything was wrong. Mark the root element with data-element-call-root and match on that instead. Scoping this way keeps the selectors more specific than they were, rather than less: widening them to a bare [data-platform=…] would have dropped specificity from (0,1,1) to (0,1,0) and changed which rules win. The platform attribute moves with them, from the initializer's write onto document.body to a layout effect on the root, alongside the theme — so it still lands before anything is painted. No visual change while Element Call owns the page: the root is the body, which now carries the attribute, so every rewritten selector matches the element it always did.
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
/*
|
|
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 { useUrlParams } from "./UrlParams";
|
|
import { useRootElement } from "./RootElementContext";
|
|
import { platform } from "./Platform";
|
|
import { useHostBridge } from "./HostBridge";
|
|
|
|
export const useTheme = (): void => {
|
|
const rootElement = useRootElement();
|
|
const hostBridge = useHostBridge();
|
|
const { theme } = useUrlParams();
|
|
const [requestedTheme, setRequestedTheme] = useState(theme);
|
|
const previousTheme = useRef<string | null>(rootElement.classList.item(0));
|
|
|
|
useEffect(() => {
|
|
const subscription = hostBridge.themeChange$.subscribe(
|
|
({ data, reply }) => {
|
|
if (typeof data.name === "string") setRequestedTheme(data.name);
|
|
reply();
|
|
},
|
|
);
|
|
return (): void => subscription.unsubscribe();
|
|
}, [hostBridge]);
|
|
|
|
// Mark the element as Element Call's root and record the platform on it, so
|
|
// that the stylesheets can find both without naming `body`. A layout effect,
|
|
// like the theme below, so that it lands before anything is painted.
|
|
useLayoutEffect(() => {
|
|
rootElement.setAttribute("data-element-call-root", "");
|
|
rootElement.setAttribute("data-platform", platform);
|
|
}, [rootElement]);
|
|
|
|
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) {
|
|
rootElement.classList.remove(
|
|
"cpd-theme-light",
|
|
"cpd-theme-dark",
|
|
"cpd-theme-light-hc",
|
|
"cpd-theme-dark-hc",
|
|
);
|
|
rootElement.classList.add(themeString);
|
|
previousTheme.current = themeString;
|
|
}
|
|
rootElement.classList.remove("no-theme");
|
|
}, [previousTheme, requestedTheme, rootElement]);
|
|
};
|