From bf69ddf2b457c04c438da80658ad2ffbd9606179 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Mar 2026 10:58:00 +0000 Subject: [PATCH] Add module APIs to enable widget toggle buttons --- .../element-web-module-api.api.md | 19 +++++- packages/element-web-module-api/package.json | 3 + .../element-web-module-api/src/api/extras.ts | 16 +++++ .../element-web-module-api/src/api/index.ts | 9 +++ .../element-web-module-api/src/api/widget.ts | 68 +++++++++++++++++++ packages/element-web-module-api/src/index.ts | 1 + 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 packages/element-web-module-api/src/api/widget.ts diff --git a/packages/element-web-module-api/element-web-module-api.api.md b/packages/element-web-module-api/element-web-module-api.api.md index 86cce8dfec..aff9156165 100644 --- a/packages/element-web-module-api/element-web-module-api.api.md +++ b/packages/element-web-module-api/element-web-module-api.api.md @@ -5,6 +5,7 @@ ```ts import { ComponentType } from 'react'; +import { IWidget } from 'matrix-widget-api'; import { JSX } from 'react'; import { ModuleApi } from '@matrix-org/react-sdk-module-api'; import { Root } from 'react-dom/client'; @@ -56,6 +57,8 @@ export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiEx readonly rootNode: HTMLElement; readonly stores: StoresApi; // @alpha + readonly widget: WidgetApi; + // @alpha readonly widgetLifecycle: WidgetLifecycleApi; } @@ -107,6 +110,9 @@ export interface ConfigApi { get(key?: K): Config | Config[K]; } +// @alpha +export type Container = "top" | "right" | "center"; + // @alpha export interface CustomComponentsApi { registerMessageRenderer(eventTypeOrFilter: string | ((mxEvent: MatrixEvent) => boolean), renderer: CustomMessageRenderFunction, hints?: CustomMessageRenderHints): void; @@ -174,6 +180,7 @@ export interface DirectoryCustomisations { // @alpha export interface ExtrasApi { getVisibleRoomBySpaceKey(spaceKey: string, cb: () => string[]): void; + setRoomHeaderButtonCallback(cb: RoomHeaderButtonsCallback): void; setSpacePanelItem(spaceKey: string, props: SpacePanelItemProps): void; } @@ -358,6 +365,9 @@ export interface Room { name: Watchable; } +// @alpha +export type RoomHeaderButtonsCallback = (roomId: string) => JSX.Element | undefined; + // @alpha @deprecated (undocumented) export interface RoomListCustomisations { isRoomVisible?(room: Room): boolean; @@ -436,6 +446,14 @@ export class Watchable { watch(listener: (value: T) => void): void; } +// @alpha +export interface WidgetApi { + getAppAvatarUrl(app: IWidget, width?: number, height?: number, resizeMethod?: string): string | null; + getWidgetsInRoom(roomId: string): IWidget[]; + isAppInContainer(app: IWidget, container: Container, roomId: string): boolean; + moveAppToContainer(app: IWidget, container: Container, roomId: string): void; +} + // @alpha export type WidgetDescriptor = { id: string; @@ -476,4 +494,3 @@ export interface WidgetVariablesCustomisations { // (No @packageDocumentation comment for this package) ``` - diff --git a/packages/element-web-module-api/package.json b/packages/element-web-module-api/package.json index 6950765e7b..63a87ad3b2 100644 --- a/packages/element-web-module-api/package.json +++ b/packages/element-web-module-api/package.json @@ -61,5 +61,8 @@ "matrix-web-i18n": { "optional": true } + }, + "dependencies": { + "matrix-widget-api": "^1.17.0" } } diff --git a/packages/element-web-module-api/src/api/extras.ts b/packages/element-web-module-api/src/api/extras.ts index 5ffc9c4f16..1f6f9818c6 100644 --- a/packages/element-web-module-api/src/api/extras.ts +++ b/packages/element-web-module-api/src/api/extras.ts @@ -43,6 +43,15 @@ export interface SpacePanelItemProps { onSelected: () => void; } +/** + * A callback that returns a JSX element representing the buttons. + * + * @alpha + * @param roomId - The ID of the room for which the header is being rendered. + * @returns A JSX element representing the buttons to be rendered in the room header, or undefined if no buttons should be rendered. + */ +export type RoomHeaderButtonsCallback = (roomId: string) => JSX.Element | undefined; + /** * API for inserting extra UI into Element Web. * @alpha Subject to change. @@ -67,4 +76,11 @@ export interface ExtrasApi { * @param cb - A callback that returns the list of visible room IDs. */ getVisibleRoomBySpaceKey(spaceKey: string, cb: () => string[]): void; + + /** + * Sets the callback to get extra buttons in the room header (which can vary depending on the room being displayed). + * + * @param cb - A callback that returns a JSX element representing the buttons (see {@link RoomHeaderButtonsCallback}). + */ + setRoomHeaderButtonCallback(cb: RoomHeaderButtonsCallback): void; } diff --git a/packages/element-web-module-api/src/api/index.ts b/packages/element-web-module-api/src/api/index.ts index 9c5e59ed57..840eea6eb2 100644 --- a/packages/element-web-module-api/src/api/index.ts +++ b/packages/element-web-module-api/src/api/index.ts @@ -1,5 +1,6 @@ /* Copyright 2025 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. @@ -20,6 +21,7 @@ import { type BuiltinsApi } from "./builtins.ts"; import { type StoresApi } from "./stores.ts"; import { type ClientApi } from "./client.ts"; import { type WidgetLifecycleApi } from "./widget-lifecycle.ts"; +import { type WidgetApi } from "./widget.ts"; /** * Module interface for modules to implement. @@ -143,6 +145,13 @@ export interface Api */ readonly widgetLifecycle: WidgetLifecycleApi; + /** + * API for modules to interact with widgets in Element Web, including getting what widgets + * are active in a given room. + * @alpha Subject to change. + */ + readonly widget: WidgetApi; + /** * Create a ReactDOM root for rendering React components. * Exposed to allow modules to avoid needing to bundle their own ReactDOM. diff --git a/packages/element-web-module-api/src/api/widget.ts b/packages/element-web-module-api/src/api/widget.ts new file mode 100644 index 0000000000..66c8b31e73 --- /dev/null +++ b/packages/element-web-module-api/src/api/widget.ts @@ -0,0 +1,68 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { type IWidget } from "matrix-widget-api"; + +/** + * Containers that control where a widget is displayed on the screen. + * + * "top" is the app drawer, and currently the only sensible value. + * + * "right" is the right panel, and the default for widgets. Setting + * this as a container on a widget is essentially like saying "no + * changes needed", though this may change in the future. + * + * "center" was uncodumented at time of porting this from an enum. + * Possibly when a widget replaces the main chat view like element call. + * + * @alpha Subject to change. + */ +export type Container = "top" | "right" | "center"; + +/** + * An API for interfacing with widgets in Element Web, including getting what widgets + * are active in a given room. + * @alpha Subject to change. + */ +export interface WidgetApi { + /** + * Gets the widgets active in a given room. + * + * @param roomId - The room to get the widgets for. + */ + getWidgetsInRoom(roomId: string): IWidget[]; + + /** + * Gets the URL of a widget's avatar, if it has one. + * + * @param app - The widget to get the avatar URL for. + * @param width - Optional width to resize the avatar to. + * @param height - Optional height to resize the avatar to. + * @param resizeMethod - Optional method to use when resizing the avatar. + * @returns The URL of the widget's avatar, or null if it doesn't have one. + */ + getAppAvatarUrl(app: IWidget, width?: number, height?: number, resizeMethod?: string): string | null; + + /** + * Checks if a widget is in a specific container in a given room. + * + * @param app - The widget to check. + * @param container - The container to check. + * @param roomId - The room to check in. + * @returns True if the widget is in the specified container, false otherwise. + */ + isAppInContainer(app: IWidget, container: Container, roomId: string): boolean; + + /** + * Moves a widget to a specific container in a given room. + * + * @param app - The widget to move. + * @param container - The container to move the widget to. + * @param roomId - The room to move the widget in. + */ + moveAppToContainer(app: IWidget, container: Container, roomId: string): void; +} diff --git a/packages/element-web-module-api/src/index.ts b/packages/element-web-module-api/src/index.ts index 89c8b24dbd..33b8b92c58 100644 --- a/packages/element-web-module-api/src/index.ts +++ b/packages/element-web-module-api/src/index.ts @@ -23,5 +23,6 @@ export type * from "./api/builtins"; export type * from "./api/stores"; export type * from "./api/client"; export type * from "./api/widget-lifecycle"; +export type * from "./api/widget"; export * from "./api/watchable"; export type * from "./utils";