feat(new room list): improve list accessibility
This commit is contained in:
@@ -5,8 +5,9 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, type JSX } from "react";
|
||||
import React, { useCallback, type JSX, useEffect, useRef, type RefObject } from "react";
|
||||
import { AutoSizer, List, type ListRowProps } from "react-virtualized";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
@@ -30,9 +31,11 @@ export function RoomList({ vm: { rooms, openRoom } }: RoomListProps): JSX.Elemen
|
||||
[rooms, openRoom],
|
||||
);
|
||||
|
||||
const ref = useAccessibleList(rooms);
|
||||
|
||||
// The first div is needed to make the virtualized list take all the remaining space and scroll correctly
|
||||
return (
|
||||
<div className="mx_RoomList" data-testid="room-list">
|
||||
<div className="mx_RoomList" data-testid="room-list" ref={ref}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
@@ -43,9 +46,61 @@ export function RoomList({ vm: { rooms, openRoom } }: RoomListProps): JSX.Elemen
|
||||
rowHeight={48}
|
||||
height={height}
|
||||
width={width}
|
||||
role="listbox"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the list of rooms accessible. The ref should be put on the list node.
|
||||
*
|
||||
* The list rendered by react-virtualized has not the best role and attributes for accessibility.
|
||||
* The react-virtualized list has the following a11y attributes: "grid" -> "row" -> ["gridcell", "gridcell", "gridcell"].
|
||||
* Using a grid role is not the best choice for a list of items, we want instead a listbox role with children having an option role.
|
||||
*
|
||||
* The listbox and option roles can be set directly in the jsx of the `List` and the `RoomListCell` component but the "gridcell" role is remaining.
|
||||
* This hook removes the "gridcell" role from the list items and set "aria-setsize" on the list too.
|
||||
*
|
||||
* @returns The ref to put on the list node.
|
||||
*/
|
||||
function useAccessibleList(rooms: Room[]): RefObject<HTMLDivElement> {
|
||||
// To be put on the list node
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
const list = ref.current?.querySelector('[role="listbox"]');
|
||||
if (!list) return;
|
||||
|
||||
// The list is virtualized so the number of items in the dom is not the same as the number of rooms
|
||||
list.setAttribute("aria-setsize", `${rooms.length}`);
|
||||
|
||||
// Determine if a node is a row node
|
||||
const isRowNode = (node: HTMLElement): boolean => node.getAttribute("role") === "row";
|
||||
|
||||
// Watch the node with the "row" role to be added to the dom and remove the role
|
||||
// If the role is re-added/modified later, we remove it too
|
||||
const observer = new MutationObserver((mutationList) => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "childList") {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node instanceof HTMLElement && isRowNode(node)) {
|
||||
node.removeAttribute("role");
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.target instanceof HTMLElement &&
|
||||
isRowNode(mutation.target)
|
||||
) {
|
||||
mutation.target.removeAttribute("role");
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(list, { childList: true, subtree: true, attributeFilter: ["role"] });
|
||||
return () => observer.disconnect();
|
||||
}, [rooms]);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ export function RoomListCell({ room, ...props }: RoomListCellProps): JSX.Element
|
||||
type="button"
|
||||
aria-label={_t("room_list|room|open_room", { roomName: room.name })}
|
||||
{...props}
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
>
|
||||
{/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */}
|
||||
<Flex className="mx_RoomListCell_container" gap="var(--cpd-space-3x)" align="center">
|
||||
|
||||
Reference in New Issue
Block a user