Give the component what the app shell was providing

The component built and typechecked in the previous commit, but only because
nothing had rendered it. Everything Element Call needs that `src/main.tsx`
side-loads was missing from it.

Its stylesheet: only main.tsx imported index.css, so the library build emitted
CSS-module styles with every `--cpd-*` and `--font-size-*` unresolved. Split
into base.css, which both the app and the component import, and the rules that
are about owning a page, which only the app does. The split is a straight move
— comment-stripped and sorted, the old file and the two new ones differ by
exactly one line — and that line is the deliberate part: `.no-scroll-body`
becomes `body.no-scroll-body`. Element Call adds that class to whatever it
treats as its root, and since the root can now be a container, `position:
fixed` would have taken that container out of the host's layout. Pinning the
page is what it always meant.

Its translations: `initializeElementCall` called `i18n.init` with neither
resources nor a backend, so every key would have rendered as itself. English is
bundled in. The app fetches locale files from URLs its own build emits, which a
host serving the library from somewhere else could not resolve, so how a host
picks a language is left open.

And the types a host needs: `HostBridge` alone is not enough to implement
`HostBridge` — `HostRequest`, `DeviceMuteState`, `DeviceMuteRequest` and
`JoinCallData` all appear in its signatures, and `ConfigOptions` in
`initializeElementCall`'s.
This commit is contained in:
Valere
2026-09-03 16:10:26 +02:00
parent 979b521563
commit db0f6837ce
4 changed files with 250 additions and 183 deletions
+8
View File
@@ -22,3 +22,11 @@ using an iframe. */
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
/* Compound's overlay container, which holds tooltips and popovers, has to fill
the container for the elements inside it to be positioned against it. The
standalone page does the same for the container under `#root`. */
.root > [data-overlay-container] {
position: relative;
block-size: 100%;
}
+30 -1
View File
@@ -17,6 +17,18 @@ Please see LICENSE in the repository root for full details.
* host instead, or is confined to the container it is mounted in. * host instead, or is confined to the container it is mounted in.
*/ */
// The design tokens, fonts and element defaults every Element Call stylesheet
// builds on.
//
// Where these land relative to the component stylesheets is the bundler's
// choice — the standalone app puts them first, this build puts them in the
// middle — so nothing in base.css may depend on winning or losing against a
// component's own rules at equal specificity. It currently does not: what it
// declares unlayered is custom properties on Element Call's root, which
// components inherit rather than compete with, and everything from Compound
// sits in a `@layer`, which loses to unlayered rules either way.
import "../src/base.css";
import { type FC, type JSX, type ReactNode, useMemo, useState } from "react"; import { type FC, type JSX, type ReactNode, useMemo, useState } from "react";
import { type MatrixClient } from "matrix-js-sdk"; import { type MatrixClient } from "matrix-js-sdk";
import { logger } from "matrix-js-sdk/lib/logger"; import { logger } from "matrix-js-sdk/lib/logger";
@@ -26,6 +38,7 @@ import { TooltipProvider } from "@vector-im/compound-web";
import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill"; import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill";
import { shouldPolyfill as shouldPolyfillDurationFormat } from "@formatjs/intl-durationformat/should-polyfill.js"; import { shouldPolyfill as shouldPolyfillDurationFormat } from "@formatjs/intl-durationformat/should-polyfill.js";
import EN from "../locales/en/app.json";
import { ElementCallView } from "../src/ElementCallView"; import { ElementCallView } from "../src/ElementCallView";
import { ClientProvider } from "../src/ClientContext"; import { ClientProvider } from "../src/ClientContext";
import { import {
@@ -50,7 +63,17 @@ import { useTheme } from "../src/useTheme";
import { useInitial } from "../src/useInitial"; import { useInitial } from "../src/useInitial";
import styles from "./ElementCall.module.css"; import styles from "./ElementCall.module.css";
export { type HostBridge } from "../src/HostBridge"; // Everything needed to implement a HostBridge, not just the interface itself
export {
type DeviceMuteRequest,
type DeviceMuteState,
type HostBridge,
type HostRequest,
} from "../src/HostBridge";
export { type JoinCallData } from "../src/widget";
// The deployment-wide configuration, as distinct from ElementCallConfiguration
// above, which is per call
export { type ConfigOptions } from "../src/config/ConfigOptions";
/** /**
* How Element Call should behave. Everything is optional; anything left out * How Element Call should behave. Everything is optional; anything left out
@@ -102,6 +125,12 @@ export async function initializeElementCall(
contextSeparator: "|", contextSeparator: "|",
lng: "en", lng: "en",
interpolation: { escapeValue: false }, interpolation: { escapeValue: false },
// English only, bundled in. The standalone app fetches its locale files at
// runtime from URLs its own build emits, which a host serving the library
// from elsewhere could not resolve; bundling one language at least keeps
// the component self-contained. Letting a host supply the rest, or its own
// translations, is still to do.
resources: { en: { app: EN } },
}); });
} }
+203
View File
@@ -0,0 +1,203 @@
/*
Copyright 2021-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.
*/
/* The styles Element Call needs wherever it is shown: the design tokens, fonts
and element defaults its own stylesheets build on top of.
Split out from index.css so that Element Call embedded in a host application
can have these without also being given the standalone page's layout, which
would style the host's own document. What remains here does still reach outside
Element Call's container — normalize.css and the typography below use bare
element selectors, and the custom properties are declared on `:root` — so a
host gets those too. Narrowing them needs a real host to check against, so it
waits for the Element Web integration rather than being guessed at here.
Nothing here should depend on where it lands relative to Element Call's
component stylesheets: the bundler decides that, and it decides differently for
the app and for the component build. */
@layer normalize, compound-legacy, compound;
@import url("@fontsource/inter/400.css");
@import url("@fontsource/inter/500.css");
@import url("@fontsource/inter/600.css");
@import url("@fontsource/inter/700.css");
@import url("@fontsource/inconsolata/400.css");
@import url("@fontsource/inconsolata/700.css");
@import url("normalize.css/normalize.css") layer(normalize);
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
@import url("@vector-im/compound-web/dist/style.css") layer(compound.components);
:root {
--font-scale: 1;
--font-size-micro: calc(10px * var(--font-scale));
--font-size-caption: calc(12px * var(--font-scale));
--font-size-body: calc(15px * var(--font-scale));
--font-size-subtitle: calc(18px * var(--font-scale));
--font-size-title: calc(24px * var(--font-scale));
--font-size-headline: calc(32px * var(--font-scale));
--cpd-color-border-accent: var(--cpd-color-green-800);
/* The distance to inset non-full-width content from the edge of the window
along the inline axis. This ramps up from 16px for typical mobile windows, to
96px for typical desktop windows, and accounts for the safe area. */
--content-inset-left: calc(
env(safe-area-inset-left) +
min(
var(--cpd-space-24x),
max(var(--cpd-space-4x), calc((100vw - 900px) / 3))
)
);
--content-inset-right: calc(
env(safe-area-inset-right) +
min(
var(--cpd-space-24x),
max(var(--cpd-space-4x), calc((100vw - 900px) / 3))
)
);
--small-drop-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15);
--big-drop-shadow: 0px 0px 24px 0px #1b1d221a;
--subtle-drop-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
--call-view-overlay-layer: 1;
--call-view-header-footer-layer: 2;
}
:root,
[class*="cpd-theme-"] {
--video-tile-background: var(--cpd-color-bg-subtle-secondary);
}
.cpd-theme-dark {
--cpd-color-border-accent: var(--cpd-color-green-1100);
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
--stopgap-background-85: rgba(16, 19, 23, 0.85);
}
@media (min-height: 330px) {
[data-element-call-root][data-background="gradient"]::before {
content: "";
position: fixed;
/* Chromium abruptly fades our images to fully transparent at the edge of
the element. If we just make the element a little bigger than the viewport,
this is no longer visible. */
inset: -20px;
background-image: url("graphics/mobile-gradient.png");
background-size: 1400px 305px;
background-position: bottom;
background-repeat: no-repeat;
}
[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;
}
}
/* We use this to not render the page at all until we know the theme.*/
.no-theme {
opacity: 0;
}
/* 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. */
[data-element-call-root][data-platform="android"] {
--cpd-font-family-sans: "Roboto", "Noto", "Inter", sans-serif;
}
[data-element-call-root][data-platform="ios"] {
--cpd-font-family-sans:
-apple-system, BlinkMacSystemFont, "Inter", sans-serif;
}
@layer compound-legacy {
h1,
h2,
h3,
h4,
h5,
h6,
p,
a {
margin-top: 0;
}
/* Headline Semi Bold */
h1 {
font-weight: 600;
font-size: var(--font-size-headline);
}
/* Title */
h2 {
font-weight: 600;
font-size: var(--font-size-title);
}
/* Subtitle */
h3 {
font-weight: 600;
font-size: var(--font-size-subtitle);
}
/* Body Semi Bold */
h4 {
font-weight: 600;
font-size: var(--font-size-body);
}
h1,
h2,
h3 {
line-height: 1.2;
}
/* Body */
p {
font-size: var(--font-size-body);
line-height: var(--font-size-title);
}
hr {
width: calc(100% - 24px);
border: none;
border-top: 1px solid var(--cpd-color-border-interactive-secondary);
color: var(--cpd-color-border-interactive-secondary);
overflow: visible;
text-align: center;
height: 5px;
font-weight: 600;
font-size: var(--font-size-body);
line-height: 24px;
margin: 0 12px;
}
summary {
font-size: var(--font-size-body);
}
details > :not(summary) {
margin-left: var(--font-size-body);
}
details[open] > summary {
margin-bottom: var(--font-size-body);
}
}
/* normalize.css sets the focus rings on buttons in Firefox to an unusual custom
outline, which is inconsistent with our other components and is not sufficiently
visible to be accessible. This resets it back to 'auto'. */
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: auto;
}
+9 -182
View File
@@ -5,64 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details. Please see LICENSE in the repository root for full details.
*/ */
@layer normalize, compound-legacy, compound; /* Styles for Element Call as a page of its own. The parts that apply wherever
Element Call is shown live in base.css; these are about owning the document,
and are not loaded when a host embeds Element Call as a component. */
@import url("@fontsource/inter/400.css"); @import url("./base.css");
@import url("@fontsource/inter/500.css");
@import url("@fontsource/inter/600.css");
@import url("@fontsource/inter/700.css");
@import url("@fontsource/inconsolata/400.css");
@import url("@fontsource/inconsolata/700.css");
@import url("normalize.css/normalize.css") layer(normalize);
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
@import url("@vector-im/compound-web/dist/style.css") layer(compound.components);
:root {
--font-scale: 1;
--font-size-micro: calc(10px * var(--font-scale));
--font-size-caption: calc(12px * var(--font-scale));
--font-size-body: calc(15px * var(--font-scale));
--font-size-subtitle: calc(18px * var(--font-scale));
--font-size-title: calc(24px * var(--font-scale));
--font-size-headline: calc(32px * var(--font-scale));
--cpd-color-border-accent: var(--cpd-color-green-800);
/* The distance to inset non-full-width content from the edge of the window
along the inline axis. This ramps up from 16px for typical mobile windows, to
96px for typical desktop windows, and accounts for the safe area. */
--content-inset-left: calc(
env(safe-area-inset-left) +
min(
var(--cpd-space-24x),
max(var(--cpd-space-4x), calc((100vw - 900px) / 3))
)
);
--content-inset-right: calc(
env(safe-area-inset-right) +
min(
var(--cpd-space-24x),
max(var(--cpd-space-4x), calc((100vw - 900px) / 3))
)
);
--small-drop-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15);
--big-drop-shadow: 0px 0px 24px 0px #1b1d221a;
--subtle-drop-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
--call-view-overlay-layer: 1;
--call-view-header-footer-layer: 2;
}
:root,
[class*="cpd-theme-"] {
--video-tile-background: var(--cpd-color-bg-subtle-secondary);
}
.cpd-theme-dark {
--cpd-color-border-accent: var(--cpd-color-green-1100);
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
--stopgap-background-85: rgba(16, 19, 23, 0.85);
}
body { body {
background-color: var(--cpd-color-bg-canvas-default); background-color: var(--cpd-color-bg-canvas-default);
@@ -74,39 +21,16 @@ body {
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
@media (min-height: 330px) {
[data-element-call-root][data-background="gradient"]::before {
content: "";
position: fixed;
/* Chromium abruptly fades our images to fully transparent at the edge of
the element. If we just make the element a little bigger than the viewport,
this is no longer visible. */
inset: -20px;
background-image: url("graphics/mobile-gradient.png");
background-size: 1400px 305px;
background-position: bottom;
background-repeat: no-repeat;
}
[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;
}
}
/* This prohibits the view to scroll for pages smaller than 122px in width /* This prohibits the view to scroll for pages smaller than 122px in width
we use this for mobile pip webviews */ we use this for mobile pip webviews. Element Call adds this class to whatever
.no-scroll-body { it treats as its root, but it is only ever the page that should be pinned like
this — done to a container inside a host application it would take that
container out of the host's layout — so the selector says so. */
body.no-scroll-body {
position: fixed; position: fixed;
width: 100%; width: 100%;
} }
/* We use this to not render the page at all until we know the theme.*/
.no-theme {
opacity: 0;
}
html, html,
body, body,
#root { #root {
@@ -123,104 +47,7 @@ body,
isolation: isolate; isolation: isolate;
} }
/* 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. */
[data-element-call-root][data-platform="android"] {
--cpd-font-family-sans: "Roboto", "Noto", "Inter", sans-serif;
}
[data-element-call-root][data-platform="ios"] {
--cpd-font-family-sans:
-apple-system, BlinkMacSystemFont, "Inter", sans-serif;
}
@layer compound-legacy {
h1,
h2,
h3,
h4,
h5,
h6,
p,
a {
margin-top: 0;
}
/* Headline Semi Bold */
h1 {
font-weight: 600;
font-size: var(--font-size-headline);
}
/* Title */
h2 {
font-weight: 600;
font-size: var(--font-size-title);
}
/* Subtitle */
h3 {
font-weight: 600;
font-size: var(--font-size-subtitle);
}
/* Body Semi Bold */
h4 {
font-weight: 600;
font-size: var(--font-size-body);
}
h1,
h2,
h3 {
line-height: 1.2;
}
/* Body */
p {
font-size: var(--font-size-body);
line-height: var(--font-size-title);
}
hr {
width: calc(100% - 24px);
border: none;
border-top: 1px solid var(--cpd-color-border-interactive-secondary);
color: var(--cpd-color-border-interactive-secondary);
overflow: visible;
text-align: center;
height: 5px;
font-weight: 600;
font-size: var(--font-size-body);
line-height: 24px;
margin: 0 12px;
}
summary {
font-size: var(--font-size-body);
}
details > :not(summary) {
margin-left: var(--font-size-body);
}
details[open] > summary {
margin-bottom: var(--font-size-body);
}
}
#root > [data-overlay-container] { #root > [data-overlay-container] {
position: relative; position: relative;
height: 100%; height: 100%;
} }
/* normalize.css sets the focus rings on buttons in Firefox to an unusual custom
outline, which is inconsistent with our other components and is not sufficiently
visible to be accessible. This resets it back to 'auto'. */
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: auto;
}