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"
},
"resolutions": {
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0",
"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": "npm:wrap-ansi@^7.0.0"
},
@ -179,7 +183,7 @@
"@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^10.4.0",
"@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",
"@types/commonmark": "^0.27.4",
"@types/counterpart": "^0.18.1",

View file

@ -15,5 +15,10 @@ declare module "react" {
): (props: P & React.RefAttributes<T>) => React.ReactElement<any> | null;
// 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.
*/
import React, { Key, MutableRefObject, ReactElement, RefCallback } from "react";
import React, { HTMLAttributes, Key, MutableRefObject, ReactElement, RefCallback } from "react";
interface IChildProps {
style: React.CSSProperties;
@ -68,21 +68,22 @@ export default class NodeAnimator extends React.Component<IProps> {
this.children = {};
React.Children.toArray(newChildren).forEach((c) => {
if (!isReactElement(c)) return;
const props = c.props as HTMLAttributes<HTMLElement>;
if (oldChildren[c.key!]) {
const old = oldChildren[c.key!];
const oldNode = this.nodes[old.key!];
if (oldNode && oldNode.style.left !== c.props.style.left) {
this.applyStyles(oldNode, { left: c.props.style.left });
if (oldNode && oldNode.style.left !== props.style!.left) {
this.applyStyles(oldNode, { left: props.style!.left });
}
// clone the old element with the props (and children) of the new element
// 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 {
// new element. If we have a startStyle, use that as the style and go through
// the enter animations
const newProps: Partial<IChildProps> = {};
const restingStyle = c.props.style;
const restingStyle = props.style!;
const startStyles = this.props.startStyles;
if (startStyles.length > 0) {

View file

@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
scrollIntoView,
onKeyDown,
}) => {
const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, {
const [state, dispatch] = useReducer(reducer, {
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.
*/
import React, { ComponentProps, forwardRef, Ref, type JSX } from "react";
import React, { ComponentProps, forwardRef, Ref } from "react";
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;
// whether the context menu is currently open
isExpanded: boolean;
};
// 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>,
ref: Ref<HTMLElement>,
) {
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
element={element as React.ElementType}
onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined}
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.
*/
import React, { ComponentProps, forwardRef, Ref, type JSX } from "react";
import React, { ComponentProps, forwardRef, Ref } from "react";
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
isExpanded: boolean;
};
// 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>,
ref: Ref<HTMLElement>,
) {
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
element={element as React.ElementType}
onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined}
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.
*/
import React from "react";
import React, { type JSX } from "react";
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;
}
};
// 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;
return (

View file

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

View file

@ -8,16 +8,11 @@ Please see LICENSE files in the repository root for full details.
*/
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> =
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
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;
export type IProps<T extends React.ElementType> = React.ComponentPropsWithoutRef<T> & {
element?: T;
className?: string;
onScroll?: (event: Event) => void;
onWheel?: (event: WheelEvent) => void;
style?: React.CSSProperties;
tabIndex?: number;
@ -25,11 +20,7 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElem
children: ReactNode;
};
export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> extends React.Component<IProps<T>> {
public static defaultProps = {
element: "div" as keyof ReactHTML,
};
export default class AutoHideScrollbar<T extends React.ElementType> extends React.Component<IProps<T>> {
public readonly containerRef: React.RefObject<HTMLDivElement | null> = React.createRef();
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;
return React.createElement(
element,
element ?? "div",
{
...otherProps,
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) {
// Render as a child of the current parent
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.
*/
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";
export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollbarProps<T>, "onWheel" | "element"> & {
element?: T;
export type IProps<T extends React.ElementType> = Omit<ComponentProps<typeof AutoHideScrollbar<T>>, "onWheel"> & {
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element.
@ -30,10 +29,7 @@ interface IState {
rightIndicatorOffset: string;
}
export default class IndicatorScrollbar<T extends keyof JSX.IntrinsicElements> extends React.Component<
IProps<T>,
IState
> {
export default class IndicatorScrollbar<T extends React.ElementType> extends React.Component<IProps<T>, IState> {
private autoHideScrollbar = createRef<AutoHideScrollbar<any>>();
private scrollElement?: HTMLDivElement;
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.
*/
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 {
state: ReducerState<AuthHeaderReducer>;
dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>;
dispatch: Dispatch<AuthHeaderAction>;
}
export const AuthHeaderContext = createContext<AuthHeaderContextType | undefined>(undefined);

View file

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

View file

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

View file

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

View file

@ -13,12 +13,12 @@ import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
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;
inputRef?: Ref;
};
export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
export const TooltipOption = <T extends React.ElementType>({
inputRef,
className,
element,
@ -34,7 +34,7 @@ export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
tabIndex={-1}
aria-selected={isActive}
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.
*/
import React, {
ComponentProps,
forwardRef,
FunctionComponent,
HTMLAttributes,
InputHTMLAttributes,
Ref,
type JSX,
} from "react";
import React, { ComponentProps, ComponentPropsWithRef, FunctionComponent, type JSX, PropsWithChildren } from "react";
import classnames from "classnames";
import { Tooltip } from "@vector-im/compound-web";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
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.
@ -54,25 +46,11 @@ export type AccessibleButtonKind =
*
* To remain compatible with existing code, well continue to support InputHTMLAttributes<Element>
*/
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 keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
type ButtonOwnProps<C extends SupportedElement> = PropsWithChildren<{
/**
* The base element type. "div" by default.
*/
element?: T;
element?: C;
/**
* 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.
*/
onClick: ((e: ButtonEvent) => void | Promise<void>) | null;
onClick: ((e: ButtonEvent<C>) => void | Promise<void>) | null;
/**
* 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.
*/
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.
*/
interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
ref?: React.Ref<Element>;
}
type RenderedElementProps<T extends SupportedElement> = ComponentPropsWithRef<T> & {
// 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
@ -132,27 +140,27 @@ interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
* @param {Object} props react element properties
* @returns {Object} rendered react
*/
const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
{
element = "div" as T,
onClick,
children,
kind,
disabled,
className,
onKeyDown,
onKeyUp,
triggerOnMouseDown,
title,
caption,
placement = "right",
onTooltipOpenChange,
disableTooltip,
...restProps
}: Props<T>,
ref: Ref<HTMLElement>,
): JSX.Element {
const newProps: RenderedElementProps = restProps;
const AccessibleButton = function <T extends SupportedElement>({
element = "div" as T,
onClick,
children,
kind,
disabled,
className,
onKeyDown,
onKeyUp,
triggerOnMouseDown,
title,
caption,
placement = "right",
onTooltipOpenChange,
disableTooltip,
role = "button",
tabIndex = 0,
ref,
...restProps
}: Props<T>): JSX.Element {
const newProps: RenderedElementProps<any> = { ...restProps, role, tabIndex };
newProps["aria-label"] = newProps["aria-label"] ?? title;
if (disabled) {
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.
// Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here
newProps.onKeyDown = (e) => {
newProps.onKeyDown = (e: React.KeyboardEvent<T>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
@ -232,13 +240,9 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
);
}
return button;
});
};
// 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";
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.
*/
import React, {
ContextType,
createRef,
CSSProperties,
MutableRefObject,
ReactNode,
type JSX,
} from "react";
import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode, type JSX } from "react";
import classNames from "classnames";
import { IWidget, MatrixCapabilities } from "matrix-widget-api";
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;
};
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.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.
// This may be useful when displaying a preview of the event.
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;
}
interface IProps extends IBodyProps {
/* whether or not to show the default placeholder for the file. Defaults to true. */
showGenericPlaceholder: boolean;
}
interface IState {
decryptedBlob?: Blob;
}
export default class MFileBody extends React.Component<IProps, IState> {
export default class MFileBody extends React.Component<IBodyProps, IState> {
public static contextType = 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) {
this.props.onHeightChanged();
}

View file

@ -58,7 +58,7 @@ export interface IOperableEventTile {
getEventTileOps(): IEventTileOps | null;
}
const baseBodyTypes = new Map<string, typeof React.Component>([
const baseBodyTypes = new Map<string, React.ComponentType<IBodyProps>>([
[MsgType.Text, TextualBody],
[MsgType.Notice, 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 {
private body: React.RefObject<React.Component | IOperableEventTile | null> = createRef();
private mediaHelper?: MediaEventHelper;
private bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries());
private evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries());
private bodyTypes = new Map(baseBodyTypes.entries());
private evTypes = new Map(baseEvTypes.entries());
public static contextType = MatrixClientContext;
declare public context: React.ContextType<typeof MatrixClientContext>;
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context);
public constructor(props: IProps) {
super(props);
if (MediaEventHelper.isEligible(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 {
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 ?? {})) {
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 ?? {})) {
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.
*/
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 { logger } from "matrix-js-sdk/src/logger";
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
if (event.target.open) {
if (event.currentTarget.open) {
// if local aliases haven't been preloaded yet at component mount
if (!this.props.canSetCanonicalAlias && this.state.localAliases.length === 0) {
this.loadLocalAliases();

View file

@ -26,6 +26,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { renderReplyTile } from "../../../events/EventTileFactory";
import { GetRelationsForEvent } from "../rooms/EventTile";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { IBodyProps } from "../messages/IBodyProps.ts";
interface IProps {
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,
// Override audio and video body with file body. We also hide the download/decrypt button using CSS
[MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : 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
[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 React, { HTMLAttributes, ReactHTML, type JSX } from "react";
import React, { ElementType, HTMLAttributes, type JSX } from "react";
import { roomContextDetails } from "../../../utils/i18n-helpers";
type Props<T extends keyof ReactHTML> = HTMLAttributes<T> & {
type Props<T extends ElementType> = HTMLAttributes<T> & {
component?: T;
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);
if (contextDetails) {
return React.createElement(

View file

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

View file

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

View file

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

View file

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

View file

@ -42,6 +42,7 @@ import HiddenBody from "../components/views/messages/HiddenBody";
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
import { ElementCall } from "../models/Call";
import { IBodyProps } from "../components/views/messages/IBodyProps.ts";
// Subset of EventTile's IProps plus some mixins
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
timestamp?: JSX.Element;
maxImageHeight?: number; // pixels
overrideBodyTypes?: Record<string, typeof React.Component>;
overrideEventTypes?: Record<string, typeof React.Component>;
overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
}
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 { Optional } from "matrix-events-sdk";
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 * as Matrix 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>>(
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>,
): Promise<{ didOkOrSubmit: boolean; model: M }> {
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.
*/
import { ReactNode } from "react";
import { type JSX } from "react";
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 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);
this.roots.push(root);
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
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 { createClient, AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/matrix";
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);
}
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();
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.
*/
import React, { ReactElement } from "react";
import React, { DOMAttributes, ReactElement } from "react";
import { mocked } from "jest-mock";
import { render, screen } from "jest-matrix-react";
import { IContent } from "matrix-js-sdk/src/matrix";
@ -57,8 +57,9 @@ describe("topicToHtml", () => {
});
describe("bodyToHtml", () => {
function getHtml(content: IContent, highlights?: string[]): string {
return (bodyToSpan(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html;
function getHtml(content: IContent, highlights?: string[]): string | TrustedHTML | undefined {
return ((bodyToSpan(content, highlights, {}) as ReactElement).props as DOMAttributes<any>)
.dangerouslySetInnerHTML?.__html;
}
it("should apply highlights to HTML messages", () => {

View file

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

View file

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

View file

@ -34,7 +34,7 @@ jest.mock("../../../../../src/stores/VoiceRecordingStore", () => ({
}));
describe("<VoiceRecordComposerTile/>", () => {
let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile>;
let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile | null>;
let mockRecorder: VoiceMessageRecording;
let mockUpload: IUpload;
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.
*/
import React from "react";
import React, { type JSX } from "react";
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 { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
@ -236,7 +236,7 @@ describe("ProxiedApiModule", () => {
super(props);
}
trySubmit = async () => ({ result: true });
render = () => (
render = (): JSX.Element => (
<button type="button" onClick={this.props.cancel}>
No need for action
</button>

View file

@ -2702,10 +2702,10 @@
lodash "^4.17.21"
redent "^3.0.0"
"@testing-library/react@^16.0.0":
version "16.0.1"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875"
integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==
"@testing-library/react@^16.1.0":
version "16.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.1.0.tgz#aa0c61398bac82eaf89776967e97de41ac742d71"
integrity sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg==
dependencies:
"@babel/runtime" "^7.12.5"
@ -3126,11 +3126,6 @@
resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404"
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":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac"
@ -3155,7 +3150,7 @@
dependencies:
"@types/react" "*"
"@types/react-dom@^19":
"@types/react-dom@19.0.0", "@types/react-dom@^19":
version "19.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130"
integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==
@ -3179,15 +3174,7 @@
dependencies:
"@types/react" "*"
"@types/react@*":
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":
"@types/react@*", "@types/react@19.0.0", "@types/react@^19":
version "19.0.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf"
integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==
@ -9932,7 +9919,7 @@ react-clientside-effect@^1.2.6:
dependencies:
"@babel/runtime" "^7.12.13"
react-dom@^19:
react-dom@19.0.0, react-dom@^19:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
@ -10016,7 +10003,7 @@ react-transition-group@^4.4.1:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@^19:
react@19.0.0, react@^19:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==