diff --git a/package.json b/package.json index d9b67acd65804bdd711ea1f38887276bde62e4b4..76e97babb4dd20417d69703f72ef7a132a1c568b 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "temporal-polyfill": "^0.3.0", "ua-parser-js": "^1.0.2", "uuid": "^11.0.0", + "virtua": "^0.41.0", "what-input": "^5.2.10" }, "devDependencies": { diff --git a/src/components/views/rooms/MemberList/MemberListView.tsx b/src/components/views/rooms/MemberList/MemberListView.tsx index 64c88113fa6de2e17dd3590a1bda370088fff7f5..58d5f240b3a3af6bfe55d9565b45c6fe114c25e2 100644 --- a/src/components/views/rooms/MemberList/MemberListView.tsx +++ b/src/components/views/rooms/MemberList/MemberListView.tsx @@ -7,7 +7,8 @@ Please see LICENSE files in the repository root for full details. import { Form } from "@vector-im/compound-web"; import React, { useCallback, useRef, type JSX } from "react"; -import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; +import { Virtualizer, VirtualizerHandle } from "virtua"; +// import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; import { Flex } from "../../../utils/Flex"; import { @@ -29,9 +30,9 @@ interface IProps { const MemberListView: React.FC<IProps> = (props: IProps) => { const vm = useMemberListViewModel(props.roomId); const totalRows = vm.members.length; - const ref = useRef<VirtuosoHandle | null>(null); + const ref = useRef<VirtualizerHandle | null>(null); + const scrollRef = useRef<HTMLDivElement | null>(null); const [focusedIndex, setFocusedIndex] = React.useState(-1); - const listRef = useRef<HTMLButtonElement | null>(null); const getRowComponent = (item: MemberWithSeparator, focused: boolean): JSX.Element => { if (item === SEPARATOR) { @@ -39,19 +40,16 @@ const MemberListView: React.FC<IProps> = (props: IProps) => { } else if (item.member) { return <RoomMemberTileView member={item.member} showPresence={vm.isPresenceEnabled} focused={focused} />; } else { - return <ThreePidInviteTileView threePidInvite={item.threePidInvite} />; + return <ThreePidInviteTileView threePidInvite={item.threePidInvite} focused={focused} />; } }; const scrollToIndex = useCallback( (index: number): void => { - ref?.current?.scrollIntoView({ - index: index, - behavior: "auto", - done: () => { - setFocusedIndex(index); - }, + ref?.current?.scrollToIndex(index, { + align: "nearest", }); + setFocusedIndex(index); }, [ref], ); @@ -81,18 +79,6 @@ const MemberListView: React.FC<IProps> = (props: IProps) => { [scrollToIndex, focusedIndex, setFocusedIndex, vm, totalRows], ); - const scrollerRef = useCallback( - (element: any) => { - if (element) { - element.addEventListener("keydown", keyDownCallback); - listRef.current = element; - } else { - listRef?.current?.removeEventListener("keydown", keyDownCallback); - } - }, - [keyDownCallback], - ); - const onFocus = (e: React.FocusEvent): void => { const nextIndex = focusedIndex == -1 ? 0 : focusedIndex; scrollToIndex(nextIndex); @@ -116,20 +102,24 @@ const MemberListView: React.FC<IProps> = (props: IProps) => { <Form.Root> <MemberListHeaderView vm={vm} /> </Form.Root> - <Virtuoso + <div + style={{ + overflowY: "auto", + // opt out browser's scroll anchoring on header/footer because it will conflict to scroll anchoring of virtualizer + overflowAnchor: "none", + }} aria-label={_t("room_list|list_title")} role="grid" - ref={ref} - style={{ height: "100%" }} - scrollerRef={scrollerRef} - context={{ focusedIndex }} - // Don't focus on the table as a whole go straight to the first item in the list - tabIndex={undefined} - data={vm.members} + ref={scrollRef} onFocus={onFocus} - itemContent={(index, member) => getRowComponent(member, index === focusedIndex)} - components={{ Footer: () => footer() }} - /> + onKeyDown={keyDownCallback} + tabIndex={0} + > + <Virtualizer ref={ref} scrollRef={scrollRef}> + {vm.members.map((member, index) => getRowComponent(member, index === focusedIndex))} + </Virtualizer> + {footer()} + </div> </Flex> </BaseCard> ); diff --git a/yarn.lock b/yarn.lock index ec8336e202a9516d2c1499662d15ed004241e22e..8cd8b472ef667397dee86cf619fdbff5007740fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3729,7 +3729,7 @@ resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.38.3.tgz#cc54d8b3e9472bcd8e622126ba364ee31952cd8a" integrity sha512-fqo8P55Vc/t0vxpFar9RDJN5gKEjJmzrLo+O4piDbFda6VrRoqrWAtiu0Au0g6B4hRDPKIuFupk8v9Ja7q8Hvg== dependencies: - "@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm" + "@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm" "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" @@ -13020,6 +13020,11 @@ vaul@^1.0.0: dependencies: "@radix-ui/react-dialog" "^1.1.1" +virtua@^0.41.0: + version "0.41.0" + resolved "https://registry.yarnpkg.com/virtua/-/virtua-0.41.0.tgz#1e3c847baceb14c57e14be36272903f80a95f006" + integrity sha512-P8XJhPAz3mNrxAjPc+NRvD8a8+JtInKgHXuvtucThXW93U1ztUKJ2TDCAMaCPRY0CQCR465mYcOf26yQkCHWEw== + vt-pbf@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac"