Replace usage of forwardRef with React 19 ref prop (#29803)

* Replace usage of `forwardRef` with React 19 ref prop

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add lint rule

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2025-04-24 13:31:37 +01:00
committed by GitHub
parent 5e7b58a722
commit 22d5c00174
48 changed files with 989 additions and 963 deletions

View File

@@ -30,6 +30,10 @@ module.exports = {
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
"Use UIStore to access window dimensions instead.",
),
...buildRestrictedPropertiesOptions(
["React.forwardRef", "*.forwardRef", "forwardRef"],
"Use ref props instead.",
),
...buildRestrictedPropertiesOptions(
["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
"Use Media helper instead to centralise access for customisation.",
@@ -55,6 +59,11 @@ module.exports = {
"error",
{
paths: [
{
name: "react",
importNames: ["forwardRef"],
message: "Use ref props instead.",
},
{
name: "@testing-library/react",
message: "Please use jest-matrix-react instead",

View File

@@ -6,16 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type PropsWithChildren } from "react";
import type React from "react";
import { type ComponentType } from "react";
declare module "react" {
// Fix forwardRef types for Generic components - https://stackoverflow.com/a/58473012
function forwardRef<T, P extends object>(
render: (props: PropsWithChildren<P>, ref: React.ForwardedRef<T>) => React.ReactElement | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
// Fix lazy types - https://stackoverflow.com/a/71017028
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;

View File

@@ -6,18 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef } from "react";
import React, { type Ref, type JSX } from "react";
import { RovingTabIndexProvider } from "./RovingTabIndex";
import { getKeyBindingsManager } from "../KeyBindingsManager";
import { KeyBindingAction } from "./KeyboardShortcuts";
interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {}
interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {
ref?: Ref<HTMLDivElement>;
}
// This component implements the Toolbar design pattern from the WAI-ARIA Authoring Practices guidelines.
// https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar
// All buttons passed in children must use RovingTabIndex to set `onFocus`, `isActive`, `ref`
const Toolbar = forwardRef<HTMLDivElement, IProps>(({ children, ...props }, ref) => {
const Toolbar = ({ children, ref, ...props }: IProps): JSX.Element => {
const onKeyDown = (ev: React.KeyboardEvent): void => {
const target = ev.target as HTMLElement;
// Don't interfere with input default keydown behaviour
@@ -55,6 +57,6 @@ const Toolbar = forwardRef<HTMLDivElement, IProps>(({ children, ...props }, ref)
)}
</RovingTabIndexProvider>
);
});
};
export default Toolbar;

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef, type Ref } from "react";
import React, { type Ref, type JSX } from "react";
import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";
@@ -16,13 +16,19 @@ type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
label?: string;
// whether the context menu is currently open
isExpanded: boolean;
ref?: Ref<HTMLElementTagNameMap[T]>;
};
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
{ label, isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
) {
export const ContextMenuButton = function <T extends keyof HTMLElementTagNameMap>({
label,
isExpanded,
children,
onClick,
onContextMenu,
ref,
...props
}: Props<T>): JSX.Element {
return (
<AccessibleButton
{...props}
@@ -36,4 +42,4 @@ export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElemen
{children}
</AccessibleButton>
);
});
};

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef, type Ref } from "react";
import React, { type JSX } from "react";
import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";
@@ -18,10 +18,14 @@ type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
};
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
{ isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
) {
export const ContextMenuTooltipButton = function <T extends keyof HTMLElementTagNameMap>({
isExpanded,
children,
onClick,
onContextMenu,
ref,
...props
}: Props<T>): JSX.Element {
return (
<AccessibleButton
{...props}
@@ -35,4 +39,4 @@ export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTM
{children}
</AccessibleButton>
);
});
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef } from "react";
import React, { type Ref, type JSX } from "react";
import classNames from "classnames";
/* These were earlier stateless functional components but had to be converted
@@ -16,14 +16,24 @@ presumably wrap them in a <div> before rendering but I think this is the better
*/
interface ITextualCompletionProps {
title?: string;
subtitle?: string;
description?: string;
className?: string;
"title"?: string;
"subtitle"?: string;
"description"?: string;
"className"?: string;
"aria-selected"?: boolean;
"ref"?: Ref<HTMLDivElement>;
}
export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props, ref) => {
const { title, subtitle, description, className, "aria-selected": ariaSelectedAttribute, ...restProps } = props;
export const TextualCompletion = (props: ITextualCompletionProps): JSX.Element => {
const {
title,
subtitle,
description,
className,
"aria-selected": ariaSelectedAttribute,
ref,
...restProps
} = props;
return (
<div
{...restProps}
@@ -37,13 +47,13 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
<span className="mx_Autocomplete_Completion_description">{description}</span>
</div>
);
});
};
interface IPillCompletionProps extends ITextualCompletionProps {
children?: React.ReactNode;
}
export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref) => {
export const PillCompletion = (props: IPillCompletionProps): JSX.Element => {
const {
title,
subtitle,
@@ -51,6 +61,7 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
className,
children,
"aria-selected": ariaSelectedAttribute,
ref,
...restProps
} = props;
return (
@@ -67,4 +78,4 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
<span className="mx_Autocomplete_Completion_description">{description}</span>
</div>
);
});
};

View File

