Say "component" for the component

"Embedded" already means something here: the embedded package is the
widget build. Where a comment meant Element Call running as a React
component inside a host application, it now says so, and the params it
starts from are `componentProperties`. "Host" stays the word for the
application on the other side, whether that is a widget container or an
application rendering the component.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Timo K.
2026-09-08 15:39:00 +02:00
co-authored by Claude Fable 5.1
parent f70fba1dd6
commit 3dc7cfe10b
14 changed files with 48 additions and 47 deletions
+2 -2
View File
@@ -17,8 +17,8 @@ they are meant to cover the page — resolve against the viewport and appear in
the middle of the host's window rather than in the middle of the call.
The clipping cuts both ways: a menu near the edge of a small container is
trimmed rather than overflowing into the host. That is the trade being an
embedded component makes. */
trimmed rather than overflowing into the host. That is the trade being a
component rather than a page makes. */
.root {
display: flex;
flex-direction: column;
+1 -1
View File
@@ -41,7 +41,7 @@ const IS_ROOT = `:where(${ROOT_SELECTOR})`;
*
* As a page of its own, Element Call can style the document: normalize.css and
* Compound speak of `html`, `body` and bare elements, and the design tokens are
* declared on `:root`. Embedded in a host, all of that would land on the host's
* declared on `:root`. As a component, all of that would land on the host's
* document too. This rewrites every selector so that it matches only the root
* or its descendants:
*
+7 -7
View File
@@ -83,7 +83,7 @@ interface LogEntry {
}
/**
* One embedded Element Call, with the controls a host would have over it: the
* One Element Call component, with the controls a host would have over it: the
* requests it can make of Element Call, and the ability to take it off screen
* altogether.
*/
@@ -198,9 +198,9 @@ const HostChrome: FC = (): ReactNode => (
);
/**
* A dialog of the host's own, over the top of the calls. Element Call embedded
* in a host has to sit underneath this — being unable to is one of the reasons
* for embedding it rather than putting it in an iframe.
* A dialog of the host's own, over the top of the calls. Element Call as a
* component has to sit underneath this — being unable to is one of the reasons
* for a component rather than an iframe.
*/
const HostDialog: FC<{ onClose: () => void }> = ({ onClose }): ReactNode => (
<div className={styles.dialogScrim}>
@@ -213,7 +213,7 @@ const HostDialog: FC<{ onClose: () => void }> = ({ onClose }): ReactNode => (
);
/**
* Stands in for a host application embedding Element Call: it owns the Matrix
* Stands in for a host application using the Element Call component: it owns the Matrix
* clients, the page and the space each call is given, and reaches Element Call
* only through the component's public interface.
*
@@ -299,8 +299,8 @@ export const Harness: FC = (): ReactNode => {
<form className={styles.credentials} onSubmit={start}>
<h1>Element Call component harness</h1>
<p>
Signs in twice and shows Element Call embedded twice, in a page that
is not Element Call&apos;s own.
Signs in twice and shows the Element Call component twice, in a page
that is not Element Call&apos;s own.
</p>
{field("homeserver", "Homeserver")}
{field("username", "Username")}
+2 -2
View File
@@ -31,8 +31,8 @@ import { useInitial } from "../src/useInitial";
import { useLatest } from "../src/useLatest";
/**
* What Element Call tells the application embedding it. Everything is
* optional: a host implements what it wants to hear about.
* What Element Call tells the application hosting it as a component.
* Everything is optional: a host implements what it wants to hear about.
*
* Compared by nothing — Element Call always calls whichever one it was most
* recently given, so this may be written inline.
+4 -4
View File
@@ -58,7 +58,7 @@ import { HostBridgeProvider } from "../src/HostBridge";
import { RootElementProvider } from "../src/RootElementContext";
import {
configurationForIntent,
hostedProperties,
componentProperties,
type UrlParams,
UrlParamsProvider,
UserIntent,
@@ -209,7 +209,7 @@ export const ElementCall: FC<ElementCallProps> = ({
const stableConfig = useStableValue(config);
const params = useMemo(
(): UrlParams => ({
...hostedProperties,
...componentProperties,
roomId,
...configurationForIntent(intent),
...stableConfig,
@@ -249,8 +249,8 @@ export const ElementCall: FC<ElementCallProps> = ({
<I18nextProvider i18n={i18n}>
<HostBridgeProvider value={hostBridge}>
<UrlParamsProvider value={params}>
{/* Element Call's own navigation stays in memory, so that being
embedded cannot disturb the host's URL. */}
{/* Element Call's own navigation stays in memory, so that the
component cannot disturb the host's URL. */}
<MemoryRouter>
<div ref={setContainer} className={styles.root}>
{container !== null &&
+3 -3
View File
@@ -136,9 +136,9 @@ interface Props {
/**
* The client Element Call should use.
*
* When a host embeds Element Call it already has a client, and owns the
* user's session; supplying it here means Element Call neither authenticates
* anyone nor manages their session. Left out, Element Call finds a client
* An application hosting Element Call as a component already has a client,
* and owns the user's session; supplying it here means Element Call neither
* authenticates anyone nor manages their session. Left out, Element Call finds a client
* itself — from the widget API, or by restoring or creating a session of its
* own.
*/
+3 -3
View File
@@ -51,7 +51,7 @@ export interface HostRequest<Data, Reply = void> {
* Element Call's view of the application hosting it.
*
* Element Call can run as its own page, as a widget inside a Matrix client, or
* embedded directly into one. Only the last two give it a host, and each of
* as a component inside one. Only the last two give it a host, and each of
* them reaches it by a different route — so everything Element Call needs from
* whatever is hosting it goes through this interface, rather than being
* expressed in terms of the widget API.
@@ -101,8 +101,8 @@ export interface HostBridge {
/**
* Whether Element Call may offer to change the user's profile — their
* display name and avatar. Only when the account is Element Call's own,
* which is to say standalone: a widget's host and an application embedding
* Element Call both signed the user in themselves, so the profile is theirs
* which is to say standalone: a widget's host and an application hosting
* the component both signed the user in themselves, so the profile is theirs
* to manage and Element Call must not offer to edit it.
*/
readonly supportsProfileChanges: boolean;
+3 -3
View File
@@ -12,9 +12,9 @@ import { createContext, use } from "react";
*
* Element Call decorates this element with the theme, layout and background
* attributes its stylesheets key off, and portals its modals into it. When
* Element Call owns the page this is simply the document body; the intent is
* 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.
* Element Call owns the page this is simply the document body; as a component
* it is the container the host mounted it into, so that Element Call does not
* reach outside its own subtree.
*
* 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
+2 -2
View File
@@ -17,7 +17,7 @@ import {
computeUrlParams,
HeaderStyle,
getUrlParams,
hostedProperties,
componentProperties,
UserIntent,
} from "../src/UrlParams";
import { mockConfig } from "./utils/test";
@@ -433,7 +433,7 @@ describe("UrlParams", () => {
// has no URL of its own for any of this to come from
describe("hosted defaults", () => {
it("assume nothing about a session or a page", () => {
expect(hostedProperties).toMatchObject({
expect(componentProperties).toMatchObject({
// The host is not a widget host, and supplies the client itself, so
// none of the widget or session plumbing applies
isWidget: false,
+8 -6
View File
@@ -446,7 +446,8 @@ export function configurationForIntent(intent: UserIntent): UrlConfiguration {
}
/**
* The {@link UrlProperties} for Element Call embedded in a host application.
* The {@link UrlProperties} for Element Call running as a component inside a
* host application.
*
* It has no URL of its own to read these from, and it does not need most of
* them: the widget plumbing does not apply, the Matrix client and the analytics
@@ -454,7 +455,7 @@ export function configurationForIntent(intent: UserIntent): UrlConfiguration {
* the host's to state through the component's props or Element Call's own
* default.
*/
export const hostedProperties: UrlProperties = {
export const componentProperties: UrlProperties = {
widgetId: null,
parentUrl: null,
isWidget: false,
@@ -594,8 +595,9 @@ const UrlParamsContext = createContext<UrlParams | null>(null);
/**
* Supplies the parameters Element Call should run with.
*
* The standalone and widget builds derive these from the URL, but an embedder
* has no URL of its own to put them in, so it provides them directly instead.
* The standalone and widget builds derive these from the URL, but the
* component has no URL of its own to read them from, so its host provides them
* directly instead.
*
* TODO: `UrlParams` is no longer an accurate name now that these need not come
* from a URL. Renaming it touches every consumer, so it is left until the rest
@@ -615,8 +617,8 @@ export const useUrlParams = (): UrlParams =>
/**
* Derives {@link UrlParams} from the current router location.
*
* Only meaningful when Element Call owns the URL; embedders provide the params
* directly through {@link UrlParamsProvider}.
* Only meaningful when Element Call owns the URL; the component is given its
* params directly through {@link UrlParamsProvider}.
*/
export const useUrlParamsFromLocation = (): UrlParams => {
const { search, hash } = useLocation();
+3 -3
View File
@@ -8,9 +8,9 @@ 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 still speaks of the
Split out from index.css so that Element Call as a component can have these
without also being given the standalone page's layout, which would style the
host's own document. What remains here still speaks of the
document — normalize.css and the typography below use bare element selectors,
and the custom properties are declared on `:root` — which is right for the
page, and is why the component build rewrites it: there every selector is
+5 -6
View File
@@ -36,7 +36,7 @@ export class Config {
*
* Does nothing if the config has already been initialized, including by
* {@link Config.initWith}, so that the regular startup path can run unchanged
* when an embedder has already supplied the config.
* when a component host has already supplied the config.
*/
public static async init(): Promise<void> {
if (!Config.internalInstance?.initPromise) {
@@ -66,16 +66,15 @@ export class Config {
}
/**
* Initializes the config from an object supplied by the embedder, instead of
* fetching `config.json`.
* Initializes the config from an object supplied by the application hosting
* the component, instead of fetching `config.json`.
*
* {@link Config.init} derives the location of `config.json` from
* `window.location`, which only makes sense while Element Call owns the page.
* When it is embedded in a host application the host owns the configuration
* and passes it in here.
* As a component, the host owns the configuration and passes it in here.
*
* The config goes through the same validation and defaulting as a fetched
* one, so that an injected config behaves identically to a hosted one.
* one, so that a supplied config behaves identically to a fetched one.
*
* Replaces any config initialized earlier.
*/
+1 -1
View File
@@ -229,7 +229,7 @@ export interface CallViewModelOptions {
*
* Callers share this rather than picking the fields out themselves. The
* defaults on {@link CallViewModelOptions} describe a standalone Element Call,
* so a widget or embedded caller that misses one does not get an error — it
* so a widget or component caller that misses one does not get an error — it
* quietly gets standalone behaviour instead.
*
* Note `autoLeaveWhenOthersLeft` and `waitForCallPickup` are deliberately not
+4 -4
View File
@@ -13,10 +13,10 @@ export const i18nKey = (key: string): string => key;
/**
* Element Call's own i18next instance.
*
* We deliberately do not use the global i18next singleton: when Element Call is
* embedded in a host application (rather than running as its own page), that
* singleton belongs to the host, and configuring it would clobber the host's
* translations.
* We deliberately do not use the global i18next singleton: when Element Call
* runs as a component inside a host application (rather than as its own page),
* that singleton belongs to the host, and configuring it would clobber the
* host's translations.
*
* It is configured by `Initializer.initBeforeReact` and made available to
* components via `<I18nextProvider>`; tests and stories configure it directly.