Merge pull request #3358 from element-hq/robin/remove-forward-ref
Remove usages of forwardRef
This commit is contained in:
@@ -6,12 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import {
|
import { type Ref, type FC, type HTMLAttributes, type ReactNode } from "react";
|
||||||
type FC,
|
|
||||||
type HTMLAttributes,
|
|
||||||
type ReactNode,
|
|
||||||
forwardRef,
|
|
||||||
} from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Heading, Text } from "@vector-im/compound-web";
|
import { Heading, Text } from "@vector-im/compound-web";
|
||||||
@@ -24,12 +19,17 @@ import { EncryptionLock } from "./room/EncryptionLock";
|
|||||||
import { useMediaQuery } from "./useMediaQuery";
|
import { useMediaQuery } from "./useMediaQuery";
|
||||||
|
|
||||||
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
||||||
|
ref?: Ref<HTMLElement>;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = forwardRef<HTMLElement, HeaderProps>(
|
export const Header: FC<HeaderProps> = ({
|
||||||
({ children, className, ...rest }, ref) => {
|
ref,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -39,8 +39,7 @@ export const Header = forwardRef<HTMLElement, HeaderProps>(
|
|||||||
{children}
|
{children}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
Header.displayName = "Header";
|
Header.displayName = "Header";
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { type ComponentProps, type FC, type MouseEvent } from "react";
|
||||||
type ComponentPropsWithoutRef,
|
|
||||||
forwardRef,
|
|
||||||
type MouseEvent,
|
|
||||||
} from "react";
|
|
||||||
import { Link as CpdLink } from "@vector-im/compound-web";
|
import { Link as CpdLink } from "@vector-im/compound-web";
|
||||||
import { type LinkProps, useHref, useLinkClickHandler } from "react-router-dom";
|
import { type LinkProps, useHref, useLinkClickHandler } from "react-router-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
@@ -26,31 +22,30 @@ export function useLink(
|
|||||||
return [href, onClick];
|
return [href, onClick];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = Omit<
|
type Props = Omit<ComponentProps<typeof CpdLink>, "href" | "onClick"> & {
|
||||||
ComponentPropsWithoutRef<typeof CpdLink>,
|
to: LinkProps["to"];
|
||||||
"href" | "onClick"
|
state?: unknown;
|
||||||
> & { to: LinkProps["to"]; state?: unknown };
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A version of Compound's link component that integrates with our router setup.
|
* A version of Compound's link component that integrates with our router setup.
|
||||||
* This is only for app-internal links.
|
* This is only for app-internal links.
|
||||||
*/
|
*/
|
||||||
export const Link = forwardRef<HTMLAnchorElement, Props>(function Link(
|
export const Link: FC<Props> = ({ ref, to, state, ...props }) => {
|
||||||
{ to, state, ...props },
|
|
||||||
ref,
|
|
||||||
) {
|
|
||||||
const [path, onClick] = useLink(to, state);
|
const [path, onClick] = useLink(to, state);
|
||||||
return <CpdLink ref={ref} {...props} href={path} onClick={onClick} />;
|
return <CpdLink ref={ref} {...props} href={path} onClick={onClick} />;
|
||||||
});
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A link to an external web page, made to fit into blocks of text more subtly
|
* A link to an external web page, made to fit into blocks of text more subtly
|
||||||
* than the normal Compound link component.
|
* than the normal Compound link component.
|
||||||
*/
|
*/
|
||||||
export const ExternalLink = forwardRef<
|
export const ExternalLink: FC<ComponentProps<"a">> = ({
|
||||||
HTMLAnchorElement,
|
ref,
|
||||||
ComponentPropsWithoutRef<"a">
|
className,
|
||||||
>(function ExternalLink({ className, children, ...props }, ref) {
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -62,4 +57,4 @@ export const ExternalLink = forwardRef<
|
|||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|||||||
@@ -5,24 +5,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ComponentPropsWithoutRef, forwardRef } from "react";
|
import { type ComponentProps, type FC } from "react";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import type { LinkProps } from "react-router-dom";
|
import type { LinkProps } from "react-router-dom";
|
||||||
import { useLink } from "./Link";
|
import { useLink } from "./Link";
|
||||||
|
|
||||||
type Props = Omit<
|
type Props = Omit<ComponentProps<typeof Button<"a">>, "as" | "href"> & {
|
||||||
ComponentPropsWithoutRef<typeof Button<"a">>,
|
to: LinkProps["to"];
|
||||||
"as" | "href"
|
state?: unknown;
|
||||||
> & { to: LinkProps["to"]; state?: unknown };
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A version of Compound's button component that acts as a link and integrates
|
* A version of Compound's button component that acts as a link and integrates
|
||||||
* with our router setup.
|
* with our router setup.
|
||||||
*/
|
*/
|
||||||
export const LinkButton = forwardRef<HTMLAnchorElement, Props>(
|
export const LinkButton: FC<Props> = ({ ref, to, state, ...props }) => {
|
||||||
function LinkButton({ to, state, ...props }, ref) {
|
|
||||||
const [path, onClick] = useLink(to, state);
|
const [path, onClick] = useLink(to, state);
|
||||||
return <Button as="a" ref={ref} {...props} href={path} onClick={onClick} />;
|
return <Button as="a" ref={ref} {...props} href={path} onClick={onClick} />;
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|||||||
@@ -6,18 +6,23 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { type FormEventHandler, forwardRef, type ReactNode } from "react";
|
import {
|
||||||
|
type FC,
|
||||||
|
type Ref,
|
||||||
|
type FormEventHandler,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import styles from "./Form.module.css";
|
import styles from "./Form.module.css";
|
||||||
|
|
||||||
interface FormProps {
|
interface FormProps {
|
||||||
|
ref?: Ref<HTMLFormElement>;
|
||||||
className: string;
|
className: string;
|
||||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||||
children: ReactNode[];
|
children: ReactNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Form = forwardRef<HTMLFormElement, FormProps>(
|
export const Form: FC<FormProps> = ({ ref, children, className, onSubmit }) => {
|
||||||
({ children, className, onSubmit }, ref) => {
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
@@ -27,7 +32,6 @@ export const Form = forwardRef<HTMLFormElement, FormProps>(
|
|||||||
{children}
|
{children}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
Form.displayName = "Form";
|
Form.displayName = "Form";
|
||||||
|
|||||||
@@ -18,11 +18,10 @@ import {
|
|||||||
type ComponentType,
|
type ComponentType,
|
||||||
type Dispatch,
|
type Dispatch,
|
||||||
type FC,
|
type FC,
|
||||||
type LegacyRef,
|
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
|
type Ref,
|
||||||
type SetStateAction,
|
type SetStateAction,
|
||||||
createContext,
|
createContext,
|
||||||
forwardRef,
|
|
||||||
memo,
|
memo,
|
||||||
use,
|
use,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -162,7 +161,7 @@ const windowHeightObservable$ = fromEvent(window, "resize").pipe(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export interface LayoutProps<LayoutModel, TileModel, R extends HTMLElement> {
|
export interface LayoutProps<LayoutModel, TileModel, R extends HTMLElement> {
|
||||||
ref: LegacyRef<R>;
|
ref?: Ref<R>;
|
||||||
model: LayoutModel;
|
model: LayoutModel;
|
||||||
/**
|
/**
|
||||||
* Component creating an invisible "slot" for a tile to go in.
|
* Component creating an invisible "slot" for a tile to go in.
|
||||||
@@ -171,7 +170,7 @@ export interface LayoutProps<LayoutModel, TileModel, R extends HTMLElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TileProps<Model, R extends HTMLElement> {
|
export interface TileProps<Model, R extends HTMLElement> {
|
||||||
ref: LegacyRef<R>;
|
ref?: Ref<R>;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: ComponentProps<typeof animated.div>["style"];
|
style?: ComponentProps<typeof animated.div>["style"];
|
||||||
/**
|
/**
|
||||||
@@ -297,14 +296,13 @@ export function Grid<
|
|||||||
// render of Grid causes a re-render of Layout, which in turn re-renders Grid
|
// render of Grid causes a re-render of Layout, which in turn re-renders Grid
|
||||||
const LayoutMemo = useMemo(
|
const LayoutMemo = useMemo(
|
||||||
() =>
|
() =>
|
||||||
memo(
|
memo(function LayoutMemo({
|
||||||
forwardRef<
|
ref,
|
||||||
LayoutRef,
|
Layout,
|
||||||
LayoutMemoProps<LayoutModel, TileModel, LayoutRef>
|
...props
|
||||||
>(function LayoutMemo({ Layout, ...props }, ref): ReactNode {
|
}: LayoutMemoProps<LayoutModel, TileModel, LayoutRef>): ReactNode {
|
||||||
return <Layout {...props} ref={ref} />;
|
return <Layout {...props} ref={ref} />;
|
||||||
}),
|
}),
|
||||||
),
|
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type CSSProperties, forwardRef, useCallback, useMemo } from "react";
|
import {
|
||||||
|
type CSSProperties,
|
||||||
|
type ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
} from "react";
|
||||||
import { distinctUntilChanged } from "rxjs";
|
import { distinctUntilChanged } from "rxjs";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
@@ -33,7 +38,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
|
|
||||||
// The "fixed" (non-scrolling) part of the layout is where the spotlight tile
|
// The "fixed" (non-scrolling) part of the layout is where the spotlight tile
|
||||||
// lives
|
// lives
|
||||||
fixed: forwardRef(function GridLayoutFixed({ model, Slot }, ref) {
|
fixed: function GridLayoutFixed({ ref, model, Slot }): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
const alignment = useObservableEagerState(
|
const alignment = useObservableEagerState(
|
||||||
useInitial(() =>
|
useInitial(() =>
|
||||||
@@ -68,10 +73,10 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
|
||||||
// The scrolling part of the layout is where all the grid tiles live
|
// The scrolling part of the layout is where all the grid tiles live
|
||||||
scrolling: forwardRef(function GridLayout({ model, Slot }, ref) {
|
scrolling: function GridLayout({ ref, model, Slot }): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
useVisibleTiles(model.setVisibleTiles);
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
const { width, height: minHeight } = useObservableEagerState(minBounds$);
|
const { width, height: minHeight } = useObservableEagerState(minBounds$);
|
||||||
@@ -98,5 +103,5 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef, useCallback, useMemo } from "react";
|
import { type ReactNode, useCallback, useMemo } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
|||||||
}) => ({
|
}) => ({
|
||||||
scrollingOnTop: false,
|
scrollingOnTop: false,
|
||||||
|
|
||||||
fixed: forwardRef(function OneOnOneLayoutFixed(_props, ref) {
|
fixed: function OneOnOneLayoutFixed({ ref }): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
return <div ref={ref} />;
|
return <div ref={ref} />;
|
||||||
}),
|
},
|
||||||
|
|
||||||
scrolling: forwardRef(function OneOnOneLayoutScrolling({ model, Slot }, ref) {
|
scrolling: function OneOnOneLayoutScrolling({ ref, model, Slot }): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
const { width, height } = useObservableEagerState(minBounds$);
|
const { width, height } = useObservableEagerState(minBounds$);
|
||||||
const pipAlignmentValue = useObservableEagerState(pipAlignment$);
|
const pipAlignmentValue = useObservableEagerState(pipAlignment$);
|
||||||
@@ -66,5 +66,5 @@ export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
|||||||
</Slot>
|
</Slot>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef, useCallback } from "react";
|
import { type ReactNode, useCallback } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
import { type SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel";
|
import { type SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel";
|
||||||
@@ -22,10 +22,11 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
|||||||
> = ({ pipAlignment$ }) => ({
|
> = ({ pipAlignment$ }) => ({
|
||||||
scrollingOnTop: true,
|
scrollingOnTop: true,
|
||||||
|
|
||||||
fixed: forwardRef(function SpotlightExpandedLayoutFixed(
|
fixed: function SpotlightExpandedLayoutFixed({
|
||||||
{ model, Slot },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
model,
|
||||||
|
Slot,
|
||||||
|
}): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -37,12 +38,13 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
|
||||||
scrolling: forwardRef(function SpotlightExpandedLayoutScrolling(
|
scrolling: function SpotlightExpandedLayoutScrolling({
|
||||||
{ model, Slot },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
model,
|
||||||
|
Slot,
|
||||||
|
}): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
const pipAlignmentValue = useObservableEagerState(pipAlignment$);
|
const pipAlignmentValue = useObservableEagerState(pipAlignment$);
|
||||||
|
|
||||||
@@ -69,5 +71,5 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef } from "react";
|
import { type ReactNode } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
@@ -24,10 +24,11 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
> = ({ minBounds$ }) => ({
|
> = ({ minBounds$ }) => ({
|
||||||
scrollingOnTop: false,
|
scrollingOnTop: false,
|
||||||
|
|
||||||
fixed: forwardRef(function SpotlightLandscapeLayoutFixed(
|
fixed: function SpotlightLandscapeLayoutFixed({
|
||||||
{ model, Slot },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
model,
|
||||||
|
Slot,
|
||||||
|
}): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
useObservableEagerState(minBounds$);
|
useObservableEagerState(minBounds$);
|
||||||
|
|
||||||
@@ -43,12 +44,13 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
<div className={styles.grid} />
|
<div className={styles.grid} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
|
||||||
scrolling: forwardRef(function SpotlightLandscapeLayoutScrolling(
|
scrolling: function SpotlightLandscapeLayoutScrolling({
|
||||||
{ model, Slot },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
model,
|
||||||
|
Slot,
|
||||||
|
}): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
useVisibleTiles(model.setVisibleTiles);
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
useObservableEagerState(minBounds$);
|
useObservableEagerState(minBounds$);
|
||||||
@@ -69,5 +71,5 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type CSSProperties, forwardRef } from "react";
|
import { type ReactNode, type CSSProperties } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
@@ -30,10 +30,11 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
|||||||
> = ({ minBounds$ }) => ({
|
> = ({ minBounds$ }) => ({
|
||||||
scrollingOnTop: false,
|
scrollingOnTop: false,
|
||||||
|
|
||||||
fixed: forwardRef(function SpotlightPortraitLayoutFixed(
|
fixed: function SpotlightPortraitLayoutFixed({
|
||||||
{ model, Slot },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
model,
|
||||||
|
Slot,
|
||||||
|
}): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,12 +48,13 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
|
||||||
scrolling: forwardRef(function SpotlightPortraitLayoutScrolling(
|
scrolling: function SpotlightPortraitLayoutScrolling({
|
||||||
{ model, Slot },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
model,
|
||||||
|
Slot,
|
||||||
|
}): ReactNode {
|
||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
useVisibleTiles(model.setVisibleTiles);
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
const { width } = useObservableEagerState(minBounds$);
|
const { width } = useObservableEagerState(minBounds$);
|
||||||
@@ -90,5 +92,5 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
type ChangeEvent,
|
type ChangeEvent,
|
||||||
type FC,
|
type FC,
|
||||||
type ForwardedRef,
|
type ForwardedRef,
|
||||||
forwardRef,
|
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useId,
|
useId,
|
||||||
type JSX,
|
type JSX,
|
||||||
|
type Ref,
|
||||||
} from "react";
|
} from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ function Field({ children, className }: FieldProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InputFieldProps {
|
interface InputFieldProps {
|
||||||
|
ref?: Ref<HTMLInputElement | HTMLTextAreaElement>;
|
||||||
label?: string;
|
label?: string;
|
||||||
type: string;
|
type: string;
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
@@ -78,12 +79,8 @@ interface InputFieldProps {
|
|||||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputField = forwardRef<
|
export const InputField: FC<InputFieldProps> = ({
|
||||||
HTMLInputElement | HTMLTextAreaElement,
|
ref,
|
||||||
InputFieldProps
|
|
||||||
>(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
className,
|
className,
|
||||||
@@ -95,9 +92,7 @@ export const InputField = forwardRef<
|
|||||||
disabled,
|
disabled,
|
||||||
min,
|
min,
|
||||||
...rest
|
...rest
|
||||||
},
|
}) => {
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
const descriptionId = useId();
|
const descriptionId = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -158,8 +153,7 @@ export const InputField = forwardRef<
|
|||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
InputField.displayName = "InputField";
|
InputField.displayName = "InputField";
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,14 @@ import { type MatrixClient } from "matrix-js-sdk";
|
|||||||
import {
|
import {
|
||||||
type FC,
|
type FC,
|
||||||
type PointerEvent,
|
type PointerEvent,
|
||||||
type PropsWithoutRef,
|
|
||||||
type TouchEvent,
|
type TouchEvent,
|
||||||
forwardRef,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
type JSX,
|
type JSX,
|
||||||
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
@@ -437,13 +436,14 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
|
|
||||||
const Tile = useMemo(
|
const Tile = useMemo(
|
||||||
() =>
|
() =>
|
||||||
forwardRef<
|
function Tile({
|
||||||
HTMLDivElement,
|
|
||||||
PropsWithoutRef<TileProps<TileViewModel, HTMLDivElement>>
|
|
||||||
>(function Tile(
|
|
||||||
{ className, style, targetWidth, targetHeight, model },
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
className,
|
||||||
|
style,
|
||||||
|
targetWidth,
|
||||||
|
targetHeight,
|
||||||
|
model,
|
||||||
|
}: TileProps<TileViewModel, HTMLDivElement>): ReactNode {
|
||||||
const spotlightExpanded = useObservableEagerState(
|
const spotlightExpanded = useObservableEagerState(
|
||||||
vm.spotlightExpanded$,
|
vm.spotlightExpanded$,
|
||||||
);
|
);
|
||||||
@@ -481,7 +481,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
[vm, openProfile],
|
[vm, openProfile],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type ComponentProps,
|
type ComponentProps,
|
||||||
|
type FC,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
forwardRef,
|
type Ref,
|
||||||
useCallback,
|
useCallback,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@@ -50,6 +51,7 @@ import { useMergedRefs } from "../useMergedRefs";
|
|||||||
import { useReactionsSender } from "../reactions/useReactionsSender";
|
import { useReactionsSender } from "../reactions/useReactionsSender";
|
||||||
|
|
||||||
interface TileProps {
|
interface TileProps {
|
||||||
|
ref?: Ref<HTMLDivElement>;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: ComponentProps<typeof animated.div>["style"];
|
style?: ComponentProps<typeof animated.div>["style"];
|
||||||
targetWidth: number;
|
targetWidth: number;
|
||||||
@@ -66,9 +68,8 @@ interface UserMediaTileProps extends TileProps {
|
|||||||
menuEnd?: ReactNode;
|
menuEnd?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||||
(
|
ref,
|
||||||
{
|
|
||||||
vm,
|
vm,
|
||||||
showSpeakingIndicators,
|
showSpeakingIndicators,
|
||||||
locallyMuted,
|
locallyMuted,
|
||||||
@@ -77,9 +78,7 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
|||||||
className,
|
className,
|
||||||
displayName,
|
displayName,
|
||||||
...props
|
...props
|
||||||
},
|
}) => {
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
const { toggleRaisedHand } = useReactionsSender();
|
const { toggleRaisedHand } = useReactionsSender();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const video = useObservableEagerState(vm.video$);
|
const video = useObservableEagerState(vm.video$);
|
||||||
@@ -190,8 +189,7 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
|||||||
{menu}
|
{menu}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
UserMediaTile.displayName = "UserMediaTile";
|
UserMediaTile.displayName = "UserMediaTile";
|
||||||
|
|
||||||
@@ -200,8 +198,12 @@ interface LocalUserMediaTileProps extends TileProps {
|
|||||||
onOpenProfile: (() => void) | null;
|
onOpenProfile: (() => void) | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocalUserMediaTile = forwardRef<HTMLDivElement, LocalUserMediaTileProps>(
|
const LocalUserMediaTile: FC<LocalUserMediaTileProps> = ({
|
||||||
({ vm, onOpenProfile, ...props }, ref) => {
|
ref,
|
||||||
|
vm,
|
||||||
|
onOpenProfile,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const mirror = useObservableEagerState(vm.mirror$);
|
const mirror = useObservableEagerState(vm.mirror$);
|
||||||
const alwaysShow = useObservableEagerState(vm.alwaysShow$);
|
const alwaysShow = useObservableEagerState(vm.alwaysShow$);
|
||||||
@@ -240,8 +242,7 @@ const LocalUserMediaTile = forwardRef<HTMLDivElement, LocalUserMediaTileProps>(
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
LocalUserMediaTile.displayName = "LocalUserMediaTile";
|
LocalUserMediaTile.displayName = "LocalUserMediaTile";
|
||||||
|
|
||||||
@@ -249,10 +250,11 @@ interface RemoteUserMediaTileProps extends TileProps {
|
|||||||
vm: RemoteUserMediaViewModel;
|
vm: RemoteUserMediaViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RemoteUserMediaTile = forwardRef<
|
const RemoteUserMediaTile: FC<RemoteUserMediaTileProps> = ({
|
||||||
HTMLDivElement,
|
ref,
|
||||||
RemoteUserMediaTileProps
|
vm,
|
||||||
>(({ vm, ...props }, ref) => {
|
...props
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const locallyMuted = useObservableEagerState(vm.locallyMuted$);
|
const locallyMuted = useObservableEagerState(vm.locallyMuted$);
|
||||||
const localVolume = useObservableEagerState(vm.localVolume$);
|
const localVolume = useObservableEagerState(vm.localVolume$);
|
||||||
@@ -303,11 +305,12 @@ const RemoteUserMediaTile = forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
RemoteUserMediaTile.displayName = "RemoteUserMediaTile";
|
RemoteUserMediaTile.displayName = "RemoteUserMediaTile";
|
||||||
|
|
||||||
interface GridTileProps {
|
interface GridTileProps {
|
||||||
|
ref?: Ref<HTMLDivElement>;
|
||||||
vm: GridTileViewModel;
|
vm: GridTileViewModel;
|
||||||
onOpenProfile: (() => void) | null;
|
onOpenProfile: (() => void) | null;
|
||||||
targetWidth: number;
|
targetWidth: number;
|
||||||
@@ -317,8 +320,12 @@ interface GridTileProps {
|
|||||||
showSpeakingIndicators: boolean;
|
showSpeakingIndicators: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GridTile = forwardRef<HTMLDivElement, GridTileProps>(
|
export const GridTile: FC<GridTileProps> = ({
|
||||||
({ vm, onOpenProfile, ...props }, theirRef) => {
|
ref: theirRef,
|
||||||
|
vm,
|
||||||
|
onOpenProfile,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const ourRef = useRef<HTMLDivElement | null>(null);
|
const ourRef = useRef<HTMLDivElement | null>(null);
|
||||||
const ref = useMergedRefs(ourRef, theirRef);
|
const ref = useMergedRefs(ourRef, theirRef);
|
||||||
const media = useObservableEagerState(vm.media$);
|
const media = useObservableEagerState(vm.media$);
|
||||||
@@ -344,7 +351,6 @@ export const GridTile = forwardRef<HTMLDivElement, GridTileProps>(
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
GridTile.displayName = "GridTile";
|
GridTile.displayName = "GridTile";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { type TrackReferenceOrPlaceholder } from "@livekit/components-core";
|
import { type TrackReferenceOrPlaceholder } from "@livekit/components-core";
|
||||||
import { animated } from "@react-spring/web";
|
import { animated } from "@react-spring/web";
|
||||||
import { type RoomMember } from "matrix-js-sdk";
|
import { type RoomMember } from "matrix-js-sdk";
|
||||||
import { type ComponentProps, type ReactNode, forwardRef } from "react";
|
import { type FC, type ComponentProps, type ReactNode } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { VideoTrack } from "@livekit/components-react";
|
import { VideoTrack } from "@livekit/components-react";
|
||||||
@@ -47,9 +47,8 @@ interface Props extends ComponentProps<typeof animated.div> {
|
|||||||
videoStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
videoStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaView = forwardRef<HTMLDivElement, Props>(
|
export const MediaView: FC<Props> = ({
|
||||||
(
|
ref,
|
||||||
{
|
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
targetWidth,
|
targetWidth,
|
||||||
@@ -71,9 +70,7 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
|
|||||||
audioStreamStats,
|
audioStreamStats,
|
||||||
videoStreamStats,
|
videoStreamStats,
|
||||||
...props
|
...props
|
||||||
},
|
}) => {
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
||||||
|
|
||||||
@@ -182,7 +179,6 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
|
|||||||
</div>
|
</div>
|
||||||
</animated.div>
|
</animated.div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
MediaView.displayName = "MediaView";
|
MediaView.displayName = "MediaView";
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type ComponentProps,
|
type ComponentProps,
|
||||||
|
type FC,
|
||||||
|
type Ref,
|
||||||
type RefAttributes,
|
type RefAttributes,
|
||||||
forwardRef,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
@@ -44,6 +45,7 @@ import { useLatest } from "../useLatest";
|
|||||||
import { type SpotlightTileViewModel } from "../state/TileViewModel";
|
import { type SpotlightTileViewModel } from "../state/TileViewModel";
|
||||||
|
|
||||||
interface SpotlightItemBaseProps {
|
interface SpotlightItemBaseProps {
|
||||||
|
ref?: Ref<HTMLDivElement>;
|
||||||
className?: string;
|
className?: string;
|
||||||
"data-id": string;
|
"data-id": string;
|
||||||
targetWidth: number;
|
targetWidth: number;
|
||||||
@@ -67,13 +69,13 @@ interface SpotlightLocalUserMediaItemProps
|
|||||||
vm: LocalUserMediaViewModel;
|
vm: LocalUserMediaViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpotlightLocalUserMediaItem = forwardRef<
|
const SpotlightLocalUserMediaItem: FC<SpotlightLocalUserMediaItemProps> = ({
|
||||||
HTMLDivElement,
|
vm,
|
||||||
SpotlightLocalUserMediaItemProps
|
...props
|
||||||
>(({ vm, ...props }, ref) => {
|
}) => {
|
||||||
const mirror = useObservableEagerState(vm.mirror$);
|
const mirror = useObservableEagerState(vm.mirror$);
|
||||||
return <MediaView ref={ref} mirror={mirror} {...props} />;
|
return <MediaView mirror={mirror} {...props} />;
|
||||||
});
|
};
|
||||||
|
|
||||||
SpotlightLocalUserMediaItem.displayName = "SpotlightLocalUserMediaItem";
|
SpotlightLocalUserMediaItem.displayName = "SpotlightLocalUserMediaItem";
|
||||||
|
|
||||||
@@ -81,16 +83,15 @@ interface SpotlightUserMediaItemProps extends SpotlightItemBaseProps {
|
|||||||
vm: UserMediaViewModel;
|
vm: UserMediaViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpotlightUserMediaItem = forwardRef<
|
const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||||
HTMLDivElement,
|
vm,
|
||||||
SpotlightUserMediaItemProps
|
...props
|
||||||
>(({ vm, ...props }, ref) => {
|
}) => {
|
||||||
const videoEnabled = useObservableEagerState(vm.videoEnabled$);
|
const videoEnabled = useObservableEagerState(vm.videoEnabled$);
|
||||||
const cropVideo = useObservableEagerState(vm.cropVideo$);
|
const cropVideo = useObservableEagerState(vm.cropVideo$);
|
||||||
|
|
||||||
const baseProps: SpotlightUserMediaItemBaseProps &
|
const baseProps: SpotlightUserMediaItemBaseProps &
|
||||||
RefAttributes<HTMLDivElement> = {
|
RefAttributes<HTMLDivElement> = {
|
||||||
ref,
|
|
||||||
videoEnabled,
|
videoEnabled,
|
||||||
videoFit: cropVideo ? "cover" : "contain",
|
videoFit: cropVideo ? "cover" : "contain",
|
||||||
...props,
|
...props,
|
||||||
@@ -101,11 +102,12 @@ const SpotlightUserMediaItem = forwardRef<
|
|||||||
) : (
|
) : (
|
||||||
<MediaView mirror={false} {...baseProps} />
|
<MediaView mirror={false} {...baseProps} />
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
SpotlightUserMediaItem.displayName = "SpotlightUserMediaItem";
|
SpotlightUserMediaItem.displayName = "SpotlightUserMediaItem";
|
||||||
|
|
||||||
interface SpotlightItemProps {
|
interface SpotlightItemProps {
|
||||||
|
ref?: Ref<HTMLDivElement>;
|
||||||
vm: MediaViewModel;
|
vm: MediaViewModel;
|
||||||
targetWidth: number;
|
targetWidth: number;
|
||||||
targetHeight: number;
|
targetHeight: number;
|
||||||
@@ -117,18 +119,15 @@ interface SpotlightItemProps {
|
|||||||
"aria-hidden"?: boolean;
|
"aria-hidden"?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpotlightItem = forwardRef<HTMLDivElement, SpotlightItemProps>(
|
const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||||
(
|
ref: theirRef,
|
||||||
{
|
|
||||||
vm,
|
vm,
|
||||||
targetWidth,
|
targetWidth,
|
||||||
targetHeight,
|
targetHeight,
|
||||||
intersectionObserver$,
|
intersectionObserver$,
|
||||||
snap,
|
snap,
|
||||||
"aria-hidden": ariaHidden,
|
"aria-hidden": ariaHidden,
|
||||||
},
|
}) => {
|
||||||
theirRef,
|
|
||||||
) => {
|
|
||||||
const ourRef = useRef<HTMLDivElement | null>(null);
|
const ourRef = useRef<HTMLDivElement | null>(null);
|
||||||
const ref = useMergedRefs(ourRef, theirRef);
|
const ref = useMergedRefs(ourRef, theirRef);
|
||||||
const displayName = useObservableEagerState(vm.displayname$);
|
const displayName = useObservableEagerState(vm.displayname$);
|
||||||
@@ -167,21 +166,16 @@ const SpotlightItem = forwardRef<HTMLDivElement, SpotlightItemProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return vm instanceof ScreenShareViewModel ? (
|
return vm instanceof ScreenShareViewModel ? (
|
||||||
<MediaView
|
<MediaView videoEnabled videoFit="contain" mirror={false} {...baseProps} />
|
||||||
videoEnabled
|
|
||||||
videoFit="contain"
|
|
||||||
mirror={false}
|
|
||||||
{...baseProps}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<SpotlightUserMediaItem vm={vm} {...baseProps} />
|
<SpotlightUserMediaItem vm={vm} {...baseProps} />
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
SpotlightItem.displayName = "SpotlightItem";
|
SpotlightItem.displayName = "SpotlightItem";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
ref?: Ref<HTMLDivElement>;
|
||||||
vm: SpotlightTileViewModel;
|
vm: SpotlightTileViewModel;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
onToggleExpanded: (() => void) | null;
|
onToggleExpanded: (() => void) | null;
|
||||||
@@ -192,9 +186,8 @@ interface Props {
|
|||||||
style?: ComponentProps<typeof animated.div>["style"];
|
style?: ComponentProps<typeof animated.div>["style"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
export const SpotlightTile: FC<Props> = ({
|
||||||
(
|
ref: theirRef,
|
||||||
{
|
|
||||||
vm,
|
vm,
|
||||||
expanded,
|
expanded,
|
||||||
onToggleExpanded,
|
onToggleExpanded,
|
||||||
@@ -203,17 +196,13 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
showIndicators,
|
showIndicators,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
},
|
}) => {
|
||||||
theirRef,
|
|
||||||
) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
|
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
|
||||||
const ref = useMergedRefs(ourRef, theirRef);
|
const ref = useMergedRefs(ourRef, theirRef);
|
||||||
const maximised = useObservableEagerState(vm.maximised$);
|
const maximised = useObservableEagerState(vm.maximised$);
|
||||||
const media = useObservableEagerState(vm.media$);
|
const media = useObservableEagerState(vm.media$);
|
||||||
const [visibleId, setVisibleId] = useState<string | undefined>(
|
const [visibleId, setVisibleId] = useState<string | undefined>(media[0]?.id);
|
||||||
media[0]?.id,
|
|
||||||
);
|
|
||||||
const latestMedia = useLatest(media);
|
const latestMedia = useLatest(media);
|
||||||
const latestVisibleId = useLatest(visibleId);
|
const latestVisibleId = useLatest(visibleId);
|
||||||
const visibleIndex = media.findIndex((vm) => vm.id === visibleId);
|
const visibleIndex = media.findIndex((vm) => vm.id === visibleId);
|
||||||
@@ -243,9 +232,7 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
|
|
||||||
const [scrollToId, setScrollToId] = useReactiveState<string | null>(
|
const [scrollToId, setScrollToId] = useReactiveState<string | null>(
|
||||||
(prev) =>
|
(prev) =>
|
||||||
prev == null ||
|
prev == null || prev === visibleId || media.every((vm) => vm.id !== prev)
|
||||||
prev === visibleId ||
|
|
||||||
media.every((vm) => vm.id !== prev)
|
|
||||||
? null
|
? null
|
||||||
: prev,
|
: prev,
|
||||||
[visibleId],
|
[visibleId],
|
||||||
@@ -341,7 +328,6 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
)}
|
)}
|
||||||
</animated.div>
|
</animated.div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
SpotlightTile.displayName = "SpotlightTile";
|
SpotlightTile.displayName = "SpotlightTile";
|
||||||
|
|||||||
@@ -5,21 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type MutableRefObject, type RefCallback, useCallback } from "react";
|
import { type RefCallback, type RefObject, useCallback } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines multiple refs into one, useful for attaching multiple refs to the
|
* Combines multiple refs into one, useful for attaching multiple refs to the
|
||||||
* same DOM node.
|
* same DOM node.
|
||||||
*/
|
*/
|
||||||
export const useMergedRefs = <T>(
|
export const useMergedRefs = <T>(
|
||||||
...refs: (MutableRefObject<T | null> | RefCallback<T | null> | null)[]
|
...refs: (RefObject<T | null> | RefCallback<T | null> | null | undefined)[]
|
||||||
): RefCallback<T | null> =>
|
): RefCallback<T | null> =>
|
||||||
useCallback(
|
useCallback(
|
||||||
(value) =>
|
(value) =>
|
||||||
refs.forEach((ref) => {
|
refs.forEach((ref) => {
|
||||||
if (typeof ref === "function") {
|
if (typeof ref === "function") {
|
||||||
ref(value);
|
ref(value);
|
||||||
} else if (ref !== null) {
|
} else if (ref) {
|
||||||
ref.current = value;
|
ref.current = value;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user