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 68c3e643f4..5e32792e81 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 { ReactNode } from 'react'; @@ -59,6 +60,8 @@ export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiEx readonly rootNode: HTMLElement; readonly stores: StoresApi; // @alpha + readonly widget: WidgetApi; + // @alpha readonly widgetLifecycle: WidgetLifecycleApi; } @@ -110,6 +113,9 @@ export interface ConfigApi { get(key?: K): Config | Config[K]; } +// @alpha +export type Container = "top" | "right" | "center"; + // @alpha export interface CustomComponentsApi { registerLoginComponent(renderer: CustomLoginRenderFunction): void; @@ -205,6 +211,7 @@ originalComponent: (props: P) => JSX.Element) => JSX.Element; // @alpha export interface ExtrasApi { + addRoomHeaderButtonCallback(cb: RoomHeaderButtonsCallback): void; getVisibleRoomBySpaceKey(spaceKey: string, cb: () => string[]): void; setSpacePanelItem(spaceKey: string, props: SpacePanelItemProps): void; } @@ -390,6 +397,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; @@ -479,6 +489,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; diff --git a/packages/element-web-module-api/package.json b/packages/element-web-module-api/package.json index c8e1b4aafa..a1b1ffac3f 100644 --- a/packages/element-web-module-api/package.json +++ b/packages/element-web-module-api/package.json @@ -39,6 +39,7 @@ "@types/semver": "^7.5.8", "@vitest/coverage-v8": "^4.0.0", "matrix-web-i18n": "^3.3.0", + "matrix-widget-api": "^1.17.0", "rollup-plugin-external-globals": "^0.13.0", "semver": "^7.6.3", "typescript": "^5.7.3", @@ -61,5 +62,6 @@ "matrix-web-i18n": { "optional": true } - } + }, + "dependencies": {} } diff --git a/packages/element-web-module-api/src/api/extras.ts b/packages/element-web-module-api/src/api/extras.ts index 5ffc9c4f16..ad437c4986 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; + + /** + * Adds a 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}). + */ + addRoomHeaderButtonCallback(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 3015892f65..3d60fc2a43 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"; import { type CustomisationsApi } from "./customisations.ts"; /** @@ -144,6 +146,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; + /** * Allows modules to customise behaviour of app's components. * @alpha Subject to change. 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 e009f2641c..1f34106cfc 100644 --- a/packages/element-web-module-api/src/index.ts +++ b/packages/element-web-module-api/src/index.ts @@ -23,6 +23,7 @@ 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 type * from "./api/customisations"; export { UIComponent } from "./api/customisations"; export * from "./api/watchable";