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 1e41a6da51..c86bf9cc60 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 @@ -6,6 +6,7 @@ import { JSX } from 'react'; import { ModuleApi } from '@matrix-org/react-sdk-module-api'; +import { RoomEvent } from 'matrix-js-sdk'; import { Root } from 'react-dom/client'; import { RuntimeModule } from '@matrix-org/react-sdk-module-api'; @@ -60,26 +61,42 @@ export interface ConfigApi { get(key?: K): Config | Config[K]; } +// @public (undocumented) +export interface ContextMenuItem { + iconClassName: string; + label: string; + onClick: (e: React.MouseEvent | React.KeyboardEvent | React.FormEvent) => void | Promise; +} + // @public export type CustomComponentProps = { - [CustomComponentTarget.TextualEvent]: { - mxEvent: any; - stripReply: boolean; + [CustomComponentTarget.TextualBody]: { + mxEvent: RoomEvent; + highlights?: string[]; + showUrlPreview?: boolean; + forExport?: boolean; + }; + [CustomComponentTarget.MessageContextMenu]: { + mxEvent: RoomEvent; + closeMenu: () => void; }; }; // @public -export type CustomComponentRenderFunction = (props: CustomComponentProps[T], originalComponent: JSX.Element) => JSX.Element | null; +export type CustomComponentRenderFunction = ( +props: CustomComponentProps[T], +originalComponent: JSX.Element) => JSX.Element | null; // @public export interface CustomComponentsApi { + buildContextMenuBlock(items: ContextMenuItem[]): JSX.Element; register(target: T, renderer: CustomComponentRenderFunction): void; } // @public export enum CustomComponentTarget { - // (undocumented) - TextualEvent = "TextualEvent" + MessageContextMenu = "MessageContextMenu", + TextualBody = "TextualBody" } // @alpha @deprecated (undocumented) diff --git a/packages/element-web-module-api/package.json b/packages/element-web-module-api/package.json index 9c0e0cc364..cd329b8788 100644 --- a/packages/element-web-module-api/package.json +++ b/packages/element-web-module-api/package.json @@ -44,13 +44,15 @@ "vite": "^6.0.11", "vite-plugin-dts": "^4.5.0", "vitest": "^3.0.5", - "vitest-sonar-reporter": "^2.0.0" + "vitest-sonar-reporter": "^2.0.0", + "matrix-js-sdk": "^37.5.0" }, "peerDependencies": { "@matrix-org/react-sdk-module-api": "*", "@types/react": "*", "@types/react-dom": "*", "matrix-web-i18n": "*", + "matrix-js-sdk": "*", "react": "^19" }, "peerDependenciesMeta": { diff --git a/packages/element-web-module-api/src/api/custom-components.ts b/packages/element-web-module-api/src/api/custom-components.ts index 582a281ce1..f3cd115582 100644 --- a/packages/element-web-module-api/src/api/custom-components.ts +++ b/packages/element-web-module-api/src/api/custom-components.ts @@ -1,21 +1,38 @@ -import { ComponentType, JSX, MouseEventHandler, PropsWithChildren } from "react"; +import type { JSX } from "react"; +import type { RoomEvent } from "matrix-js-sdk"; /** - * Target of the renderer function. + * Targets in Element for custom components. * @public */ export enum CustomComponentTarget { /** - * TODO: We should make this more descriptive. - * The renderer for text events in the timeline, such as "m.room.message" + * Component that renders "m.room.message" events in the room timeline. */ TextualBody = "TextualBody", + /** + * "Options" Context menu for a timeline event. + * Use `buildContextMenuBlock` to build a section to be used by this component. + * @see buildContextMenuBlock + */ MessageContextMenu = "MessageContextMenu", } export interface ContextMenuItem { + /** + * The human readable label for an event. + * TODO: Should this be i18n-d + */ label: string; + /** + * The icon to use for this item. + * https://github.com/vector-im/riot-web/blob/efc6149a8b3362c01b93f52e76e5c4ae8cbcb65c/res/css/views/context_menus/_MessageContextMenu.pcss#L10 + */ iconClassName: string; + /** + * Handler for click events on the context menu item. + * Does NOT close the menu after execution. + */ onClick: ( e: React.MouseEvent | React.KeyboardEvent | React.FormEvent, ) => void | Promise; @@ -28,12 +45,12 @@ export interface ContextMenuItem { export type CustomComponentProps = { [CustomComponentTarget.TextualBody]: { /** - * The Matrix event information. - * TODO: Should this just use the types from matrix-js-sdk? + * The Matrix event for this textual body. */ - mxEvent: any; + mxEvent: RoomEvent; /** - * Words to highlight on + * Words to highlight on (e.g. from search results).\ + * May be undefined if the client does not need to highlight */ highlights?: string[]; /** @@ -47,12 +64,11 @@ export type CustomComponentProps = { }; [CustomComponentTarget.MessageContextMenu]: { /** - * The Matrix event information. - * TODO: Should this just use the types from matrix-js-sdk? + * The Matrix event which this context menu targets. */ - mxEvent: any; + mxEvent: RoomEvent; /** - * Close the menu + * Function that will close the menu. */ closeMenu: () => void; }; @@ -62,24 +78,37 @@ export type CustomComponentProps = { * @public */ export type CustomComponentRenderFunction = ( + /** + * Properties from the given target to be used for rendering. + */ props: CustomComponentProps[T], + /** + * The original component. + */ originalComponent: JSX.Element, ) => JSX.Element | null; /** - * The API for creating custom components. + * API for inserting custom components into Element. * @public */ export interface CustomComponentsApi { /** * Register a renderer for a component type. - * @param target - The target type of component + * The render function should either return a rendered component, or `null` if the + * component should not be overidden. + * + * Multiple render function may be registered for a single target, however the first + * non-null result will be used. If all results are null, or no registrations exist + * for a target then the original component is used. + * + * @param target - The target location for the component. * @param renderer - The render method. */ register(target: T, renderer: CustomComponentRenderFunction): void; /** - * Register a context menu section with a given set of items. + * Generate a context menu section for a given set of items. * @param items - A set of items to render. */ buildContextMenuBlock(items: ContextMenuItem[]): JSX.Element;