mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-10 21:55:19 +00:00
Find Element Call's root by attribute, not by being the body
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.
This commit is contained in:
@@ -131,7 +131,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
body[data-platform="ios"] {
|
||||
[data-element-call-root][data-platform="ios"] {
|
||||
.bar > header {
|
||||
grid-template-rows: minmax(var(--cpd-space-11x), auto) var(--cpd-space-4x);
|
||||
grid-template-areas: "primaryButton title secondaryButton";
|
||||
|
||||
@@ -48,7 +48,7 @@ Please see LICENSE in the repository root for full details.
|
||||
--handle-inset-block-end: var(--cpd-space-4x);
|
||||
}
|
||||
|
||||
body[data-platform="ios"] .drawer {
|
||||
[data-element-call-root][data-platform="ios"] .drawer {
|
||||
--border-radius: 10px;
|
||||
--handle-block-size: 5px;
|
||||
--handle-inline-size: 36px;
|
||||
|
||||
@@ -16,17 +16,18 @@ import { createContext, use } from "react";
|
||||
* that when embedded in a host application it becomes the container the host
|
||||
* mounted it into, so that Element Call does not reach outside its own subtree.
|
||||
*
|
||||
* That intent is not yet achievable: several selectors still name `body`
|
||||
* directly — `body[data-background="gradient"]` and `body[data-platform=…]` in
|
||||
* `index.css`, and `body[data-platform="ios"]` in `AppBar.module.css` and
|
||||
* `Modal.module.css` — and `Initializer.initBeforeReact` writes
|
||||
* `data-platform` straight onto the body. So anything other than the body will
|
||||
* be decorated correctly and styled incorrectly, silently. Until those are
|
||||
* scoped, treat this as preparation rather than a working seam.
|
||||
* The stylesheets find this element by its `data-element-call-root` attribute,
|
||||
* which {@link useTheme} sets along with the platform and theme, so they no
|
||||
* longer depend on it being the body.
|
||||
*
|
||||
* What remains body-specific is the standalone page's own furniture: the
|
||||
* `body` rule in `index.css` still sets the page background and margin, and
|
||||
* `index.html` starts the body hidden with `no-theme` until the theme lands.
|
||||
* Neither applies when a host mounts Element Call into a container of its own.
|
||||
*/
|
||||
// No provider is exported yet: nothing supplies a root element, so every
|
||||
// consumer falls back to the document body. M1 adds one along with the
|
||||
// component that mounts Element Call into a container.
|
||||
// consumer falls back to the document body. One arrives with the entry point
|
||||
// that mounts Element Call into a container.
|
||||
const RootElementContext = createContext<HTMLElement | null>(null);
|
||||
|
||||
/**
|
||||
|
||||
+4
-4
@@ -75,7 +75,7 @@ body {
|
||||
}
|
||||
|
||||
@media (min-height: 330px) {
|
||||
body[data-background="gradient"]::before {
|
||||
[data-element-call-root][data-background="gradient"]::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
/* Chromium abruptly fades our images to fully transparent at the edge of
|
||||
@@ -88,7 +88,7 @@ body {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[data-background="gradient"][data-platform="desktop"]::before {
|
||||
[data-element-call-root][data-background="gradient"][data-platform="desktop"]::before {
|
||||
background-image: url("graphics/desktop-gradient.png");
|
||||
background-size: max(1440px, 100vw) max(1440px, 100vh);
|
||||
background-position: center;
|
||||
@@ -126,11 +126,11 @@ body,
|
||||
/* On Android and iOS, prefer native system fonts. The global.css file of
|
||||
Compound Web is where these variables ultimately get consumed to set the page's
|
||||
font-family. */
|
||||
body[data-platform="android"] {
|
||||
[data-element-call-root][data-platform="android"] {
|
||||
--cpd-font-family-sans: "Roboto", "Noto", "Inter", sans-serif;
|
||||
}
|
||||
|
||||
body[data-platform="ios"] {
|
||||
[data-element-call-root][data-platform="ios"] {
|
||||
--cpd-font-family-sans:
|
||||
-apple-system, BlinkMacSystemFont, "Inter", sans-serif;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
import { getUrlParams } from "./UrlParams";
|
||||
import { Config } from "./config/Config";
|
||||
import { seedSettingsFromConfig } from "./settings/settings";
|
||||
import { platform } from "./Platform";
|
||||
import { isFailure } from "./utils/fetch";
|
||||
import { initializeWidget, type WidgetHelpers } from "./widget";
|
||||
import { enableExtendedLivekitLogs } from "./settings/settings.ts";
|
||||
@@ -229,9 +228,6 @@ export class Initializer {
|
||||
);
|
||||
}
|
||||
|
||||
// Add the platform to the DOM, so CSS can query it
|
||||
document.body.setAttribute("data-platform", platform);
|
||||
|
||||
// livekit logging configuration
|
||||
setLKLogExtension((level, msg, context) => {
|
||||
// we pass a synthetic logger name of "livekit" to the rageshake to make it easier to read
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "vitest";
|
||||
|
||||
import { useTheme } from "./useTheme";
|
||||
import { platform } from "./Platform";
|
||||
import { useUrlParams } from "./UrlParams";
|
||||
import {
|
||||
type HostBridge,
|
||||
@@ -88,6 +89,20 @@ describe("useTheme", () => {
|
||||
expect(originalClassList.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("marks the element as Element Call's root, for the stylesheets", () => {
|
||||
renderHook(() => useTheme(), { wrapper });
|
||||
|
||||
// The stylesheets find the root by this rather than by naming `body`, so
|
||||
// that they still apply when Element Call is mounted into a container
|
||||
expect(document.body.hasAttribute("data-element-call-root")).toBe(true);
|
||||
});
|
||||
|
||||
test("records the platform on the root, for the stylesheets", () => {
|
||||
renderHook(() => useTheme(), { wrapper });
|
||||
|
||||
expect(document.body.getAttribute("data-platform")).toBe(platform);
|
||||
});
|
||||
|
||||
test("theme changes in response to host requests", () => {
|
||||
renderHook(() => useTheme(), { wrapper });
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 => {
|
||||
@@ -28,6 +29,14 @@ export const useTheme = (): void => {
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user