@@ -127,7 +127,7 @@ export default class UserProvider extends AutocompleteProvider {
suffix: selection.beginning && range!.start === 0 ? ": " : " ",
href: makeUserPermalink(user.userId),
component: (
<PillCompletion title={displayName} description={description}>
<PillCompletion title={displayName} description={description ?? undefined}>
<MemberAvatar member={user} size="24px" />
</PillCompletion>
),

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type PropsWithChildren, useEffect, useState } from "react";
import React, { type PropsWithChildren, useEffect, useState, type JSX } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
import { logger } from "matrix-js-sdk/src/logger";
@@ -85,7 +85,7 @@ interface Props {
* A React component which exposes a {@link MatrixClientContext} and a {@link LocalDeviceVerificationStateContext}
* to its children.
*/
export function MatrixClientContextProvider(props: PropsWithChildren<Props>): React.JSX.Element {
export function MatrixClientContextProvider(props: PropsWithChildren<Props>): JSX.Element {
const verificationState = useLocalVerificationState(props.client);
return (
<MatrixClientContext.Provider value={props.client}>

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import React, { type JSX, type Ref, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import {
type ISearchResults,
type IThreadBundledRelationship,
@@ -44,12 +44,22 @@ interface Props {
resizeNotifier: ResizeNotifier;
className: string;
onUpdate(inProgress: boolean, results: ISearchResults | null, error: Error | null): void;
ref?: Ref<ScrollPanel>;
}
// XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work?
export const RoomSearchView = forwardRef<ScrollPanel, Props>(
({ term, scope, promise, abortController, resizeNotifier, className, onUpdate, inProgress }: Props, ref) => {
export const RoomSearchView = ({
term,
scope,
promise,
abortController,
resizeNotifier,
className,
onUpdate,
inProgress,
ref,
}: Props): JSX.Element => {
const client = useContext(MatrixClientContext);
const roomContext = useScopedRoomContext("showHiddenEvents");
const [highlights, setHighlights] = useState<string[] | null>(null);
@@ -97,8 +107,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
for (const result of results.results) {
for (const event of result.context.getTimeline()) {
const bundledRelationship =
event.getServerAggregatedRelation<IThreadBundledRelationship>(
const bundledRelationship = event.getServerAggregatedRelation<IThreadBundledRelationship>(
THREAD_RELATION_TYPE.name,
);
if (!bundledRelationship || event.getThread()) continue;
@@ -261,9 +270,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
// add the index of the matching event of the next searchResult
ourEventsIndexes.push(
ourEventsIndexes[ourEventsIndexes.length - 1] +
results.results[i - 1].context.getOurEventIndex() +
1,
ourEventsIndexes[ourEventsIndexes.length - 1] + results.results[i - 1].context.getOurEventIndex() + 1,
);
continue;
@@ -308,5 +315,4 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
{ret}
</ScrollPanel>
);
},
);
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import React, { type JSX } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
@@ -43,13 +43,13 @@ type MigrationState = {
/**
* The view that is displayed after we have logged in, before the first /sync is completed.
*/
export function LoginSplashView(props: Props): React.JSX.Element {
export function LoginSplashView(props: Props): JSX.Element {
const migrationState = useTypedEventEmitterState(
props.matrixClient,
CryptoEvent.LegacyCryptoStoreMigrationProgress,
(progress?: number, total?: number): MigrationState => ({ progress: progress ?? -1, totalSteps: total ?? -1 }),
);
let errorBox: React.JSX.Element | undefined;
let errorBox: JSX.Element | undefined;
if (props.syncError) {
errorBox = <div className="mx_LoginSplashView_syncError">{messageForSyncError(props.syncError)}</div>;
}

View File

@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type AriaRole, forwardRef, useCallback, useContext, useEffect, useState } from "react";
import React, { type AriaRole, type JSX, type Ref, useCallback, useContext, useEffect, useState } from "react";
import classNames from "classnames";
import { ClientEvent, type SyncState } from "matrix-js-sdk/src/matrix";
import { Avatar } from "@vector-im/compound-web";
@@ -34,6 +34,7 @@ interface IProps {
tabIndex?: number;
altText?: string;
role?: AriaRole;
ref?: Ref<HTMLElement>;
}
const calculateUrls = (url?: string | null, urls?: string[], lowBandwidth = false): string[] => {
@@ -87,7 +88,7 @@ const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [
return [imageUrl, onError];
};
const BaseAvatar = forwardRef<HTMLElement, IProps>((props, ref) => {
const BaseAvatar = (props: IProps): JSX.Element => {
const {
name,
idName,
@@ -99,6 +100,7 @@ const BaseAvatar = forwardRef<HTMLElement, IProps>((props, ref) => {
className,
type = "round",
altText = _t("common|avatar"),
ref,
...otherProps
} = props;
@@ -134,7 +136,7 @@ const BaseAvatar = forwardRef<HTMLElement, IProps>((props, ref) => {
data-testid="avatar-img"
/>
);
});
};
export default BaseAvatar;
export type BaseAvatarType = React.FC<IProps>;

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, forwardRef, type ReactNode, type Ref, useContext } from "react";
import React, { type JSX, type ReactNode, type Ref, useContext } from "react";
import { type RoomMember, type ResizeMethod } from "matrix-js-sdk/src/matrix";
import dis from "../../../dispatcher/dispatcher";
@@ -33,10 +33,10 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
forceHistorical?: boolean; // true to deny `useOnlyCurrentProfiles` usage. Default false.
hideTitle?: boolean;
children?: ReactNode;
ref?: Ref<HTMLElement>;
}
function MemberAvatar(
{
export default function MemberAvatar({
size,
resizeMethod = "crop",
viewUserOnClick,
@@ -44,10 +44,9 @@ function MemberAvatar(
fallbackUserId,
hideTitle,
member: propsMember,
ref,
...props
}: IProps,
ref: Ref<HTMLElement>,
): JSX.Element {
}: IProps): JSX.Element {
const cli = useContext(MatrixClientContext);
const card = useContext(CardContext);
@@ -101,5 +100,3 @@ function MemberAvatar(
/>
);
}
export default forwardRef(MemberAvatar);

View File

@@ -10,8 +10,6 @@ import React, {
type JSX,
type ComponentProps,
type ComponentPropsWithoutRef,
forwardRef,
type FunctionComponent,
type ReactElement,
type KeyboardEvent,
type Ref,
@@ -100,6 +98,8 @@ type Props<T extends ElementType = "div"> = {
* Whether the tooltip should be disabled.
*/
disableTooltip?: TooltipProps["disabled"];
ref?: Ref<HTMLElementTagNameMap[T]>;
};
export type ButtonProps<T extends ElementType> = Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>;
@@ -119,8 +119,7 @@ type RenderedElementProps<T extends ElementType> = React.InputHTMLAttributes<Ele
* @param {Object} props react element properties
* @returns {Object} rendered react
*/
const AccessibleButton = forwardRef(function <T extends ElementType = typeof defaultElement>(
{
const AccessibleButton = function AccessibleButton<T extends ElementType = typeof defaultElement>({
element,
onClick,
children,
@@ -137,10 +136,9 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
disableTooltip,
role = "button",
tabIndex = 0,
ref,
...restProps
}: ButtonProps<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
): JSX.Element {
}: ButtonProps<T>): JSX.Element {
const newProps = {
...restProps,
tabIndex,
@@ -226,10 +224,7 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
);
}
return button;
});
// Type assertion required due to forwardRef type workaround in react.d.ts
(AccessibleButton as FunctionComponent).displayName = "AccessibleButton";
};
interface RefProp<T extends ElementType> {
ref?: Ref<HTMLElementTagNameMap[T]>;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type ReactNode, useState } from "react";
import React, { type JSX, type ReactNode, type Ref, useState } from "react";
import classNames from "classnames";
import { type RoomMember } from "matrix-js-sdk/src/matrix";
import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin-solid";
@@ -21,6 +21,7 @@ interface Props {
// use member text color as background
useMemberColor?: boolean;
tooltip?: ReactNode;
ref?: Ref<HTMLDivElement>;
}
/**
@@ -55,7 +56,7 @@ const OptionalTooltip: React.FC<{
/**
* Generic location marker
*/
const Marker = React.forwardRef<HTMLDivElement, Props>(({ id, roomMember, useMemberColor, tooltip }, ref) => {
const Marker = ({ id, roomMember, useMemberColor, tooltip, ref }: Props): JSX.Element => {
const memberColorClass = useMemberColor && roomMember ? getUserNameColorClass(roomMember.userId) : "";
return (
<div
@@ -82,6 +83,6 @@ const Marker = React.forwardRef<HTMLDivElement, Props>(({ id, roomMember, useMem
</OptionalTooltip>
</div>
);
});
};
export default Marker;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef, useCallback, useContext, useMemo } from "react";
import React, { type Ref, useCallback, useContext, useMemo, type JSX } from "react";
import type { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import { ConnectionState, type ElementCall } from "../../../models/Call";
@@ -37,10 +37,19 @@ interface ActiveCallEventProps {
buttonKind: AccessibleButtonKind;
buttonDisabledTooltip?: string;
onButtonClick: ((ev: ButtonEvent) => void) | null;
ref?: Ref<HTMLDivElement>;
}
const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
({ mxEvent, call, participatingMembers, buttonText, buttonKind, buttonDisabledTooltip, onButtonClick }, ref) => {
const ActiveCallEvent = ({
mxEvent,
call,
participatingMembers,
buttonText,
buttonKind,
buttonDisabledTooltip,
onButtonClick,
ref,
}: ActiveCallEventProps): JSX.Element => {
const senderName = useMemo(() => mxEvent.sender?.name ?? mxEvent.getSender(), [mxEvent]);
const facePileMembers = useMemo(() => participatingMembers.slice(0, MAX_FACES), [participatingMembers]);
@@ -82,15 +91,15 @@ const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
</div>
</div>
);
},
);
};
interface ActiveLoadedCallEventProps {
mxEvent: MatrixEvent;
call: ElementCall;
ref?: Ref<HTMLDivElement>;
}
const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxEvent, call }, ref) => {
const ActiveLoadedCallEvent = ({ mxEvent, call, ref }: ActiveLoadedCallEventProps): JSX.Element => {
const connectionState = useConnectionState(call);
const participatingMembers = useParticipatingMembers(call);
const joinCallButtonDisabledTooltip = useJoinCallButtonDisabledTooltip(call);
@@ -141,16 +150,17 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
onButtonClick={onButtonClick}
/>
);
});
};
interface CallEventProps {
mxEvent: MatrixEvent;
ref?: Ref<HTMLDivElement>;
}
/**
* An event tile representing an active or historical Element call.
*/
export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
export const CallEvent = ({ mxEvent, ref }: CallEventProps): JSX.Element => {
const client = useContext(MatrixClientContext);
const call = useCall(mxEvent.getRoomId()!);
const latestEvent = client
@@ -187,4 +197,4 @@ export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
}
return <ActiveLoadedCallEvent mxEvent={mxEvent} call={call as ElementCall} ref={ref} />;
});
};

View File

@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import classNames from "classnames";
import React, { forwardRef, type ForwardRefExoticComponent, useContext } from "react";
import React, { type JSX, useContext } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
import { BlockIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
@@ -16,7 +16,7 @@ import { _t } from "../../../languageHandler";
import { type IBodyProps } from "./IBodyProps";
import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string | React.JSX.Element {
function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string | JSX.Element {
switch (mxEvent.decryptionFailureReason) {
case DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE:
return _t("timeline|decryption_failure|blocked");
@@ -72,7 +72,7 @@ function errorClassName(mxEvent: MatrixEvent): string | null {
}
// A placeholder element for messages that could not be decrypted
export const DecryptionFailureBody = forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent }, ref): React.JSX.Element => {
export const DecryptionFailureBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
const verificationState = useContext(LocalDeviceVerificationStateContext);
const classes = classNames("mx_DecryptionFailureBody", "mx_EventTile_content", errorClassName(mxEvent));
@@ -81,4 +81,4 @@ export const DecryptionFailureBody = forwardRef<HTMLDivElement, IBodyProps>(({ m
{getErrorMessage(mxEvent, verificationState)}
</div>
);
}) as ForwardRefExoticComponent<IBodyProps>;
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, forwardRef } from "react";
import React, { type JSX, type Ref, type ReactNode } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
@@ -22,9 +22,10 @@ import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
interface IProps {
mxEvent: MatrixEvent;
timestamp?: JSX.Element;
ref?: Ref<HTMLDivElement>;
}
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => {
const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
const cli = useMatrixClientContext();
const roomId = mxEvent.getRoomId()!;
const isRoomEncrypted = useIsEncrypted(cli, cli.getRoom(roomId) || undefined);
@@ -80,6 +81,6 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
timestamp={timestamp}
/>
);
});
};
export default EncryptionEvent;

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { memo, forwardRef, useContext, useMemo } from "react";
import React, { memo, useContext, useMemo, type Ref } from "react";
import { type IContent, type MatrixEvent, MsgType, PushRuleKind } from "matrix-js-sdk/src/matrix";
import parse from "html-react-parser";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
@@ -139,6 +139,7 @@ interface Props extends ReplacerOptions {
* Whether to include the `dir="auto"` attribute on the rendered element
*/
includeDir?: boolean;
ref?: Ref<HTMLElement>;
}
/**
@@ -148,8 +149,7 @@ interface Props extends ReplacerOptions {
* Returns a div or span depending on `as`, the `dir` on a `div` is always set to `"auto"` but set by `includeDir` otherwise.
*/
const EventContentBody = memo(
forwardRef<HTMLElement, Props>(
({ as, mxEvent, stripReply, content, linkify, highlights, includeDir = true, ...options }, ref) => {
({ as, mxEvent, stripReply, content, linkify, highlights, includeDir = true, ref, ...options }: Props) => {
const enableBigEmoji = useSettingValue("TextualBody.enableBigEmoji");
const [mediaIsVisible] = useMediaVisible(mxEvent?.getId(), mxEvent?.getRoomId());
@@ -193,7 +193,6 @@ const EventContentBody = memo(
return <Linkify options={linkifyOptions}>{body}</Linkify>;
},
),
);
export default EventContentBody;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, forwardRef, type ReactNode } from "react";
import React, { type JSX, type ReactNode, type Ref } from "react";
import classNames from "classnames";
interface IProps {
@@ -15,10 +15,10 @@ interface IProps {
timestamp?: JSX.Element;
subtitle?: ReactNode;
children?: JSX.Element;
ref?: Ref<HTMLDivElement>;
}
const EventTileBubble = forwardRef<HTMLDivElement, IProps>(
({ className, title, timestamp, subtitle, children }, ref) => {
const EventTileBubble = ({ className, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
return (
<div className={classNames("mx_EventTileBubble", className)} ref={ref}>
<div className="mx_EventTileBubble_title">{title}</div>
@@ -27,7 +27,6 @@ const EventTileBubble = forwardRef<HTMLDivElement, IProps>(
{timestamp}
</div>
);
},
);
};
export default EventTileBubble;

View File

@@ -6,23 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import React, { type JSX } from "react";
import { _t } from "../../../languageHandler";
import { type IBodyProps } from "./IBodyProps";
interface IProps {
mxEvent: MatrixEvent;
}
/**
* A message hidden from the user pending moderation.
*
* Note: This component must not be used when the user is the author of the message
* or has a sufficient powerlevel to see the message.
*/
const HiddenBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, ref) => {
const HiddenBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
let text;
const visibility = mxEvent.messageVisibility();
switch (visibility.visible) {
@@ -42,6 +37,6 @@ const HiddenBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, ref)
{text}
</span>
);
});
};
export default HiddenBody;

View File

@@ -6,7 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type LegacyRef } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import type React from "react";
@@ -44,7 +43,7 @@ export interface IBodyProps {
// helper function to access relations for this event
getRelationsForEvent?: GetRelationsForEvent;
ref?: React.RefObject<any> | LegacyRef<any>;
ref?: React.RefObject<any>;
// 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.

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ForwardRefExoticComponent, useCallback, useContext, useEffect, useState } from "react";
import React, { type JSX, useCallback, useContext, useEffect, useState } from "react";
import {
type Beacon,
BeaconEvent,
@@ -122,7 +122,7 @@ const useHandleBeaconRedaction = (
}, [event, onBeforeBeaconInfoRedaction]);
};
const MBeaconBody = React.forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent, getRelationsForEvent }, ref) => {
const MBeaconBody = ({ mxEvent, getRelationsForEvent, ref }: IBodyProps): JSX.Element => {
const { beacon, isLive, latestLocationState, waitingToStart } = useBeaconState(mxEvent);
const mapId = useUniqueId(mxEvent.getId()!);
@@ -225,6 +225,6 @@ const MBeaconBody = React.forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent, get
)}
</div>
);
}) as ForwardRefExoticComponent<IBodyProps>;
};
export default MBeaconBody;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { useEffect, useState, useContext, type ForwardRefExoticComponent } from "react";
import React, { useEffect, useState, useContext, type JSX } from "react";
import { MatrixEvent, M_TEXT } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
@@ -85,7 +85,7 @@ const usePollStartEvent = (event: MatrixEvent): { pollStartEvent?: MatrixEvent;
return { pollStartEvent, isLoadingPollStartEvent };
};
export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...props }, ref) => {
export const MPollEndBody = ({ mxEvent, ref, ...props }: IBodyProps): JSX.Element => {
const cli = useMatrixClientContext();
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);
@@ -105,4 +105,4 @@ export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...pro
<MPollBody mxEvent={pollStartEvent} {...props} />
</div>
);
}) as ForwardRefExoticComponent<IBodyProps>;
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type ForwardRefExoticComponent, useContext } from "react";
import React, { useContext, type JSX } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler";
@@ -15,7 +15,7 @@ import { formatFullDate } from "../../../DateUtils";
import SettingsStore from "../../../settings/SettingsStore";
import { type IBodyProps } from "./IBodyProps";
const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
const RedactedBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
const cli: MatrixClient = useContext(MatrixClientContext);
let text = _t("timeline|self_redaction");
const unsigned = mxEvent.getUnsigned();
@@ -37,6 +37,6 @@ const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
{text}
</span>
);
}) as ForwardRefExoticComponent<IBodyProps>;
};
export default RedactedBody;

