Add support for Widget & Room Header Buttons module APIs (#32734)

* Add support for Widget & Room Header Buttons module APIs

To support https://github.com/element-hq/element-modules/pull/217

* Update for new api

* Test addRoomHeaderButtonCallback

* Extra mock api

* Test for widgetapi

* Convert enum

* Convert other enum usage

* Add tests for widget context menu move buttons

Which have just changed because of the enum

* Add tests for moving the widgets

* Fix copyright

Co-authored-by: Florian Duros <florianduros@element.io>

* Update module API

* A little import/export

---------

Co-authored-by: Florian Duros <florianduros@element.io>
This commit is contained in:
David Baker
2026-03-13 13:44:18 +00:00
committed by GitHub
parent 86692ce0a7
commit 09bbf796dc
30 changed files with 553 additions and 225 deletions
+2
View File
@@ -31,6 +31,7 @@ import { ElementWebBuiltinsApi } from "./BuiltinsApi.tsx";
import { ClientApi } from "./ClientApi.ts";
import { StoresApi } from "./StoresApi.ts";
import { WidgetLifecycleApi } from "./WidgetLifecycleApi.ts";
import { WidgetApi } from "./WidgetApi.ts";
import { CustomisationsApi } from "./customisationsApi.ts";
const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
@@ -89,6 +90,7 @@ export class ModuleApi implements Api {
public readonly extras = new ElementWebExtrasApi();
public readonly builtins = new ElementWebBuiltinsApi();
public readonly widgetLifecycle = new WidgetLifecycleApi();
public readonly widget = new WidgetApi();
public readonly rootNode = document.getElementById("matrixchat")!;
public readonly client = new ClientApi();
public readonly stores = new StoresApi();
+11 -1
View File
@@ -1,4 +1,5 @@
/*
Copyright 2026 Element Creations Ltd.
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -6,7 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/
import { useState } from "react";
import { type SpacePanelItemProps, type ExtrasApi } from "@element-hq/element-web-module-api";
import {
type SpacePanelItemProps,
type ExtrasApi,
type RoomHeaderButtonsCallback,
} from "@element-hq/element-web-module-api";
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
import { useTypedEventEmitter } from "../hooks/useEventEmitter";
@@ -26,6 +31,7 @@ interface EmittedEvents {
export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents> implements ExtrasApi {
public spacePanelItems = new Map<string, SpacePanelItemProps>();
public visibleRoomBySpaceKey = new Map<string, () => string[]>();
public roomHeaderButtonsCallbacks: RoomHeaderButtonsCallback[] = [];
public setSpacePanelItem(spacekey: string, item: SpacePanelItemProps): void {
this.spacePanelItems.set(spacekey, item);
@@ -35,6 +41,10 @@ export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents,
public getVisibleRoomBySpaceKey(spaceKey: string, cb: () => string[]): void {
this.visibleRoomBySpaceKey.set(spaceKey, cb);
}
public addRoomHeaderButtonCallback(cb: RoomHeaderButtonsCallback): void {
this.roomHeaderButtonsCallbacks.push(cb);
}
}
export function useModuleSpacePanelItems(api: ElementWebExtrasApi): ModuleSpacePanelItem[] {
+47
View File
@@ -0,0 +1,47 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { type Container, type WidgetApi as WidgetApiInterface } from "@element-hq/element-web-module-api";
import { getHttpUriForMxc } from "matrix-js-sdk/src/matrix";
import type { IWidget } from "matrix-widget-api";
import WidgetStore, { isAppWidget } from "../stores/WidgetStore";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
/**
* Host-side implementation of the widget API.
* Allows modules to interact with widgets, including listing widgets in rooms.
*/
export class WidgetApi implements WidgetApiInterface {
public getWidgetsInRoom(roomId: string): IWidget[] {
return WidgetStore.instance.getApps(roomId);
}
public getAppAvatarUrl(app: IWidget, width?: number, height?: number, resizeMethod?: string): string | null {
if (!isAppWidget(app) || !app.avatar_url) return null;
return getHttpUriForMxc(
MatrixClientPeg.safeGet().getHomeserverUrl(),
app.avatar_url,
width,
height,
resizeMethod,
);
}
public isAppInContainer(app: IWidget, container: Container, roomId: string): boolean {
const room = MatrixClientPeg.safeGet().getRoom(roomId);
if (!room) return false;
return WidgetLayoutStore.instance.isInContainer(room, app, container);
}
public moveAppToContainer(app: IWidget, container: Container, roomId: string): void {
const room = MatrixClientPeg.safeGet().getRoom(roomId);
if (!room) return;
WidgetLayoutStore.instance.moveToContainer(room, app, container);
}
}