mirror of
https://github.com/element-hq/element-call.git
synced 2026-09-16 04:05:36 +00:00
Element Call writes its theme classes, background and layout attributes straight onto document.body, and portals its modals there too. That is only correct while it owns the page; embedded in a host application it has to confine itself to the container it was mounted into. Add a RootElementContext, defaulting to document.body so that the standalone and widget builds are unaffected, and point the theme classes, data-background, no-scroll-body and the fullscreen target at it. Give the Modal and Toast portals an explicit container as well. Radix and vaul both default to document.body, so without this every modal, drawer and toast would render outside the container and lose the theme and platform attributes set on it. No functional change: the root element is document.body until an embedder provides otherwise.
64 lines
2.0 KiB
TypeScript
64 lines
2.0 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 { WidgetApiToWidgetAction } from "matrix-widget-api";
|
|
import { type IThemeChangeActionRequest } from "matrix-widget-api";
|
|
|
|
import { getUrlParams } from "./UrlParams";
|
|
import { widget } from "./widget";
|
|
import { useRootElement } from "./RootElementContext";
|
|
|
|
export const useTheme = (): void => {
|
|
const rootElement = useRootElement();
|
|
const [requestedTheme, setRequestedTheme] = useState(
|
|
() => getUrlParams().theme,
|
|
);
|
|
const previousTheme = useRef<string | null>(rootElement.classList.item(0));
|
|
|
|
useEffect(() => {
|
|
if (widget) {
|
|
const onThemeChange = (
|
|
ev: CustomEvent<IThemeChangeActionRequest>,
|
|
): 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) {
|
|
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]);
|
|
};
|