Update react monorepo to v19 (major) (#28914)
* Update react monorepo to v19 * Import JSX explicitly for React 19 compatibility Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update usages of refs for React 19 compatibility Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update react imports Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Avoid legacy contexts as much as possible Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Avoid deprecated React symbols Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Stash Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update usages of refs for React 19 compatibility Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Switch pillify to use a html-react-parser approach rather than DOM muddling Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate react html parsing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate react html parsing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate html parsing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Memoize the EventContentBody component Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate html parsing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Simplify Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Discard changes to src/Linkify.tsx * Discard changes to src/components/views/messages/TextualBody.tsx * Discard changes to src/settings/handlers/AbstractLocalStorageSettingsHandler.ts * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Prepare for React 19 upgrade Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove stale comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
5
src/@types/react.d.ts
vendored
5
src/@types/react.d.ts
vendored
@@ -18,4 +18,9 @@ declare module "react" {
|
||||
|
||||
// Fix lazy types - https://stackoverflow.com/a/71017028
|
||||
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;
|
||||
|
||||
// Standardize defaultProps for FunctionComponent so we can write generics assuming `defaultProps` exists on ComponentType
|
||||
interface FunctionComponent {
|
||||
defaultProps?: unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 JSX, type LegacyRef, type ReactNode } from "react";
|
||||
import React, { type JSX, type Key, type LegacyRef, type ReactNode } from "react";
|
||||
import sanitizeHtml, { type IOptions } from "sanitize-html";
|
||||
import classNames from "classnames";
|
||||
import katex from "katex";
|
||||
@@ -239,7 +239,7 @@ class HtmlHighlighter extends BaseHighlighter<string> {
|
||||
|
||||
const emojiToHtmlSpan = (emoji: string): string =>
|
||||
`<span class='mx_Emoji' title='${unicodeToShortcode(emoji)}'>${emoji}</span>`;
|
||||
const emojiToJsxSpan = (emoji: string, key: number): JSX.Element => (
|
||||
const emojiToJsxSpan = (emoji: string, key: Key): JSX.Element => (
|
||||
<span key={key} className="mx_Emoji" title={unicodeToShortcode(emoji)}>
|
||||
{emoji}
|
||||
</span>
|
||||
|
||||
@@ -321,7 +321,7 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
|
||||
} catch (error) {
|
||||
logger.error("Failed to login via OIDC", error);
|
||||
|
||||
await onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
|
||||
onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -468,7 +468,7 @@ type TryAgainFunction = () => void;
|
||||
* @param description error description
|
||||
* @param tryAgain OPTIONAL function to call on try again button from error dialog
|
||||
*/
|
||||
async function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): Promise<void> {
|
||||
function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): void {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("auth|oidc|error_title"),
|
||||
description,
|
||||
|
||||
@@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
|
||||
scrollIntoView,
|
||||
onKeyDown,
|
||||
}) => {
|
||||
const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, {
|
||||
const [state, dispatch] = useReducer<IState, [Action]>(reducer, {
|
||||
nodes: [],
|
||||
});
|
||||
|
||||
|
||||
@@ -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 { type ReactElement } from "react";
|
||||
import { type ReactElement, type RefAttributes, type HTMLAttributes } from "react";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import CommandProvider from "./CommandProvider";
|
||||
@@ -31,7 +31,7 @@ export interface ICompletion {
|
||||
type?: "at-room" | "command" | "community" | "room" | "user";
|
||||
completion: string;
|
||||
completionId?: string;
|
||||
component: ReactElement;
|
||||
component: ReactElement<RefAttributes<HTMLElement> & HTMLAttributes<HTMLElement>>;
|
||||
range: ISelectionRange;
|
||||
command?: string;
|
||||
suffix?: string;
|
||||
|
||||
@@ -21,8 +21,6 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollb
|
||||
// scroll horizontally rather than vertically. This should only be used on components
|
||||
// with no vertical scroll opportunity.
|
||||
verticalScrollsHorizontally?: boolean;
|
||||
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
interface IState {
|
||||
|
||||
@@ -165,12 +165,6 @@ interface IProps {
|
||||
initialScreenAfterLogin?: IScreen;
|
||||
// displayname, if any, to set on the device when logging in/registering.
|
||||
defaultDeviceDisplayName?: string;
|
||||
|
||||
// Used by tests, this function is called when session initialisation starts
|
||||
// with a promise that resolves or rejects once the initialiation process
|
||||
// has finished, so that tests can wait for this to avoid them executing over
|
||||
// each other.
|
||||
initPromiseCallback?: (p: Promise<void>) => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -291,9 +285,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
*/
|
||||
private startInitSession = (): void => {
|
||||
const initProm = this.initSession();
|
||||
if (this.props.initPromiseCallback) {
|
||||
this.props.initPromiseCallback(initProm);
|
||||
}
|
||||
|
||||
initProm.catch((err) => {
|
||||
// TODO: show an error screen, rather than a spinner of doom
|
||||
@@ -1002,10 +993,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
// Wait for the first sync to complete so that if a room does have an alias,
|
||||
// it would have been retrieved.
|
||||
if (!this.firstSyncComplete) {
|
||||
if (!this.firstSyncPromise) {
|
||||
logger.warn("Cannot view a room before first sync. room_id:", roomInfo.room_id);
|
||||
return;
|
||||
}
|
||||
await this.firstSyncPromise.promise;
|
||||
}
|
||||
|
||||
@@ -1116,8 +1103,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
private viewUser(userId: string, subAction: string): void {
|
||||
// Wait for the first sync so that `getRoom` gives us a room object if it's
|
||||
// in the sync response
|
||||
const waitForSync = this.firstSyncPromise ? this.firstSyncPromise.promise : Promise.resolve();
|
||||
waitForSync.then(() => {
|
||||
this.firstSyncPromise.promise.then(() => {
|
||||
if (subAction === "chat") {
|
||||
this.chatCreateOrReuse(userId);
|
||||
return;
|
||||
@@ -1480,11 +1466,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
* (useful for setting listeners)
|
||||
*/
|
||||
private onWillStartClient(): void {
|
||||
// reset the 'have completed first sync' flag,
|
||||
// since we're about to start the client and therefore about
|
||||
// to do the first sync
|
||||
// Reset the 'have completed first sync' flag,
|
||||
// since we're about to start the client and therefore about to do the first sync
|
||||
// We resolve the existing promise with the new one to update any existing listeners
|
||||
if (!this.firstSyncComplete) {
|
||||
const firstSyncPromise = defer<void>();
|
||||
this.firstSyncPromise.resolve(firstSyncPromise.promise);
|
||||
this.firstSyncPromise = firstSyncPromise;
|
||||
} else {
|
||||
this.firstSyncPromise = defer();
|
||||
}
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = defer();
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
|
||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
||||
|
||||
@@ -27,14 +27,14 @@ export class Tab<T extends string> {
|
||||
* @param {string} id The tab's ID.
|
||||
* @param {string} label The untranslated tab label.
|
||||
* @param {string|JSX.Element} icon An SVG element to use for the tab icon. Can also be a string for legacy icons, in which case it is the class for the tab icon. This should be a simple mask.
|
||||
* @param {React.ReactNode} body The JSX for the tab container.
|
||||
* @param {JSX.Element} body The JSX for the tab container.
|
||||
* @param {string} screenName The screen name to report to Posthog.
|
||||
*/
|
||||
public constructor(
|
||||
public readonly id: T,
|
||||
public readonly label: TranslationKey,
|
||||
public readonly icon: string | JSX.Element | null,
|
||||
public readonly body: React.ReactNode,
|
||||
public readonly body: JSX.Element,
|
||||
public readonly screenName?: ScreenName,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ 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 { createContext, type Dispatch, type ReducerAction, type ReducerState } from "react";
|
||||
import { createContext, type Dispatch, type Reducer, type ReducerState } from "react";
|
||||
|
||||
import type { AuthHeaderReducer } from "./AuthHeaderProvider";
|
||||
|
||||
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
|
||||
|
||||
interface AuthHeaderContextType {
|
||||
state: ReducerState<AuthHeaderReducer>;
|
||||
dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>;
|
||||
|
||||
@@ -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<ComponentProps<typeof AuthHeaderModifier>[], [AuthHeaderAction]>(
|
||||
(state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => {
|
||||
switch (action.type) {
|
||||
case AuthHeaderActionType.Add:
|
||||
|
||||
@@ -18,6 +18,7 @@ import { CardContext } from "../right_panel/context";
|
||||
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
|
||||
import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
|
||||
|
||||
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
|
||||
member: RoomMember | null;
|
||||
@@ -47,6 +48,7 @@ function MemberAvatar(
|
||||
}: IProps,
|
||||
ref: Ref<HTMLElement>,
|
||||
): JSX.Element {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const card = useContext(CardContext);
|
||||
|
||||
const member = useRoomMemberProfile({
|
||||
@@ -60,7 +62,7 @@ function MemberAvatar(
|
||||
let imageUrl: string | null | undefined;
|
||||
if (member?.name) {
|
||||
if (member.getMxcAvatarUrl()) {
|
||||
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(
|
||||
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "", cli).getThumbnailOfSourceHttp(
|
||||
parseInt(size, 10),
|
||||
parseInt(size, 10),
|
||||
resizeMethod,
|
||||
|
||||
@@ -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, { createRef } from "react";
|
||||
import React, { createRef, type RefObject } from "react";
|
||||
import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
|
||||
@@ -27,10 +27,10 @@ interface IState extends IScrollableBaseState {
|
||||
// nothing special
|
||||
}
|
||||
|
||||
export class ModuleUiDialog<P extends DialogProps, C extends DialogContent<P>> extends ScrollableBaseModal<
|
||||
IProps<P, C>,
|
||||
IState
|
||||
> {
|
||||
export class ModuleUiDialog<
|
||||
P extends DialogProps = DialogProps,
|
||||
C extends DialogContent<P> = DialogContent<P>,
|
||||
> extends ScrollableBaseModal<IProps<P, C>, IState> {
|
||||
private contentRef = createRef<C>();
|
||||
|
||||
public constructor(props: IProps<P, C>) {
|
||||
@@ -74,6 +74,11 @@ export class ModuleUiDialog<P extends DialogProps, C extends DialogContent<P>> e
|
||||
...dialogProps,
|
||||
} as unknown as P;
|
||||
|
||||
return <div className="mx_ModuleUiDialog">{this.props.contentFactory(contentProps, this.contentRef)}</div>;
|
||||
// XXX: we have to fudge the types here a little as the react-sdk-module-api lacks React 19 support
|
||||
return (
|
||||
<div className="mx_ModuleUiDialog">
|
||||
{this.props.contentFactory(contentProps, this.contentRef as RefObject<C>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,12 +135,19 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
|
||||
placement = "right",
|
||||
onTooltipOpenChange,
|
||||
disableTooltip,
|
||||
role = "button",
|
||||
tabIndex = 0,
|
||||
...restProps
|
||||
}: ButtonProps<T>,
|
||||
ref: Ref<HTMLElementTagNameMap[T]>,
|
||||
): JSX.Element {
|
||||
const newProps = restProps as RenderedElementProps<T>;
|
||||
newProps["aria-label"] = newProps["aria-label"] ?? title;
|
||||
const newProps = {
|
||||
...restProps,
|
||||
tabIndex,
|
||||
role,
|
||||
"aria-label": restProps["aria-label"] ?? title,
|
||||
} as RenderedElementProps<T>;
|
||||
|
||||
if (disabled) {
|
||||
newProps["aria-disabled"] = true;
|
||||
newProps["disabled"] = true;
|
||||
@@ -222,10 +229,6 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
|
||||
});
|
||||
|
||||
// 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";
|
||||
|
||||
interface RefProp<T extends ElementType> {
|
||||
|
||||
@@ -6,7 +6,14 @@ 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 ChangeEvent, type ContextType, createRef, type SyntheticEvent } from "react";
|
||||
import React, {
|
||||
type JSX,
|
||||
type ToggleEvent,
|
||||
type ChangeEvent,
|
||||
type ContextType,
|
||||
createRef,
|
||||
type SyntheticEvent,
|
||||
} from "react";
|
||||
import { type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types";
|
||||
@@ -278,7 +285,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
|
||||
});
|
||||
};
|
||||
|
||||
private onLocalAliasesToggled = (event: ChangeEvent<HTMLDetailsElement>): void => {
|
||||
private onLocalAliasesToggled = (event: ToggleEvent<HTMLDetailsElement>): void => {
|
||||
// expanded
|
||||
if (event.currentTarget.open) {
|
||||
// if local aliases haven't been preloaded yet at component mount
|
||||
|
||||
@@ -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 ReactElement, useCallback, useEffect, useState } from "react";
|
||||
import React, { type JSX, type ReactElement, useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { type NonEmptyArray } from "../../../../../@types/common";
|
||||
import { _t, getCurrentLanguage } from "../../../../../languageHandler";
|
||||
|
||||
@@ -198,7 +198,7 @@ export const options: Opts = {
|
||||
rel: "noreferrer noopener",
|
||||
},
|
||||
|
||||
ignoreTags: ["pre", "code"],
|
||||
ignoreTags: ["a", "pre", "code"],
|
||||
|
||||
className: "linkified",
|
||||
|
||||
|
||||
@@ -9,15 +9,20 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX, type ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <span>hello world</span>
|
||||
* Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <>hello world</>
|
||||
* @param array the array of element to join
|
||||
* @param joiner the string/JSX.Element to join with
|
||||
* @returns the joined array
|
||||
*/
|
||||
export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.Element {
|
||||
const newArray: ReactNode[] = [];
|
||||
array.forEach((element, index) => {
|
||||
newArray.push(element, index === array.length - 1 ? null : joiner);
|
||||
});
|
||||
return <span>{newArray}</span>;
|
||||
return (
|
||||
<>
|
||||
{array.map((element, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{element}
|
||||
{index === array.length - 1 ? null : joiner}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { type IWidget } from "matrix-widget-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { getCallBehaviourWellKnown } from "../utils/WellKnownUtils";
|
||||
import WidgetUtils from "../utils/WidgetUtils";
|
||||
import { type IStoredLayout, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||
@@ -37,7 +36,7 @@ function getWidgetBuildUrl(room: Room): string | undefined {
|
||||
return SdkConfig.get().widget_build_url;
|
||||
}
|
||||
|
||||
const wellKnown = getCallBehaviourWellKnown(MatrixClientPeg.safeGet());
|
||||
const wellKnown = getCallBehaviourWellKnown(room.client);
|
||||
if (isDm && wellKnown?.ignore_dm) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user