View File

@@ -7,16 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef, type ForwardRefExoticComponent } from "react";
import React, { type JSX } from "react";
import { type IBodyProps } from "./IBodyProps";
export default forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent, children }, ref) => {
export default ({ mxEvent, ref }: IBodyProps): JSX.Element => {
const text = mxEvent.getContent().body;
return (
<div className="mx_UnknownBody" ref={ref}>
{text}
{children}
</div>
);
}) as ForwardRefExoticComponent<IBodyProps>;
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef, type ReactNode, type KeyboardEvent, type Ref, type MouseEvent } from "react";
import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent } from "react";
import classNames from "classnames";
import { IconButton, Text } from "@vector-im/compound-web";
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
@@ -44,9 +44,7 @@ function closeRightPanel(ev: MouseEvent<HTMLButtonElement>): void {
RightPanelStore.instance.popCard();
}
const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
(
{
const BaseCard: React.FC<IProps> = ({
closeLabel,
onClose,
onBack,
@@ -61,9 +59,8 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
children,
onKeyDown,
closeButtonRef,
},
ref,
) => {
}: IProps) => {
let backButton;
const cardHistory = RightPanelStore.instance.roomPhaseHistory;
if (cardHistory.length > 1 && !hideHeaderButtons) {
@@ -143,7 +140,6 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
</div>
</CardContext.Provider>
);
},
);
};
export default BaseCard;

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { createRef, forwardRef, type JSX, type MouseEvent, type ReactNode } from "react";
import React, { createRef, type JSX, type Ref, type MouseEvent, type ReactNode } from "react";
import classNames from "classnames";
import {
EventStatus,
@@ -228,6 +228,8 @@ export interface EventTileProps {
// The following properties are used by EventTilePreview to disable tab indexes within the event tile
hideTimestamp?: boolean;
inhibitInteraction?: boolean;
ref?: Ref<UnwrappedEventTile>;
}
interface IState {
@@ -1482,15 +1484,13 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}
// Wrap all event tiles with the tile error boundary so that any throws even during construction are captured
const SafeEventTile = forwardRef<UnwrappedEventTile, EventTileProps>((props, ref) => {
const SafeEventTile = (props: EventTileProps): JSX.Element => {
return (
<>
<TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}>
<UnwrappedEventTile ref={ref} {...props} />
<UnwrappedEventTile {...props} />
</TileErrorBoundary>
</>
);
});
};
export default SafeEventTile;
function E2ePadlockUnencrypted(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {

View File

@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/
import { Form } from "@vector-im/compound-web";
import React from "react";
import React, { type JSX } from "react";
import { List, type ListRowProps } from "react-virtualized/dist/commonjs/List";
import { AutoSizer } from "react-virtualized";
@@ -33,7 +33,7 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
const totalRows = vm.members.length;
const getRowComponent = (item: MemberWithSeparator): React.JSX.Element => {
const getRowComponent = (item: MemberWithSeparator): JSX.Element => {
if (item === SEPARATOR) {
return <hr className="mx_MemberListView_separator" />;
} else if (item.member) {
@@ -64,7 +64,7 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
}
};
const rowRenderer = ({ key, index, style }: ListRowProps): React.JSX.Element => {
const rowRenderer = ({ key, index, style }: ListRowProps): JSX.Element => {
if (index === totalRows) {
// We've rendered all the members,
// now we render an empty div to add some space to the end of the list.

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import React, { type JSX } from "react";
import { Tooltip } from "@vector-im/compound-web";
import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
@@ -14,7 +14,7 @@ import { _t } from "../../../../../../languageHandler";
import { E2EStatus } from "../../../../../../utils/ShieldUtils";
import { crossSigningUserTitles } from "../../../E2EIcon";
function getIconFromStatus(status: E2EStatus): React.JSX.Element | undefined {
function getIconFromStatus(status: E2EStatus): JSX.Element | undefined {
switch (status) {
case E2EStatus.Normal:
return undefined;

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import React, { type JSX } from "react";
import OnlineOrUnavailableIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-solid-8x8";
import OfflineIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-outline-8x8";
import DNDIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-strikethrough-8x8";
@@ -19,7 +19,7 @@ interface Props {
export const BUSY_PRESENCE_NAME = new UnstableValue("busy", "org.matrix.msc3026.busy");
function getIconForPresenceState(state: string): React.JSX.Element {
function getIconForPresenceState(state: string): JSX.Element {
switch (state) {
case "online":
return <OnlineOrUnavailableIcon height="8px" width="8px" className="mx_PresenceIconView_online" />;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { forwardRef } from "react";
import React, { type Ref, type JSX, type ReactNode } from "react";
import classNames from "classnames";
import { formatCount } from "../../../../utils/FormattingUtils";
@@ -26,6 +26,8 @@ interface Props {
* for the difference between the two.
*/
forceDot?: boolean;
children?: ReactNode;
ref?: Ref<HTMLDivElement>;
}
interface ClickableProps extends Props {
@@ -45,8 +47,14 @@ interface ClickableProps extends Props {
* notifications in the room list, it may have a green badge with the number of unread notifications,
* but somewhere else it may just have a green dot as a more compact representation of the same information.
*/
export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props, ClickableProps>>(
({ symbol, count, level, knocked, forceDot = false, ...props }, ref) => {
export const StatelessNotificationBadge = ({
symbol,
count,
level,
knocked,
forceDot = false,
...props
}: XOR<Props, ClickableProps>): JSX.Element => {
const hideBold = useSettingValue("feature_hidebold");
// Don't show a badge if we don't need to
@@ -89,7 +97,7 @@ export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props,
if (props.onClick) {
return (
<AccessibleButton {...props} className={classes} onClick={props.onClick} ref={ref}>
<AccessibleButton {...props} className={classes} onClick={props.onClick} ref={props.ref}>
<span className="mx_NotificationBadge_count">{symbol}</span>
{props.children}
</AccessibleButton>
@@ -97,9 +105,8 @@ export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props,
}
return (
<div className={classes} ref={ref}>
<div className={classes} ref={props.ref}>
<span className="mx_NotificationBadge_count">{symbol}</span>
</div>
);
},
);
};

View File

@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/
import React, { type ComponentProps, forwardRef, type JSX, useState } from "react";
import React, { type ComponentProps, type JSX, type Ref, useState } from "react";
import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem, Tooltip } from "@vector-im/compound-web";
import MarkAsReadIcon from "@vector-im/compound-design-tokens/assets/web/icons/mark-as-read";
import MarkAsUnreadIcon from "@vector-im/compound-design-tokens/assets/web/icons/mark-as-unread";
@@ -147,22 +147,22 @@ function MoreOptionsMenu({ vm, setMenuOpen }: MoreOptionsMenuProps): JSX.Element
);
}
interface MoreOptionsButtonProps extends ComponentProps<typeof IconButton> {}
interface MoreOptionsButtonProps extends ComponentProps<typeof IconButton> {
ref?: Ref<HTMLButtonElement>;
}
/**
* A button to trigger the more options menu.
*/
export const MoreOptionsButton = forwardRef<HTMLButtonElement, MoreOptionsButtonProps>(
function MoreOptionsButton(props, ref) {
export const MoreOptionsButton = function MoreOptionsButton(props: MoreOptionsButtonProps): JSX.Element {
return (
<Tooltip label={_t("room_list|room|more_options")}>
<IconButton aria-label={_t("room_list|room|more_options")} {...props} ref={ref}>
<IconButton aria-label={_t("room_list|room|more_options")} {...props}>
<OverflowIcon />
</IconButton>
</Tooltip>
);
},
);
};
interface NotificationMenuProps {
/**
@@ -238,15 +238,17 @@ interface NotificationButtonProps extends ComponentProps<typeof IconButton> {
* Whether the room is muted.
*/
isRoomMuted: boolean;
ref?: Ref<HTMLButtonElement>;
}
/**
* A button to trigger the notification menu.
*/
export const NotificationButton = forwardRef<HTMLButtonElement, NotificationButtonProps>(function MoreOptionsButton(
{ isRoomMuted, ...props },
export const NotificationButton = function MoreOptionsButton({
isRoomMuted,
ref,
) {
...props
}: NotificationButtonProps): JSX.Element {
return (
<Tooltip label={_t("room_list|notification_options")}>
<IconButton aria-label={_t("room_list|notification_options")} {...props} ref={ref}>
@@ -254,4 +256,4 @@ export const NotificationButton = forwardRef<HTMLButtonElement, NotificationButt
</IconButton>
</Tooltip>
);
});
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ForwardedRef, forwardRef, type RefObject, useMemo } from "react";
import React, { type JSX, type RefObject, useMemo, type ReactNode } from "react";
import classNames from "classnames";
import type EditorStateTransfer from "../../../../utils/EditorStateTransfer";
@@ -21,15 +21,13 @@ import { type ComposerFunctions } from "./types";
interface ContentProps {
disabled?: boolean;
composerFunctions: ComposerFunctions;
ref?: RefObject<HTMLElement | null>;
}
const Content = forwardRef<HTMLElement, ContentProps>(function Content(
{ disabled = false, composerFunctions }: ContentProps,
forwardRef: ForwardedRef<HTMLElement>,
) {
useWysiwygEditActionHandler(disabled, forwardRef as RefObject<HTMLElement>, composerFunctions);
const Content = function Content({ disabled = false, composerFunctions, ref }: ContentProps): ReactNode {
useWysiwygEditActionHandler(disabled, ref, composerFunctions);
return null;
});
};
interface EditWysiwygComposerProps {
disabled?: boolean;

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ForwardedRef, forwardRef, type RefObject, useMemo } from "react";
import React, { type JSX, type RefObject, useMemo, type ReactNode } from "react";
import { type IEventRelation } from "matrix-js-sdk/src/matrix";
import { useWysiwygSendActionHandler } from "./hooks/useWysiwygSendActionHandler";
@@ -22,15 +22,13 @@ import { ComposerContext, getDefaultContextValue } from "./ComposerContext";
interface ContentProps {
disabled?: boolean;
composerFunctions: ComposerFunctions;
ref?: RefObject<HTMLElement | null>;
}
const Content = forwardRef<HTMLElement, ContentProps>(function Content(
{ disabled = false, composerFunctions }: ContentProps,
forwardRef: ForwardedRef<HTMLElement>,
) {
useWysiwygSendActionHandler(disabled, forwardRef as RefObject<HTMLElement>, composerFunctions);
const Content = function Content({ disabled = false, composerFunctions, ref }: ContentProps): ReactNode {
useWysiwygSendActionHandler(disabled, ref, composerFunctions);
return null;
});
};
export interface SendWysiwygComposerProps {
initialContent?: string;

View File

@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import classNames from "classnames";
import React, { type CSSProperties, forwardRef, memo, type RefObject, type ReactNode } from "react";
import React, { type CSSProperties, memo, type RefObject, type ReactNode } from "react";
import { useIsExpanded } from "../hooks/useIsExpanded";
import { useSelection } from "../hooks/useSelection";
@@ -19,22 +19,15 @@ interface EditorProps {
placeholder?: string;
leftComponent?: ReactNode;
rightComponent?: ReactNode;
ref?: RefObject<HTMLDivElement | null>;
}
export const Editor = memo(
forwardRef<HTMLDivElement | null, EditorProps>(function Editor(
{ disabled, placeholder, leftComponent, rightComponent }: EditorProps,
ref,
) {
const isExpanded = useIsExpanded(ref as RefObject<HTMLDivElement | null>, HEIGHT_BREAKING_POINT);
export const Editor = memo(function Editor({ disabled, placeholder, leftComponent, rightComponent, ref }: EditorProps) {
const isExpanded = useIsExpanded(ref, HEIGHT_BREAKING_POINT);
const { onFocus, onBlur, onInput } = useSelection();
return (
<div
data-testid="WysiwygComposerEditor"
className="mx_WysiwygComposer_Editor"
data-is-expanded={isExpanded}
>
<div data-testid="WysiwygComposerEditor" className="mx_WysiwygComposer_Editor" data-is-expanded={isExpanded}>
{leftComponent}
<div className="mx_WysiwygComposer_Editor_container">
<div
@@ -58,5 +51,4 @@ export const Editor = memo(
{rightComponent}
</div>
);
}),
);
});

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ForwardedRef, forwardRef, type FunctionComponent } from "react";
import React, { type JSX, type Ref, type FunctionComponent } from "react";
import { type FormattingFunctions, type MappedSuggestion } from "@vector-im/matrix-wysiwyg";
import { logger } from "matrix-js-sdk/src/logger";
@@ -39,6 +39,8 @@ interface WysiwygAutocompleteProps {
* Handler purely for the at-room mentions special case
*/
handleAtRoomMention: FormattingFunctions["mentionAtRoom"];
ref?: Ref<Autocomplete>;
}
/**
@@ -48,11 +50,13 @@ interface WysiwygAutocompleteProps {
*
* @param props.ref - the ref will be attached to the rendered `<Autocomplete />` component
*/
const WysiwygAutocomplete = forwardRef(
(
{ suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps,
ref: ForwardedRef<Autocomplete>,
): JSX.Element | null => {
const WysiwygAutocomplete = ({
suggestion,
handleMention,
handleCommand,
handleAtRoomMention,
ref,
}: WysiwygAutocompleteProps): JSX.Element | null => {
const { room } = useScopedRoomContext("room");
const client = useMatrixClientContext();
@@ -109,8 +113,7 @@ const WysiwygAutocomplete = forwardRef(
/>
</div>
);
},
);
};
(WysiwygAutocomplete as FunctionComponent).displayName = "WysiwygAutocomplete";

View File

@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
import { type RefObject, useEffect, useState } from "react";
export function useIsExpanded(ref: RefObject<HTMLElement | null>, breakingPoint: number): boolean {
export function useIsExpanded(ref: RefObject<HTMLElement | null> | undefined, breakingPoint: number): boolean {
const [isExpanded, setIsExpanded] = useState(false);
useEffect(() => {
if (ref.current) {
if (ref?.current) {
const editor = ref.current;
const resizeObserver = new ResizeObserver((entries) => {
requestAnimationFrame(() => {

View File

@@ -22,7 +22,7 @@ import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.
export function useWysiwygEditActionHandler(
disabled: boolean,
composerElement: RefObject<HTMLElement>,
composerElement: RefObject<HTMLElement | null> | undefined,
composerFunctions: ComposerFunctions,
): void {
const roomContext = useScopedRoomContext("timelineRenderingType");
@@ -33,7 +33,7 @@ export function useWysiwygEditActionHandler(
(payload: ActionPayload) => {
// don't let the user into the composer if it is disabled - all of these branches lead
// to the cursor being in the composer
if (disabled || !composerElement.current) return;
if (disabled || !composerElement?.current) return;
const context = payload.context ?? TimelineRenderingType.Room;

View File

@@ -22,7 +22,7 @@ import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.
export function useWysiwygSendActionHandler(
disabled: boolean,
composerElement: RefObject<HTMLElement>,
composerElement: RefObject<HTMLElement | null> | undefined,
composerFunctions: ComposerFunctions,
): void {
const roomContext = useScopedRoomContext("timelineRenderingType");

View File

@@ -15,7 +15,7 @@ import AccessibleButton, { type ButtonProps } from "../../elements/AccessibleBut
type Props<T extends keyof HTMLElementTagNameMap> = Omit<
ButtonProps<T>,
"aria-label" | "title" | "kind" | "className" | "element"
"aria-label" | "title" | "kind" | "className" | "element" | "ref"
> & {
isExpanded: boolean;
};

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type ForwardedRef, forwardRef } from "react";
import React, { type JSX, type Ref } from "react";
import { type IPusher, PUSHER_DEVICE_ID, type LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../../languageHandler";
@@ -47,6 +47,7 @@ interface Props {
* Changes sign out button to be a manage button
*/
delegatedAuthAccountUrl?: string;
ref?: Ref<HTMLDivElement>;
}
const isDeviceSelected = (
@@ -237,9 +238,7 @@ const DeviceListItem: React.FC<{
* Filtered list of devices
* Sorted by latest activity descending
*/
export const FilteredDeviceList = forwardRef(
(
{
export const FilteredDeviceList = ({
devices,
pushers,
localNotificationSettings,
@@ -256,9 +255,8 @@ export const FilteredDeviceList = forwardRef(
setSelectedDeviceIds,
supportsMSC3881,
delegatedAuthAccountUrl,
}: Props,
ref: ForwardedRef<HTMLDivElement>,
) => {
ref,
}: Props): JSX.Element => {
const sortedDevices = getFilteredSortedDevices(devices, filter);
function getPusherForDevice(device: ExtendedDevice): IPusher | undefined {
@@ -384,5 +382,4 @@ export const FilteredDeviceList = forwardRef(
</ol>
</div>
);
},
);
};

View File

@@ -45,7 +45,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit<
AccessibleButtonProps<T>,
"title" | "onClick" | "size" | "element"
"title" | "onClick" | "size" | "element" | "ref"
> & {
space?: Room;
spaceKey?: SpaceKey;

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
import React, { type ComponentProps, forwardRef } from "react";
import React, { type ComponentProps, type Ref, type JSX } from "react";
import ThreadsSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
import classNames from "classnames";
import { IconButton, Text, Tooltip } from "@vector-im/compound-web";
@@ -28,16 +28,19 @@ interface ThreadsActivityCentreButtonProps extends ComponentProps<typeof IconBut
* The notification level of the threads.
*/
notificationLevel: NotificationLevel;
ref?: Ref<HTMLButtonElement>;
}
/**
* A button to open the thread activity centre.
*/
export const ThreadsActivityCentreButton = forwardRef<HTMLButtonElement, ThreadsActivityCentreButtonProps>(
function ThreadsActivityCentreButton(
{ displayLabel, notificationLevel, disableTooltip, ...props },
export const ThreadsActivityCentreButton = function ThreadsActivityCentreButton({
displayLabel,
notificationLevel,
disableTooltip,
ref,
): React.JSX.Element {
...props
}: ThreadsActivityCentreButtonProps): JSX.Element {
// Disable tooltip when the label is displayed
const openTooltip = disableTooltip || displayLabel ? false : undefined;
@@ -67,5 +70,4 @@ export const ThreadsActivityCentreButton = forwardRef<HTMLButtonElement, Threads
</IconButton>
</Tooltip>
);
},
);
};

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { createRef, useState, forwardRef } from "react";
import React, { createRef, useState, type Ref, type FC } from "react";
import classNames from "classnames";
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
@@ -41,10 +41,20 @@ type ButtonProps = Omit<AccessibleButtonProps<"div">, "title" | "element"> & {
offLabel?: string;
forceHide?: boolean;
onHover?: (hovering: boolean) => void;
ref?: Ref<HTMLElement>;
};
const LegacyCallViewToggleButton = forwardRef<HTMLElement, ButtonProps>(
({ children, state: isOn, className, onLabel, offLabel, forceHide, onHover, ...props }, ref) => {
const LegacyCallViewToggleButton: FC<ButtonProps> = ({
children,
state: isOn,
className,
onLabel,
offLabel,
forceHide,
onHover,
ref,
...props
}) => {
const classes = classNames("mx_LegacyCallViewButtons_button", className, {
mx_LegacyCallViewButtons_button_on: isOn,
mx_LegacyCallViewButtons_button_off: !isOn,
@@ -64,8 +74,7 @@ const LegacyCallViewToggleButton = forwardRef<HTMLElement, ButtonProps>(
{children}
</AccessibleButton>
);
},
);
};
interface IDropdownButtonProps extends ButtonProps {
deviceKinds: MediaDeviceKindEnum[];

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type ComponentClass, createContext, forwardRef, useContext } from "react";
import React, { type ComponentClass, createContext, useContext } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
// This context is available to components under LoggedInView,
@@ -24,22 +24,16 @@ export function useMatrixClientContext(): MatrixClient {
return useContext(MatrixClientContext);
}
const matrixHOC = <ComposedComponentProps extends object>(
const matrixHOC =
<ComposedComponentProps extends object>(
ComposedComponent: ComponentClass<ComposedComponentProps>,
): ((
props: Omit<ComposedComponentProps, "mxClient"> & React.RefAttributes<InstanceType<typeof ComposedComponent>>,
) => React.ReactElement | null) => {
type ComposedComponentInstance = InstanceType<typeof ComposedComponent>;
// eslint-disable-next-line react-hooks/rules-of-hooks
const TypedComponent = ComposedComponent;
return forwardRef<ComposedComponentInstance, Omit<ComposedComponentProps, "mxClient">>((props, ref) => {
) => React.ReactElement | null) =>
(props) => {
const client = useContext(MatrixClientContext);
// @ts-ignore
return <TypedComponent ref={ref} {...props} mxClient={client} />;
});
return <ComposedComponent {...props} mxClient={client} />;
};
export const withMatrixClientHOC = matrixHOC;

View File

@@ -69,7 +69,7 @@ export interface EventTileTypeProps
}
type FactoryProps = Omit<EventTileTypeProps, "ref">;
type Factory<X = FactoryProps> = (ref: Optional<React.RefObject<any>>, props: X) => JSX.Element;
type Factory<X = FactoryProps> = (ref: React.RefObject<any> | undefined, props: X) => JSX.Element;
export const MessageEventFactory: Factory = (ref, props) => <MessageEvent ref={ref} {...props} />;
const LegacyCallEventFactory: Factory<FactoryProps & { callEventGrouper: LegacyCallEventGrouper }> = (ref, props) => (

View File

@@ -11,8 +11,8 @@ import { act, fireEvent, screen, waitFor } from "jest-matrix-react";
import { RoomMember, User, RoomEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked } from "jest-mock";
import { type JSX } from "react";
import type React from "react";
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
import defaultDispatcher from "../../../../../../src/dispatcher/dispatcher";
import { type Rendered, renderMemberList } from "./common";
@@ -21,7 +21,7 @@ jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
}));
type Children = (args: { height: number; width: number }) => React.JSX.Element;
type Children = (args: { height: number; width: number }) => JSX.Element;
jest.mock("react-virtualized", () => {
const ReactVirtualized = jest.requireActual("react-virtualized");
return {

View File

@@ -10,8 +10,8 @@ Please see LICENSE files in the repository root for full details.
import { act } from "react";
import { waitFor } from "jest-matrix-react";
import { type Room, type RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { type JSX } from "react";
import type React from "react";
import { filterConsole } from "../../../../../test-utils";
import { type Rendered, renderMemberList } from "./common";
@@ -19,7 +19,7 @@ jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
}));
type Children = (args: { height: number; width: number }) => React.JSX.Element;
type Children = (args: { height: number; width: number }) => JSX.Element;
jest.mock("react-virtualized", () => {
const ReactVirtualized = jest.requireActual("react-virtualized");
return {