Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-12-06 11:41:40 +00:00
parent 62c765bfd3
commit 4f14d3f5ae
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
39 changed files with 182 additions and 199 deletions

View file

@ -71,9 +71,13 @@
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js" "update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
}, },
"resolutions": { "resolutions": {
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"oidc-client-ts": "3.1.0", "oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0", "jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001684", "caniuse-lite": "1.0.30001684",
"react": "19.0.0",
"react-dom": "19.0.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0" "wrap-ansi": "npm:wrap-ansi@^7.0.0"
}, },
@ -179,7 +183,7 @@
"@svgr/webpack": "^8.0.0", "@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8", "@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0", "@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/commonmark": "^0.27.4", "@types/commonmark": "^0.27.4",
"@types/counterpart": "^0.18.1", "@types/counterpart": "^0.18.1",

View file

@ -15,5 +15,10 @@ declare module "react" {
): (props: P & React.RefAttributes<T>) => React.ReactElement<any> | null; ): (props: P & React.RefAttributes<T>) => React.ReactElement<any> | null;
// Fix lazy types - https://stackoverflow.com/a/71017028 // Fix lazy types - https://stackoverflow.com/a/71017028
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T; // function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;
// Workaround for generics in React 19
interface FunctionComponent {
defaultProps?: {};
}
} }

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { Key, MutableRefObject, ReactElement, RefCallback } from "react"; import React, { HTMLAttributes, Key, MutableRefObject, ReactElement, RefCallback } from "react";
interface IChildProps { interface IChildProps {
style: React.CSSProperties; style: React.CSSProperties;
@ -68,21 +68,22 @@ export default class NodeAnimator extends React.Component<IProps> {
this.children = {}; this.children = {};
React.Children.toArray(newChildren).forEach((c) => { React.Children.toArray(newChildren).forEach((c) => {
if (!isReactElement(c)) return; if (!isReactElement(c)) return;
const props = c.props as HTMLAttributes<HTMLElement>;
if (oldChildren[c.key!]) { if (oldChildren[c.key!]) {
const old = oldChildren[c.key!]; const old = oldChildren[c.key!];
const oldNode = this.nodes[old.key!]; const oldNode = this.nodes[old.key!];
if (oldNode && oldNode.style.left !== c.props.style.left) { if (oldNode && oldNode.style.left !== props.style!.left) {
this.applyStyles(oldNode, { left: c.props.style.left }); this.applyStyles(oldNode, { left: props.style!.left });
} }
// clone the old element with the props (and children) of the new element // clone the old element with the props (and children) of the new element
// so prop updates are still received by the children. // so prop updates are still received by the children.
this.children[c.key!] = React.cloneElement(old, c.props, c.props.children); this.children[c.key!] = React.cloneElement(old, props, props.children);
} else { } else {
// new element. If we have a startStyle, use that as the style and go through // new element. If we have a startStyle, use that as the style and go through
// the enter animations // the enter animations
const newProps: Partial<IChildProps> = {}; const newProps: Partial<IChildProps> = {};
const restingStyle = c.props.style; const restingStyle = props.style!;
const startStyles = this.props.startStyles; const startStyles = this.props.startStyles;
if (startStyles.length > 0) { if (startStyles.length > 0) {

View file

@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
scrollIntoView, scrollIntoView,
onKeyDown, onKeyDown,
}) => { }) => {
const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, { const [state, dispatch] = useReducer(reducer, {
nodes: [], nodes: [],
}); });

View file

@ -8,25 +8,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ComponentProps, forwardRef, Ref, type JSX } from "react"; import React, { ComponentProps, forwardRef, Ref } from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../components/views/elements/AccessibleButton";
type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & { type Props<T extends React.ElementType> = ComponentProps<typeof AccessibleButton<T>> & {
label?: string; label?: string;
// whether the context menu is currently open // whether the context menu is currently open
isExpanded: boolean; isExpanded: boolean;
}; };
// Semantic component for representing the AccessibleButton which launches a <ContextMenu /> // Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>( export const ContextMenuButton = forwardRef(function <T extends React.ElementType>(
{ label, isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>, { label, isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElement>, ref: Ref<HTMLElement>,
) { ) {
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
element={element as keyof JSX.IntrinsicElements} element={element as React.ElementType}
onClick={onClick} onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined} onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-label={label} aria-label={label}

View file

@ -8,24 +8,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ComponentProps, forwardRef, Ref, type JSX } from "react"; import React, { ComponentProps, forwardRef, Ref } from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../components/views/elements/AccessibleButton";
type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & { type Props<T extends React.ElementType> = ComponentProps<typeof AccessibleButton<T>> & {
// whether the context menu is currently open // whether the context menu is currently open
isExpanded: boolean; isExpanded: boolean;
}; };
// Semantic component for representing the AccessibleButton which launches a <ContextMenu /> // Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>( export const ContextMenuTooltipButton = forwardRef(function <T extends React.ElementType>(
{ isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>, { isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElement>, ref: Ref<HTMLElement>,
) { ) {
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
element={element as keyof JSX.IntrinsicElements} element={element as React.ElementType}
onClick={onClick} onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined} onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-haspopup={true} aria-haspopup={true}

View file

@ -8,16 +8,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React, { type JSX } from "react";
import { RovingAccessibleButton } from "../RovingTabIndex"; import { RovingAccessibleButton } from "../RovingTabIndex";
interface IProps extends React.ComponentProps<typeof RovingAccessibleButton> { type IProps<T extends keyof JSX.IntrinsicElements> = React.ComponentProps<typeof RovingAccessibleButton<T>> & {
label?: string; label?: string;
} };
// Semantic component for representing a role=menuitem // Semantic component for representing a role=menuitem
export const MenuItem: React.FC<IProps> = ({ children, label, ...props }) => { export const MenuItem = <T extends keyof JSX.IntrinsicElements>({
children,
label,
...props
}: IProps<T>): JSX.Element => {
const ariaLabel = props["aria-label"] || label; const ariaLabel = props["aria-label"] || label;
return ( return (

View file

@ -12,16 +12,13 @@ import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex"; import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types"; import { Ref } from "./types";
type Props<T extends keyof JSX.IntrinsicElements> = Omit< type Props<T extends React.ElementType> = Omit<ComponentProps<typeof AccessibleButton<T>>, "inputRef" | "tabIndex"> & {
ComponentProps<typeof AccessibleButton<T>>,
"inputRef" | "tabIndex"
> & {
inputRef?: Ref; inputRef?: Ref;
focusOnMouseOver?: boolean; focusOnMouseOver?: boolean;
}; };
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components. // Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({ export const RovingAccessibleButton = <T extends React.ElementType>({
inputRef, inputRef,
onFocus, onFocus,
onMouseOver, onMouseOver,
@ -33,7 +30,7 @@ export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
element={element as keyof JSX.IntrinsicElements} element={element as React.ElementType}
onFocus={(event: React.FocusEvent) => { onFocus={(event: React.FocusEvent) => {
onFocusInternal(); onFocusInternal();
onFocus?.(event); onFocus?.(event);

View file

@ -8,16 +8,11 @@ Please see LICENSE files in the repository root for full details.
*/ */
import classNames from "classnames"; import classNames from "classnames";
import React, { HTMLAttributes, ReactHTML, ReactNode, WheelEvent, type JSX } from "react"; import React, { ReactNode, WheelEvent } from "react";
type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> = export type IProps<T extends React.ElementType> = React.ComponentPropsWithoutRef<T> & {
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">; element?: T;
type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<Omit<JSX.IntrinsicElements[T], "ref">>;
export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElementProps<T>, "onScroll"> & {
element: T;
className?: string; className?: string;
onScroll?: (event: Event) => void;
onWheel?: (event: WheelEvent) => void; onWheel?: (event: WheelEvent) => void;
style?: React.CSSProperties; style?: React.CSSProperties;
tabIndex?: number; tabIndex?: number;
@ -25,11 +20,7 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElem
children: ReactNode; children: ReactNode;
}; };
export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> extends React.Component<IProps<T>> { export default class AutoHideScrollbar<T extends React.ElementType> extends React.Component<IProps<T>> {
public static defaultProps = {
element: "div" as keyof ReactHTML,
};
public readonly containerRef: React.RefObject<HTMLDivElement | null> = React.createRef(); public readonly containerRef: React.RefObject<HTMLDivElement | null> = React.createRef();
public componentDidMount(): void { public componentDidMount(): void {
@ -55,7 +46,7 @@ export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> ex
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props; const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;
return React.createElement( return React.createElement(
element, element ?? "div",
{ {
...otherProps, ...otherProps,
ref: this.containerRef, ref: this.containerRef,

View file

@ -440,7 +440,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
); );
} }
public render(): React.ReactElement<any> | number | string { public render(): JSX.Element {
if (this.props.mountAsChild) { if (this.props.mountAsChild) {
// Render as a child of the current parent // Render as a child of the current parent
return this.renderMenu(); return this.renderMenu();

View file

@ -5,13 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { createRef, type JSX } from "react"; import React, { ComponentProps, createRef } from "react";
import AutoHideScrollbar, { IProps as AutoHideScrollbarProps } from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import UIStore, { UI_EVENTS } from "../../stores/UIStore"; import UIStore, { UI_EVENTS } from "../../stores/UIStore";
export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollbarProps<T>, "onWheel" | "element"> & { export type IProps<T extends React.ElementType> = Omit<ComponentProps<typeof AutoHideScrollbar<T>>, "onWheel"> & {
element?: T;
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator // If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning // and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element. // by the parent element.
@ -30,10 +29,7 @@ interface IState {
rightIndicatorOffset: string; rightIndicatorOffset: string;
} }
export default class IndicatorScrollbar<T extends keyof JSX.IntrinsicElements> extends React.Component< export default class IndicatorScrollbar<T extends React.ElementType> extends React.Component<IProps<T>, IState> {
IProps<T>,
IState
> {
private autoHideScrollbar = createRef<AutoHideScrollbar<any>>(); private autoHideScrollbar = createRef<AutoHideScrollbar<any>>();
private scrollElement?: HTMLDivElement; private scrollElement?: HTMLDivElement;
private likelyTrackpadUser: boolean | null = null; private likelyTrackpadUser: boolean | null = null;

View file

@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { createContext, Dispatch, ReducerAction, ReducerState } from "react"; import { createContext, Dispatch, ReducerState } from "react";
import type { AuthHeaderReducer } from "./AuthHeaderProvider"; import type { AuthHeaderAction, AuthHeaderReducer } from "./AuthHeaderProvider";
interface AuthHeaderContextType { interface AuthHeaderContextType {
state: ReducerState<AuthHeaderReducer>; state: ReducerState<AuthHeaderReducer>;
dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>; dispatch: Dispatch<AuthHeaderAction>;
} }
export const AuthHeaderContext = createContext<AuthHeaderContextType | undefined>(undefined); export const AuthHeaderContext = createContext<AuthHeaderContextType | undefined>(undefined);

View file

@ -17,7 +17,7 @@ export enum AuthHeaderActionType {
Remove, Remove,
} }
interface AuthHeaderAction { export interface AuthHeaderAction {
type: AuthHeaderActionType; type: AuthHeaderActionType;
value: ComponentProps<typeof AuthHeaderModifier>; value: ComponentProps<typeof AuthHeaderModifier>;
} }
@ -25,7 +25,7 @@ interface AuthHeaderAction {
export type AuthHeaderReducer = Reducer<ComponentProps<typeof AuthHeaderModifier>[], AuthHeaderAction>; export type AuthHeaderReducer = Reducer<ComponentProps<typeof AuthHeaderModifier>[], AuthHeaderAction>;
export function AuthHeaderProvider({ children }: PropsWithChildren<{}>): JSX.Element { export function AuthHeaderProvider({ children }: PropsWithChildren<{}>): JSX.Element {
const [state, dispatch] = useReducer<AuthHeaderReducer>( const [state, dispatch] = useReducer(
(state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => { (state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => {
switch (action.type) { switch (action.type) {
case AuthHeaderActionType.Add: case AuthHeaderActionType.Add:

View file

@ -17,6 +17,7 @@ import ContextMenu, {
MenuItemRadio, MenuItemRadio,
} from "../../structures/ContextMenu"; } from "../../structures/ContextMenu";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton.tsx";
interface IProps extends IContextMenuProps { interface IProps extends IContextMenuProps {
className?: string; className?: string;
@ -31,10 +32,10 @@ interface IOptionListProps {
children: ReactNode; children: ReactNode;
} }
interface IOptionProps extends React.ComponentProps<typeof MenuItem> { type IOptionProps<T extends React.ElementType> = React.ComponentProps<typeof AccessibleButton<T>> & {
iconClassName?: string; iconClassName?: string;
isDestructive?: boolean; isDestructive?: boolean;
} };
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> { interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
iconClassName: string; iconClassName: string;
@ -110,18 +111,19 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
); );
}; };
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({ export const IconizedContextMenuOption = <T extends React.ElementType>({
element,
label, label,
className, className,
iconClassName, iconClassName,
children, children,
isDestructive, isDestructive,
...props ...props
}) => { }: IOptionProps<T>): JSX.Element => {
return ( return (
<MenuItem <MenuItem
element="li"
{...props} {...props}
element={element ?? "li"}
className={classNames(className, { className={classNames(className, {
mx_IconizedContextMenu_item: true, mx_IconizedContextMenu_item: true,
mx_IconizedContextMenu_itemDestructive: isDestructive, mx_IconizedContextMenu_itemDestructive: isDestructive,

View file

@ -396,7 +396,6 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
openInMapSiteButton = ( openInMapSiteButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconOpenInMapSite" iconClassName="mx_MessageContextMenu_iconOpenInMapSite"
onClick={null}
label={_t("timeline|context_menu|open_in_osm")} label={_t("timeline|context_menu|open_in_osm")}
element="a" element="a"
{...{ {...{

View file

@ -13,12 +13,12 @@ import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton"; import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
import { Ref } from "../../../../accessibility/roving/types"; import { Ref } from "../../../../accessibility/roving/types";
type TooltipOptionProps<T extends keyof JSX.IntrinsicElements> = ButtonProps<T> & { type TooltipOptionProps<T extends React.ElementType> = ButtonProps<T> & {
endAdornment?: ReactNode; endAdornment?: ReactNode;
inputRef?: Ref; inputRef?: Ref;
}; };
export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({ export const TooltipOption = <T extends React.ElementType>({
inputRef, inputRef,
className, className,
element, element,
@ -34,7 +34,7 @@ export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
tabIndex={-1} tabIndex={-1}
aria-selected={isActive} aria-selected={isActive}
role="option" role="option"
element={element as keyof JSX.IntrinsicElements} element={element as React.ElementType}
/> />
); );
}; };

View file

@ -6,22 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { import React, { ComponentProps, ComponentPropsWithRef, FunctionComponent, type JSX, PropsWithChildren } from "react";
ComponentProps,
forwardRef,
FunctionComponent,
HTMLAttributes,
InputHTMLAttributes,
Ref,
type JSX,
} from "react";
import classnames from "classnames"; import classnames from "classnames";
import { Tooltip } from "@vector-im/compound-web"; import { Tooltip } from "@vector-im/compound-web";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>; export type ButtonEvent<T extends SupportedElement> = React.MouseEvent<T> | React.KeyboardEvent<T> | React.FormEvent<T>;
/** /**
* The kind of button, similar to how Bootstrap works. * The kind of button, similar to how Bootstrap works.
@ -54,25 +46,11 @@ export type AccessibleButtonKind =
* *
* To remain compatible with existing code, well continue to support InputHTMLAttributes<Element> * To remain compatible with existing code, well continue to support InputHTMLAttributes<Element>
*/ */
type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> = type ButtonOwnProps<C extends SupportedElement> = PropsWithChildren<{
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<
Omit<JSX.IntrinsicElements[T], "ref" | "onClick" | "onMouseDown" | "onKeyUp" | "onKeyDown">
> &
Omit<InputHTMLAttributes<Element>, "onClick">;
type TooltipProps = ComponentProps<typeof Tooltip>;
/**
* Type of props accepted by {@link AccessibleButton}.
*
* Extends props accepted by the underlying element specified using the `element` prop.
*/
type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
/** /**
* The base element type. "div" by default. * The base element type. "div" by default.
*/ */
element?: T; element?: C;
/** /**
* The kind of button, similar to how Bootstrap works. * The kind of button, similar to how Bootstrap works.
*/ */
@ -88,7 +66,7 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
/** /**
* Event handler for button activation. Should be implemented exactly like a normal `onClick` handler. * Event handler for button activation. Should be implemented exactly like a normal `onClick` handler.
*/ */
onClick: ((e: ButtonEvent) => void | Promise<void>) | null; onClick: ((e: ButtonEvent<C>) => void | Promise<void>) | null;
/** /**
* The tooltip to show on hover or focus. * The tooltip to show on hover or focus.
*/ */
@ -111,16 +89,46 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
* Whether the tooltip should be disabled. * Whether the tooltip should be disabled.
*/ */
disableTooltip?: TooltipProps["disabled"]; disableTooltip?: TooltipProps["disabled"];
}; }>;
export type ButtonProps<T extends keyof JSX.IntrinsicElements> = Props<T>; // type UnstyledButtonPropsFor<C extends React.ElementType> = React.ComponentPropsWithoutRef<C> & {
// ref?: React.Ref<React.ComponentRef<C>>;
// };
type ButtonPropsFor<C extends SupportedElement> = ButtonOwnProps<C> & ComponentProps<C>;
// type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> =
// JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
// type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<
// Omit<JSX.IntrinsicElements[T], "ref" | "onClick" | "onMouseDown" | "onKeyUp" | "onKeyDown">
// > &
// Omit<InputHTMLAttributes<Element>, "onClick">;
type TooltipProps = ComponentProps<typeof Tooltip>;
/**
* Type of props accepted by {@link AccessibleButton}.
*
* Extends props accepted by the underlying element specified using the `element` prop.
*/
type Props<T extends SupportedElement> = ButtonPropsFor<T>;
export type ButtonProps<T extends SupportedElement> = Props<T>;
type SupportedElement = "div"; // | "a" | "button";
/** /**
* Type of the props passed to the element that is rendered by AccessibleButton. * Type of the props passed to the element that is rendered by AccessibleButton.
*/ */
interface RenderedElementProps extends React.InputHTMLAttributes<Element> { type RenderedElementProps<T extends SupportedElement> = ComponentPropsWithRef<T> & {
ref?: React.Ref<Element>; // TODO
} disabled?: boolean;
/**
* Event handler for button activation. Should be implemented exactly like a normal `onClick` handler.
*/
onClick: ((e: ButtonEvent<T>) => void | Promise<void>) | null;
};
/** /**
* AccessibleButton is a generic wrapper for any element that should be treated * AccessibleButton is a generic wrapper for any element that should be treated
@ -132,8 +140,7 @@ interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
* @param {Object} props react element properties * @param {Object} props react element properties
* @returns {Object} rendered react * @returns {Object} rendered react
*/ */
const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>( const AccessibleButton = function <T extends SupportedElement>({
{
element = "div" as T, element = "div" as T,
onClick, onClick,
children, children,
@ -148,11 +155,12 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
placement = "right", placement = "right",
onTooltipOpenChange, onTooltipOpenChange,
disableTooltip, disableTooltip,
role = "button",
tabIndex = 0,
ref,
...restProps ...restProps
}: Props<T>, }: Props<T>): JSX.Element {
ref: Ref<HTMLElement>, const newProps: RenderedElementProps<any> = { ...restProps, role, tabIndex };
): JSX.Element {
const newProps: RenderedElementProps = restProps;
newProps["aria-label"] = newProps["aria-label"] ?? title; newProps["aria-label"] = newProps["aria-label"] ?? title;
if (disabled) { if (disabled) {
newProps["aria-disabled"] = true; newProps["aria-disabled"] = true;
@ -170,7 +178,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
// And divs which we report as role button to assistive technologies. // And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the // Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here // inconsistencies here
newProps.onKeyDown = (e) => { newProps.onKeyDown = (e: React.KeyboardEvent<T>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e); const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) { switch (action) {
@ -232,13 +240,9 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
); );
} }
return button; return button;
}); };
// Type assertion required due to forwardRef type workaround in react.d.ts // Type assertion required due to forwardRef type workaround in react.d.ts
(AccessibleButton as FunctionComponent).defaultProps = {
role: "button",
tabIndex: 0,
};
(AccessibleButton as FunctionComponent).displayName = "AccessibleButton"; (AccessibleButton as FunctionComponent).displayName = "AccessibleButton";
export default AccessibleButton; export default AccessibleButton;

View file

@ -9,14 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode, type JSX } from "react";
ContextType,
createRef,
CSSProperties,
MutableRefObject,
ReactNode,
type JSX,
} from "react";
import classNames from "classnames"; import classNames from "classnames";
import { IWidget, MatrixCapabilities } from "matrix-widget-api"; import { IWidget, MatrixCapabilities } from "matrix-widget-api";
import { Room, RoomEvent } from "matrix-js-sdk/src/matrix"; import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";

View file

@ -45,7 +45,7 @@ type ShareTypeOptionProps = HTMLAttributes<Element> & {
onClick?: ((e: ButtonEvent) => void | Promise<void>) | null; onClick?: ((e: ButtonEvent) => void | Promise<void>) | null;
}; };
const ShareTypeOption: React.FC<ShareTypeOptionProps> = ({ onClick, label, shareType, ...rest }) => ( const ShareTypeOption: React.FC<ShareTypeOptionProps> = ({ onClick, label, shareType, ...rest }) => (
<AccessibleButton element="button" className="mx_ShareType_option" onClick={onClick ?? null} {...rest}> <AccessibleButton element="button" className="mx_ShareType_option" onClick={onClick} {...rest}>
{shareType === LocationShareType.Own && <UserAvatar />} {shareType === LocationShareType.Own && <UserAvatar />}
{shareType === LocationShareType.Pin && ( {shareType === LocationShareType.Pin && (
<LocationIcon className={`mx_ShareType_option-icon ${LocationShareType.Pin}`} /> <LocationIcon className={`mx_ShareType_option-icon ${LocationShareType.Pin}`} />

View file

@ -51,4 +51,7 @@ export interface IBodyProps {
// Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order. // Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order.
// This may be useful when displaying a preview of the event. // This may be useful when displaying a preview of the event.
inhibitInteraction?: boolean; inhibitInteraction?: boolean;
/* Whether to show the default placeholder for files. Defaults to true. */
showGenericPlaceholder?: boolean;
} }

View file

@ -91,16 +91,11 @@ export function computedStyle(element: HTMLElement | null): string {
return cssText; return cssText;
} }
interface IProps extends IBodyProps {
/* whether or not to show the default placeholder for the file. Defaults to true. */
showGenericPlaceholder: boolean;
}
interface IState { interface IState {
decryptedBlob?: Blob; decryptedBlob?: Blob;
} }
export default class MFileBody extends React.Component<IProps, IState> { export default class MFileBody extends React.Component<IBodyProps, IState> {
public static contextType = RoomContext; public static contextType = RoomContext;
declare public context: React.ContextType<typeof RoomContext>; declare public context: React.ContextType<typeof RoomContext>;
@ -147,7 +142,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
}); });
} }
public componentDidUpdate(prevProps: IProps, prevState: IState): void { public componentDidUpdate(prevProps: IBodyProps, prevState: IState): void {
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) { if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
this.props.onHeightChanged(); this.props.onHeightChanged();
} }

View file

@ -58,7 +58,7 @@ export interface IOperableEventTile {
getEventTileOps(): IEventTileOps | null; getEventTileOps(): IEventTileOps | null;
} }
const baseBodyTypes = new Map<string, typeof React.Component>([ const baseBodyTypes = new Map<string, React.ComponentType<IBodyProps>>([
[MsgType.Text, TextualBody], [MsgType.Text, TextualBody],
[MsgType.Notice, TextualBody], [MsgType.Notice, TextualBody],
[MsgType.Emote, TextualBody], [MsgType.Emote, TextualBody],
@ -80,14 +80,14 @@ const baseEvTypes = new Map<string, React.ComponentType<IBodyProps>>([
export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile { export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile {
private body: React.RefObject<React.Component | IOperableEventTile | null> = createRef(); private body: React.RefObject<React.Component | IOperableEventTile | null> = createRef();
private mediaHelper?: MediaEventHelper; private mediaHelper?: MediaEventHelper;
private bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries()); private bodyTypes = new Map(baseBodyTypes.entries());
private evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries()); private evTypes = new Map(baseEvTypes.entries());
public static contextType = MatrixClientContext; public static contextType = MatrixClientContext;
declare public context: React.ContextType<typeof MatrixClientContext>; declare public context: React.ContextType<typeof MatrixClientContext>;
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) { public constructor(props: IProps) {
super(props, context); super(props);
if (MediaEventHelper.isEligible(this.props.mxEvent)) { if (MediaEventHelper.isEligible(this.props.mxEvent)) {
this.mediaHelper = new MediaEventHelper(this.props.mxEvent); this.mediaHelper = new MediaEventHelper(this.props.mxEvent);
@ -115,12 +115,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
} }
private updateComponentMaps(): void { private updateComponentMaps(): void {
this.bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries()); this.bodyTypes = new Map(baseBodyTypes.entries());
for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) { for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) {
this.bodyTypes.set(bodyType, bodyComponent); this.bodyTypes.set(bodyType, bodyComponent);
} }
this.evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries()); this.evTypes = new Map(baseEvTypes.entries());
for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) { for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) {
this.evTypes.set(evType, evComponent); this.evTypes.set(evType, evComponent);
} }

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ChangeEvent, ContextType, createRef, SyntheticEvent, type JSX } from "react"; import React, { ChangeEvent, ToggleEvent, ContextType, createRef, SyntheticEvent, type JSX } from "react";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types"; import { RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types";
@ -278,9 +278,9 @@ export default class AliasSettings extends React.Component<IProps, IState> {
}); });
}; };
private onLocalAliasesToggled = (event: ChangeEvent<HTMLDetailsElement>): void => { private onLocalAliasesToggled = (event: ToggleEvent<HTMLDetailsElement>): void => {
// expanded // expanded
if (event.target.open) { if (event.currentTarget.open) {
// if local aliases haven't been preloaded yet at component mount // if local aliases haven't been preloaded yet at component mount
if (!this.props.canSetCanonicalAlias && this.state.localAliases.length === 0) { if (!this.props.canSetCanonicalAlias && this.state.localAliases.length === 0) {
this.loadLocalAliases(); this.loadLocalAliases();

View file

@ -26,6 +26,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { renderReplyTile } from "../../../events/EventTileFactory"; import { renderReplyTile } from "../../../events/EventTileFactory";
import { GetRelationsForEvent } from "../rooms/EventTile"; import { GetRelationsForEvent } from "../rooms/EventTile";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { IBodyProps } from "../messages/IBodyProps.ts";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -139,13 +140,13 @@ export default class ReplyTile extends React.PureComponent<IProps> {
); );
} }
const msgtypeOverrides: Record<string, typeof React.Component> = { const msgtypeOverrides: Record<string, React.ComponentType<IBodyProps>> = {
[MsgType.Image]: MImageReplyBody, [MsgType.Image]: MImageReplyBody,
// Override audio and video body with file body. We also hide the download/decrypt button using CSS // Override audio and video body with file body. We also hide the download/decrypt button using CSS
[MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody, [MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody,
[MsgType.Video]: MFileBody, [MsgType.Video]: MFileBody,
}; };
const evOverrides: Record<string, typeof React.Component> = { const evOverrides: Record<string, React.ComponentType<IBodyProps>> = {
// Use MImageReplyBody so that the sticker isn't taking up a lot of space // Use MImageReplyBody so that the sticker isn't taking up a lot of space
[EventType.Sticker]: MImageReplyBody, [EventType.Sticker]: MImageReplyBody,
}; };

View file

@ -7,16 +7,16 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import React, { HTMLAttributes, ReactHTML, type JSX } from "react"; import React, { ElementType, HTMLAttributes, type JSX } from "react";
import { roomContextDetails } from "../../../utils/i18n-helpers"; import { roomContextDetails } from "../../../utils/i18n-helpers";
type Props<T extends keyof ReactHTML> = HTMLAttributes<T> & { type Props<T extends ElementType> = HTMLAttributes<T> & {
component?: T; component?: T;
room: Room; room: Room;
}; };
export function RoomContextDetails<T extends keyof ReactHTML>({ room, component, ...other }: Props<T>): JSX.Element { export function RoomContextDetails<T extends ElementType>({ room, component, ...other }: Props<T>): JSX.Element {
const contextDetails = roomContextDetails(room); const contextDetails = roomContextDetails(room);
if (contextDetails) { if (contextDetails) {
return React.createElement( return React.createElement(

View file

@ -80,19 +80,19 @@ export function handleEventWithAutocomplete(
switch (autocompleteAction) { switch (autocompleteAction) {
case KeyBindingAction.ForceCompleteAutocomplete: case KeyBindingAction.ForceCompleteAutocomplete:
case KeyBindingAction.CompleteAutocomplete: case KeyBindingAction.CompleteAutocomplete:
autocompleteRef.current.onConfirmCompletion(); component.onConfirmCompletion();
handled = true; handled = true;
break; break;
case KeyBindingAction.PrevSelectionInAutocomplete: case KeyBindingAction.PrevSelectionInAutocomplete:
autocompleteRef.current.moveSelection(-1); component.moveSelection(-1);
handled = true; handled = true;
break; break;
case KeyBindingAction.NextSelectionInAutocomplete: case KeyBindingAction.NextSelectionInAutocomplete:
autocompleteRef.current.moveSelection(1); component.moveSelection(1);
handled = true; handled = true;
break; break;
case KeyBindingAction.CancelAutocomplete: case KeyBindingAction.CancelAutocomplete:
autocompleteRef.current.onEscape(event as {} as React.KeyboardEvent); component.onEscape(event as {} as React.KeyboardEvent);
handled = true; handled = true;
break; break;
default: default:

View file

@ -59,7 +59,6 @@ interface ManageAccountButtonProps {
const ManageAccountButton: React.FC<ManageAccountButtonProps> = ({ externalAccountManagementUrl }) => ( const ManageAccountButton: React.FC<ManageAccountButtonProps> = ({ externalAccountManagementUrl }) => (
<AccessibleButton <AccessibleButton
onClick={null}
element="a" element="a"
kind="primary" kind="primary"
target="_blank" target="_blank"

View file

@ -13,7 +13,7 @@ import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/ic
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import AccessibleButton from "../../elements/AccessibleButton"; import AccessibleButton from "../../elements/AccessibleButton";
type Props<T extends keyof JSX.IntrinsicElements> = Omit< type Props<T extends React.ElementType> = Omit<
ComponentProps<typeof AccessibleButton<T>>, ComponentProps<typeof AccessibleButton<T>>,
"aria-label" | "title" | "kind" | "className" | "onClick" | "element" "aria-label" | "title" | "kind" | "className" | "onClick" | "element"
> & { > & {
@ -21,7 +21,7 @@ type Props<T extends keyof JSX.IntrinsicElements> = Omit<
onClick: () => void; onClick: () => void;
}; };
export const DeviceExpandDetailsButton = <T extends keyof JSX.IntrinsicElements>({ export const DeviceExpandDetailsButton = <T extends React.ElementType>({
isExpanded, isExpanded,
onClick, onClick,
...rest ...rest

View file

@ -40,7 +40,7 @@ import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
type ButtonProps<T extends keyof JSX.IntrinsicElements> = Omit< type ButtonProps<T extends React.ElementType> = Omit<
ComponentProps<typeof AccessibleButton<T>>, ComponentProps<typeof AccessibleButton<T>>,
"title" | "onClick" | "size" | "element" "title" | "onClick" | "size" | "element"
> & { > & {
@ -58,7 +58,7 @@ type ButtonProps<T extends keyof JSX.IntrinsicElements> = Omit<
onClick?(ev?: ButtonEvent): void; onClick?(ev?: ButtonEvent): void;
}; };
export const SpaceButton = <T extends keyof JSX.IntrinsicElements>({ export const SpaceButton = <T extends React.ElementType>({
space, space,
spaceKey: _spaceKey, spaceKey: _spaceKey,
className, className,

View file

@ -42,6 +42,7 @@ import HiddenBody from "../components/views/messages/HiddenBody";
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent"; import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline"; import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
import { ElementCall } from "../models/Call"; import { ElementCall } from "../models/Call";
import { IBodyProps } from "../components/views/messages/IBodyProps.ts";
// Subset of EventTile's IProps plus some mixins // Subset of EventTile's IProps plus some mixins
export interface EventTileTypeProps export interface EventTileTypeProps
@ -64,8 +65,8 @@ export interface EventTileTypeProps
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
timestamp?: JSX.Element; timestamp?: JSX.Element;
maxImageHeight?: number; // pixels maxImageHeight?: number; // pixels
overrideBodyTypes?: Record<string, typeof React.Component>; overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
overrideEventTypes?: Record<string, typeof React.Component>; overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
} }
type FactoryProps = Omit<EventTileTypeProps, "ref">; type FactoryProps = Omit<EventTileTypeProps, "ref">;

View file

@ -10,7 +10,7 @@ import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
import { TranslationStringsObject, PlainSubstitution } from "@matrix-org/react-sdk-module-api/lib/types/translations"; import { TranslationStringsObject, PlainSubstitution } from "@matrix-org/react-sdk-module-api/lib/types/translations";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
import React from "react"; import React, { type JSX } from "react";
import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo"; import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
import * as Matrix from "matrix-js-sdk/src/matrix"; import * as Matrix from "matrix-js-sdk/src/matrix";
import { IRegisterRequestParams } from "matrix-js-sdk/src/matrix"; import { IRegisterRequestParams } from "matrix-js-sdk/src/matrix";
@ -78,7 +78,7 @@ export class ProxiedModuleApi implements ModuleApi {
*/ */
public openDialog<M extends object, P extends DialogProps, C extends DialogContent<P>>( public openDialog<M extends object, P extends DialogProps, C extends DialogContent<P>>(
initialTitleOrOptions: string | ModuleUiDialogOptions, initialTitleOrOptions: string | ModuleUiDialogOptions,
body: (props: P, ref: React.RefObject<C | null>) => React.ReactNode, body: (props: P, ref: React.RefObject<C | null>) => JSX.Element,
props?: Omit<P, keyof DialogProps>, props?: Omit<P, keyof DialogProps>,
): Promise<{ didOkOrSubmit: boolean; model: M }> { ): Promise<{ didOkOrSubmit: boolean; model: M }> {
const initialOptions: ModuleUiDialogOptions = const initialOptions: ModuleUiDialogOptions =

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { ReactNode } from "react"; import { type JSX } from "react";
import { createRoot, Root } from "react-dom/client"; import { createRoot, Root } from "react-dom/client";
/** /**
@ -27,7 +27,7 @@ export class ReactRootManager {
* @param rootElement the root element to render the component into * @param rootElement the root element to render the component into
* @param revertElement the element to replace the root element with when unmounting * @param revertElement the element to replace the root element with when unmounting
*/ */
public render(children: ReactNode, rootElement: Element, revertElement?: Element): void { public render(children: JSX.Element, rootElement: Element, revertElement?: Element): void {
const root = createRoot(rootElement); const root = createRoot(rootElement);
this.roots.push(root); this.roots.push(root);
this.rootElements.push(rootElement); this.rootElements.push(rootElement);

View file

@ -12,7 +12,7 @@ Please see LICENSE files in the repository root for full details.
// To ensure we load the browser-matrix version first // To ensure we load the browser-matrix version first
import "matrix-js-sdk/src/browser-index"; import "matrix-js-sdk/src/browser-index";
import React, { ReactElement, StrictMode } from "react"; import React, { type JSX, StrictMode } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { createClient, AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/matrix"; import { createClient, AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/matrix";
import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle"; import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle";
@ -54,7 +54,7 @@ function onTokenLoginCompleted(): void {
window.history.replaceState(null, "", url.href); window.history.replaceState(null, "", url.href);
} }
export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixChat>): Promise<ReactElement<any>> { export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixChat>): Promise<JSX.Element> {
initRouting(); initRouting();
const platform = PlatformPeg.get(); const platform = PlatformPeg.get();

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ReactElement } from "react"; import React, { DOMAttributes, ReactElement } from "react";
import { mocked } from "jest-mock"; import { mocked } from "jest-mock";
import { render, screen } from "jest-matrix-react"; import { render, screen } from "jest-matrix-react";
import { IContent } from "matrix-js-sdk/src/matrix"; import { IContent } from "matrix-js-sdk/src/matrix";
@ -57,8 +57,9 @@ describe("topicToHtml", () => {
}); });
describe("bodyToHtml", () => { describe("bodyToHtml", () => {
function getHtml(content: IContent, highlights?: string[]): string { function getHtml(content: IContent, highlights?: string[]): string | TrustedHTML | undefined {
return (bodyToSpan(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html; return ((bodyToSpan(content, highlights, {}) as ReactElement).props as DOMAttributes<any>)
.dangerouslySetInnerHTML?.__html;
} }
it("should apply highlights to HTML messages", () => { it("should apply highlights to HTML messages", () => {

View file

@ -118,7 +118,7 @@ describe("RoomView", () => {
cleanup(); cleanup();
}); });
const mountRoomView = async (ref?: RefObject<RoomView>): Promise<RenderResult> => { const mountRoomView = async (ref?: RefObject<RoomView | null>): Promise<RenderResult> => {
if (stores.roomViewStore.getRoomId() !== room.roomId) { if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>((resolve) => { const switchedRoom = new Promise<void>((resolve) => {
const subFn = () => { const subFn = () => {

View file

@ -18,7 +18,7 @@ describe("SeekBar", () => {
let playback: Playback; let playback: Playback;
let renderResult: RenderResult; let renderResult: RenderResult;
let frameRequestCallback: FrameRequestCallback; let frameRequestCallback: FrameRequestCallback;
let seekBarRef: RefObject<SeekBar>; let seekBarRef: RefObject<SeekBar | null>;
beforeEach(() => { beforeEach(() => {
seekBarRef = createRef(); seekBarRef = createRef();

View file

@ -34,7 +34,7 @@ jest.mock("../../../../../src/stores/VoiceRecordingStore", () => ({
})); }));
describe("<VoiceRecordComposerTile/>", () => { describe("<VoiceRecordComposerTile/>", () => {
let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile>; let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile | null>;
let mockRecorder: VoiceMessageRecording; let mockRecorder: VoiceMessageRecording;
let mockUpload: IUpload; let mockUpload: IUpload;
let mockClient: MatrixClient; let mockClient: MatrixClient;

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React, { type JSX } from "react";
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo"; import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
@ -236,7 +236,7 @@ describe("ProxiedApiModule", () => {
super(props); super(props);
} }
trySubmit = async () => ({ result: true }); trySubmit = async () => ({ result: true });
render = () => ( render = (): JSX.Element => (
<button type="button" onClick={this.props.cancel}> <button type="button" onClick={this.props.cancel}>
No need for action No need for action
</button> </button>

View file

@ -2702,10 +2702,10 @@
lodash "^4.17.21" lodash "^4.17.21"
redent "^3.0.0" redent "^3.0.0"
"@testing-library/react@^16.0.0": "@testing-library/react@^16.1.0":
version "16.0.1" version "16.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.1.0.tgz#aa0c61398bac82eaf89776967e97de41ac742d71"
integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg== integrity sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg==
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
@ -3126,11 +3126,6 @@
resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404" resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404"
integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA== integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==
"@types/prop-types@*":
version "15.7.13"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
"@types/qrcode@^1.3.5": "@types/qrcode@^1.3.5":
version "1.5.5" version "1.5.5"
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac" resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac"
@ -3155,7 +3150,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-dom@^19": "@types/react-dom@19.0.0", "@types/react-dom@^19":
version "19.0.0" version "19.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130"
integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w== integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==
@ -3179,15 +3174,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*": "@types/react@*", "@types/react@19.0.0", "@types/react@^19":
version "18.3.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f"
integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/react@^19":
version "19.0.0" version "19.0.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf"
integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg== integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==
@ -9932,7 +9919,7 @@ react-clientside-effect@^1.2.6:
dependencies: dependencies:
"@babel/runtime" "^7.12.13" "@babel/runtime" "^7.12.13"
react-dom@^19: react-dom@19.0.0, react-dom@^19:
version "19.0.0" version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ== integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
@ -10016,7 +10003,7 @@ react-transition-group@^4.4.1:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react@^19: react@19.0.0, react@^19:
version "19.0.0" version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd" resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==