Enable some oxlint a11y rules & improve keyboard accessibility (#34291)

* Conform to oxlint rule jsx-a11y/role-has-required-aria-props

* Conform to oxlint rule jsx-a11y/role-supports-aria-props

* Conform to oxlint rule jsx-a11y/interactive-supports-focus

* Conform to oxlint rule jsx-a11y/click-events-have-key-events

* Update snapshots
This commit is contained in:
Michael Telatynski
2026-07-16 17:29:49 +00:00
committed by GitHub
parent 40137cdc54
commit b9972b7e9e
49 changed files with 215 additions and 162 deletions
+4
View File
@@ -446,6 +446,8 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
<Glass className="mx_Dialog_border">
<div className="mx_Dialog">{this.staticModal.elem}</div>
</Glass>
{/* We break the rule here as this is a mouse-only interaction */}
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
data-testid="dialog-background"
className="mx_Dialog_background mx_Dialog_staticBackground"
@@ -480,6 +482,8 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
<Glass className="mx_Dialog_border">
<div className="mx_Dialog">{modal.elem}</div>
</Glass>
{/* We break the rule here as this is a mouse-only interaction */}
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
data-testid="dialog-background"
className="mx_Dialog_background"
+2
View File
@@ -51,6 +51,8 @@ const Toolbar = ({ children, ref, ...props }: IProps): JSX.Element => {
return (
<RovingTabIndexProvider handleHomeEnd handleLeftRight handleUpDown onKeyDown={onKeyDown}>
{({ onKeyDownHandler }) => (
// This may be wrong but seems to work, this is a roving-toolbar, so the focus follows one of the children
// oxlint-disable-next-line jsx-a11y/interactive-supports-focus
<div {...props} onKeyDown={onKeyDownHandler} role="toolbar" ref={ref}>
{children}
</div>
@@ -104,7 +104,6 @@ export default class CommandProvider extends AutocompleteProvider {
return (
<div
className="mx_Autocomplete_Completion_container_pill"
role="presentation"
aria-label={_t("composer|autocomplete|command_a11y")}
>
{completions}
@@ -175,7 +175,6 @@ export default class EmojiProvider extends AutocompleteProvider {
return (
<div
className="mx_Autocomplete_Completion_container_pill"
role="presentation"
aria-label={_t("composer|autocomplete|emoji_a11y")}
>
{completions}
@@ -68,7 +68,6 @@ export default class NotifProvider extends AutocompleteProvider {
return (
<div
className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
role="presentation"
aria-label={_t("composer|autocomplete|notification_a11y")}
>
{completions}
@@ -136,7 +136,6 @@ export default class RoomProvider extends AutocompleteProvider {
return (
<div
className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
role="presentation"
aria-label={_t("composer|autocomplete|room_a11y")}
>
{completions}
@@ -196,7 +196,6 @@ export default class UserProvider extends AutocompleteProvider {
return (
<div
className="mx_Autocomplete_Completion_container_pill"
role="presentation"
aria-label={_t("composer|autocomplete|user_a11y")}
>
{completions}
@@ -115,6 +115,8 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
return (
<div className="mx_AutocompleteInput">
{/* We break the rule here as this is a mouse-only interaction */}
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
ref={editorContainerRef}
className={classNames({
@@ -402,6 +402,8 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
let background: JSX.Element;
if (hasBackground) {
background = (
// We break the rule here as this is a mouse-only interaction
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className="mx_ContextualMenu_background"
style={wrapperStyle}
@@ -180,7 +180,6 @@ const Tile: React.FC<ITileProps> = ({
if (hasPermissions) {
checkbox = (
<StyledCheckbox
role="presentation"
aria-labelledby={checkboxLabelId}
checked={!!selected}
tabIndex={-1}
@@ -198,12 +197,7 @@ const Tile: React.FC<ITileProps> = ({
ev.stopPropagation();
}}
>
<StyledCheckbox
role="presentation"
aria-labelledby={checkboxLabelId}
disabled={true}
tabIndex={-1}
/>
<StyledCheckbox aria-labelledby={checkboxLabelId} disabled={true} tabIndex={-1} />
</TextWithTooltip>
);
}
@@ -287,8 +281,9 @@ const Tile: React.FC<ITileProps> = ({
let childSection: JSX.Element | undefined;
let onKeyDown: KeyboardEventHandler | undefined;
if (children) {
// the chevron is purposefully a div rather than a button as it should be ignored for a11y
childToggle = (
// the chevron is purposefully a div rather than a button as it should be ignored for a11y
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={classNames("mx_SpaceHierarchy_subspace_toggle", {
mx_SpaceHierarchy_subspace_toggle_shown: showChildren,
@@ -102,6 +102,8 @@ function TabLabel<T extends string>({ tab, isActive, showToolip, onClick }: ITab
const label = _t(tab.label);
return (
// The RovingAccessibleComponent correctly sets the tabIndex based on roving context
// oxlint-disable-next-line jsx-a11y/interactive-supports-focus
<RovingAccessibleButton
className={classes}
onClick={onClick}
@@ -58,6 +58,7 @@ const BeaconListItem: React.FC<Props & HTMLProps<HTMLLIElement>> = ({ beacon, ..
>
{/* eat events from interactive share buttons
so parent click handlers are not triggered */}
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div className="mx_BeaconListItem_interactions" onClick={preventDefaultWrapper(() => {})}>
<ShareLatestLocation latestLocationState={latestLocationState} />
</div>
@@ -54,6 +54,9 @@ class MenuOption extends React.Component<IMenuOptionProps> {
});
return (
// We break the rule here as this is a mouse-only interaction,
// the keyboard interaction is handled by the Dropdown parent component
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<li
id={this.props.id}
className={optClasses}
@@ -79,7 +79,6 @@ export class EditableItem extends React.Component<IItemProps, IItemState> {
onClick={this.onRemove}
className="mx_EditableItem_delete"
title={_t("action|remove")}
role="button"
>
<CloseIcon />
</AccessibleButton>
@@ -201,6 +201,8 @@ export default class EditableText extends React.Component<IProps, IState> {
if (!editable || (this.state.phase === Phases.Display && (label || labelClassName) && !this.value)) {
// show the label
editableEl = (
// We break the rule here as this is a mouse-only shortcut
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div className={className + " " + labelClassName} onClick={this.onClickDiv}>
{label || initialValue}
</div>
@@ -99,6 +99,9 @@ const GenericEventListSummary: React.FC<IProps> = ({
body = (
<div className="mx_EventTile_line">
<div className="mx_EventTile_info">
{/* We break the rule here as this is a mouse-only interaction */}
{/* mx_GenericEventListSummary_toggle is the keyboard friendly outer one */}
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */}
<span className="mx_GenericEventListSummary_avatars" onClick={toggleExpanded}>
{avatars}
</span>
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useCallback, useContext, useState } from "react";
import { type Room, EventType } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { Tooltip } from "@vector-im/compound-web";
import { LinkedText } from "@element-hq/web-shared-components";
import { useTopic } from "../../../hooks/room/useTopic";
@@ -20,15 +19,16 @@ import Modal from "../../../Modal";
import InfoDialog from "../dialogs/InfoDialog";
import { useDispatcher } from "../../../hooks/useDispatcher";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton from "./AccessibleButton";
import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
import { topicToHtml } from "../../../HtmlUtils";
import { tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks";
interface IProps extends React.HTMLProps<HTMLDivElement> {
interface IProps {
room: Room;
className?: string;
}
export function onRoomTopicLinkClick(e: React.MouseEvent): void {
export function onRoomTopicLinkClick(e: ButtonEvent): void {
const anchor = e.target as HTMLLinkElement;
const localHref = tryTransformPermalinkToLocalHref(anchor.href);
@@ -39,28 +39,23 @@ export function onRoomTopicLinkClick(e: React.MouseEvent): void {
}
}
export default function RoomTopic({ room, className, ...props }: IProps): JSX.Element {
export default function RoomTopic({ room, className }: IProps): JSX.Element {
const client = useContext(MatrixClientContext);
const [disableTooltip, setDisableTooltip] = useState(false);
const topic = useTopic(room);
const body = topicToHtml(topic?.text, topic?.html);
const onClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
props.onClick?.(e);
const onClick = useCallback((e: ButtonEvent) => {
const target = e.target as HTMLElement;
const target = e.target as HTMLElement;
if (target.tagName.toUpperCase() !== "A") {
dis.fire(Action.ShowRoomTopic);
return;
}
if (target.tagName.toUpperCase() !== "A") {
dis.fire(Action.ShowRoomTopic);
return;
}
onRoomTopicLinkClick(e);
},
[props],
);
onRoomTopicLinkClick(e);
}, []);
const onHover = (ev: React.MouseEvent | React.FocusEvent): void => {
setDisableTooltip((ev.target as HTMLElement).tagName.toUpperCase() === "A");
@@ -100,19 +95,15 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El
if (!body) return <div className={classNames(className, "mx_RoomTopic")} />;
return (
<Tooltip description={_t("room|read_topic")} disabled={disableTooltip}>
<div
{...props}
tabIndex={0}
role="button"
onClick={onClick}
className={classNames(className, "mx_RoomTopic")}
onMouseOver={onHover}
onFocus={onHover}
aria-label={_t("room|read_topic")}
>
<LinkedText>{body}</LinkedText>
</div>
</Tooltip>
<AccessibleButton
onClick={onClick}
className={classNames(className, "mx_RoomTopic")}
onMouseOver={onHover}
onFocus={onHover}
title={_t("room|read_topic")}
disableTooltip={disableTooltip}
>
<LinkedText>{body}</LinkedText>
</AccessibleButton>
);
}
@@ -50,6 +50,7 @@ export default ({ checked, disabled = false, onChange, title, tooltip, ...props
className={classes}
onClick={_onClick}
role="switch"
tabIndex={0}
aria-label={title}
aria-checked={checked}
aria-disabled={disabled}
@@ -174,6 +174,8 @@ const MapComponent: React.FC<MapProps> = ({
};
return (
// We break the rule here as this is a mouse-only interaction
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div className={classNames("mx_Map", className)} id={bodyId} onClick={onMapClick}>
{!!children && !!map && children({ map })}
</div>
@@ -24,6 +24,8 @@ export interface MapErrorProps {
}
export const MapError: React.FC<MapErrorProps> = ({ error, isMinimised, className, onFinished, onClick }) => (
// We break the rule here as this is a mouse-only interaction
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div
data-testid="map-rendering-error"
className={classNames("mx_MapError", className, { mx_MapError_isMinimised: isMinimised })}
@@ -46,6 +46,8 @@ const OptionalTooltip: React.FC<{
};
return (
// We break the rule here as this is a mouse-only interaction
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div onMouseEnter={show} onClick={toggleVisibility} onMouseLeave={hide}>
{children}
{isVisible && tooltip}
@@ -13,6 +13,8 @@ import { CollapseIcon, CopyIcon, ExpandIcon } from "@vector-im/compound-design-t
import { useSettingValue } from "../../../hooks/useSettings.ts";
import { CopyTextButton } from "../elements/CopyableText.tsx";
import AccessibleButton from "../elements/AccessibleButton.tsx";
import { _t } from "../../../i18n";
const MAX_HIGHLIGHT_LENGTH = 4096;
const MAX_LINES_BEFORE_COLLAPSE = 5;
@@ -26,9 +28,14 @@ const ExpandCollapseButton: React.FC<{
onClick(this: void): void;
}> = ({ expanded, onClick }) => {
return (
<span className="mx_EventTile_button" onClick={onClick}>
<AccessibleButton
element="button"
title={expanded ? _t("action|collapse") : _t("action|expand")}
onClick={onClick}
className="mx_EventTile_button"
>
{expanded ? <CollapseIcon /> : <ExpandIcon />}
</span>
</AccessibleButton>
);
};
@@ -14,10 +14,11 @@ import { BLURHASH_FIELD } from "../../../utils/image-media";
import IconsShowStickersSvg from "../../../../res/img/icons-show-stickers.svg";
import { type IBodyProps } from "./IBodyProps";
import { useMediaVisible } from "../../../hooks/useMediaVisible";
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton.tsx";
class MStickerBodyInner extends ImageBodyBaseInner {
// Mostly empty to prevent default behaviour of MImageBody
protected onClick = (ev: React.MouseEvent): void => {
protected onClick = (ev: ButtonEvent): void => {
ev.preventDefault();
if (!this.props.mediaVisible) {
this.props.setMediaVisible(true);
@@ -27,16 +28,15 @@ class MStickerBodyInner extends ImageBodyBaseInner {
// MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding
// which is added by mx_MStickerBody_wrapper
protected wrapImage(contentUrl: string, children: React.ReactNode): JSX.Element {
let onClick: React.MouseEventHandler | undefined;
if (!this.props.mediaVisible) {
onClick = this.onClick;
const content = <> {children} </>;
if (this.props.mediaVisible) {
return (
<AccessibleButton className="mx_MStickerBody_wrapper" onClick={this.onClick}>
{content}
</AccessibleButton>
);
}
return (
<div className="mx_MStickerBody_wrapper" onClick={onClick}>
{" "}
{children}{" "}
</div>
);
return <div className="mx_MStickerBody_wrapper">{content}</div>;
}
// Placeholder to show in place of the sticker image if img onLoad hasn't fired yet.
@@ -112,6 +112,8 @@ export const PollOption: React.FC<PollOptionProps> = ({
const isWinner = isEnded && isChecked;
const answerPercent = totalVoteCount === 0 ? 0 : Math.round((100.0 * voteCount) / totalVoteCount);
return (
// We break the rule here as ActivePollOption is keyboard accessible, we are just making the click target larger
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div data-testid={`pollOption-${answer.id}`} className={cls} onClick={() => onOptionSelected?.(answer.id)}>
<ActivePollOption
pollId={pollId}
@@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { type PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { Tooltip } from "@vector-im/compound-web";
import { PollsIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { formatLocalDateShort } from "../../../../DateUtils";
import { _t } from "../../../../languageHandler";
import AccessibleButton from "../../elements/AccessibleButton.tsx";
interface Props {
event: MatrixEvent;
@@ -27,14 +27,19 @@ export const PollListItem: React.FC<Props> = ({ event, onClick }) => {
}
const formattedDate = formatLocalDateShort(event.getTs());
return (
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItem" onClick={onClick}>
<Tooltip label={_t("right_panel|poll|view_poll")} placement="top" isTriggerInteractive={false}>
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItem">
<AccessibleButton
className="mx_PollListItemEnded_content"
title={_t("right_panel|poll|view_poll")}
placement="top"
onClick={onClick}
>
<div className="mx_PollListItem_content">
<span>{formattedDate}</span>
<PollsIcon className="mx_PollListItem_icon" />
<span className="mx_PollListItem_question">{pollEvent.question.text}</span>
</div>
</Tooltip>
</AccessibleButton>
</li>
);
};
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import React, { useEffect, useState } from "react";
import { type PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { type MatrixEvent, type Poll, PollEvent, type Relations } from "matrix-js-sdk/src/matrix";
import { Tooltip } from "@vector-im/compound-web";
import { PollsEndIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../../languageHandler";
@@ -17,6 +16,7 @@ import { formatLocalDateShort } from "../../../../DateUtils";
import { allVotes, collectUserVotes, countVotes } from "../../messages/MPollBody";
import { PollOption } from "../../polls/PollOption";
import { Caption } from "../../typography/Caption";
import AccessibleButton from "../../elements/AccessibleButton.tsx";
interface Props {
event: MatrixEvent;
@@ -93,36 +93,39 @@ export const PollListItemEnded: React.FC<Props> = ({ event, poll, onClick }) =>
const formattedDate = formatLocalDateShort(event.getTs());
return (
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItemEnded" onClick={onClick}>
<Tooltip label={_t("right_panel|poll|view_poll")} placement="top" isTriggerInteractive={false}>
<div className="mx_PollListItemEnded_content">
<div className="mx_PollListItemEnded_title">
<PollsEndIcon className="mx_PollListItemEnded_icon" />
<span className="mx_PollListItemEnded_question">{pollEvent.question.text}</span>
<Caption>{formattedDate}</Caption>
</div>
{!!winningAnswers?.length && (
<div className="mx_PollListItemEnded_answers">
{winningAnswers?.map(({ answer, voteCount, optionNumber }) => (
<PollOption
key={answer.id}
answer={answer}
voteCount={voteCount}
totalVoteCount={totalVoteCount!}
pollId={poll.pollId}
optionNumber={optionNumber}
displayVoteCount
isChecked
isEnded
/>
))}
</div>
)}
<div className="mx_PollListItemEnded_voteCount">
<Caption>{_t("right_panel|poll|final_result", { count: totalVoteCount })}</Caption>
</div>
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItemEnded">
<AccessibleButton
className="mx_PollListItemEnded_content"
title={_t("right_panel|poll|view_poll")}
placement="top"
onClick={onClick}
>
<div className="mx_PollListItemEnded_title">
<PollsEndIcon className="mx_PollListItemEnded_icon" />
<span className="mx_PollListItemEnded_question">{pollEvent.question.text}</span>
<Caption>{formattedDate}</Caption>
</div>
</Tooltip>
{!!winningAnswers?.length && (
<div className="mx_PollListItemEnded_answers">
{winningAnswers?.map(({ answer, voteCount, optionNumber }) => (
<PollOption
key={answer.id}
answer={answer}
voteCount={voteCount}
totalVoteCount={totalVoteCount!}
pollId={poll.pollId}
optionNumber={optionNumber}
displayVoteCount
isChecked
isEnded
/>
))}
</div>
)}
<div className="mx_PollListItemEnded_voteCount">
<Caption>{_t("right_panel|poll|final_result", { count: totalVoteCount })}</Caption>
</div>
</AccessibleButton>
</li>
);
};
@@ -128,6 +128,7 @@ const BaseCard: React.FC<IProps> = ({
weight="medium"
className="mx_BaseCard_header_title_heading"
role="heading"
aria-level={1}
>
{header}
</Text>
@@ -857,6 +857,8 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
aria-multiline="true"
aria-autocomplete="list"
aria-haspopup="listbox"
// This is not strictly speaking a supported role, we should investigate the impact of this in the future
// oxlint-disable-next-line jsx-a11y/role-supports-aria-props
aria-expanded={hasAutocomplete ? !this.autocompleteRef.current?.state.hide : undefined}
aria-owns={hasAutocomplete ? "mx_Autocomplete" : undefined}
aria-activedescendant={activeDescendant}
@@ -46,6 +46,7 @@ export function MemberTileView(props: Props): JSX.Element {
aria-label={props?.ariaLabel}
tabIndex={props.tabIndex}
role="option"
aria-selected={props.tabIndex === 0}
aria-posinset={props.memberIndex + 1}
aria-setsize={props.memberCount}
>
@@ -114,7 +114,7 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
if (this.state.canKick && this.state.invited) {
adminTools = (
<Flex direction="column" as="section" justify="start" gap="var(--cpd-space-2x)">
<Text as="span" role="heading" size="lg" weight="semibold">
<Text as="span" role="heading" size="lg" weight="semibold" aria-level={3}>
{_t("user_info|admin_tools_section")}
</Text>
<Button size="md" kind="destructive" className="mx_MemberInfo_field" onClick={this.onKickClick}>
@@ -129,7 +129,7 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
<Flex className="mx_ThirdPartyMemberInfo" direction="column" gap="var(--cpd-space-4x)">
<Flex direction="column" as="section" justify="start" gap="var(--cpd-space-2x)">
{/* same as userinfo name style */}
<Text as="span" role="heading" size="lg" weight="semibold">
<Text as="span" role="heading" size="lg" weight="semibold" aria-level={2}>
{this.state.displayName}
</Text>
<Text as="span">{_t("user_info|invited_by", { sender: this.state.senderName })}</Text>
@@ -50,7 +50,7 @@ const DeviceNameEditor: React.FC<Props & { stopEditing: () => void }> = ({ devic
const descriptionId = `device-rename-description-${device.device_id}`;
return (
<form aria-disabled={isLoading} className="mx_DeviceDetailHeading_renameForm" onSubmit={onSubmit} method="post">
<form className="mx_DeviceDetailHeading_renameForm" onSubmit={onSubmit} method="post">
<p id={headingId} className="mx_DeviceDetailHeading_renameFormHeading">
{_t("settings|sessions|rename_form_heading")}
</p>
@@ -31,6 +31,8 @@ const DeviceTileName: React.FC<{ device: ExtendedDevice }> = ({ device }) => {
const DeviceTile: React.FC<DeviceTileProps> = ({ device, children, isSelected, onClick }) => {
return (
// We break the rule here as this is a mouse-only shortcut
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={classNames("mx_DeviceTile", { mx_DeviceTile_interactive: !!onClick })}
data-testid={`device-tile-${device.device_id}`}
@@ -43,6 +45,8 @@ const DeviceTile: React.FC<DeviceTileProps> = ({ device, children, isSelected, o
<DeviceMetaData device={device} />
</div>
</div>
{/* We break the rule here as this is a mouse-only interaction */}
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div className="mx_DeviceTile_actions" onClick={preventDefaultWrapper(() => {})}>
{children}
</div>
@@ -12,6 +12,7 @@ exports[`FilePanel renders empty state 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -3628,6 +3628,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -137,7 +137,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
aria-labelledby="react-use-id-1"
class="_input_153f2_18"
id="checkbox_MwbPDmfGtm"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -235,7 +234,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
aria-labelledby="react-use-id-2"
class="_input_153f2_18"
id="checkbox_GQvdMWe954"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -334,7 +332,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
aria-labelledby="react-use-id-3"
class="_input_153f2_18"
id="checkbox_DVIAu5CsiH"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -455,7 +452,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
class="_input_153f2_18"
disabled=""
id="checkbox_RD7nyrA2oh"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -556,7 +552,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
aria-labelledby="react-use-id-6"
class="_input_153f2_18"
id="checkbox_jWVJIPauy1"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -727,7 +722,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> should not render cycles 1`] = `
aria-labelledby="react-use-id-1"
class="_input_153f2_18"
id="checkbox_EetmBG4yVC"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -825,7 +819,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> should not render cycles 1`] = `
aria-labelledby="react-use-id-2"
class="_input_153f2_18"
id="checkbox_eEefiPqpMR"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -924,7 +917,6 @@ exports[`SpaceHierarchy <SpaceHierarchy /> should not render cycles 1`] = `
aria-labelledby="react-use-id-3"
class="_input_153f2_18"
id="checkbox_MwbPDmfGtm"
role="presentation"
tabindex="-1"
type="checkbox"
/>
@@ -467,8 +467,11 @@ num_sqrt = num **
</code>
</div>
</pre>
<span
class="mx_EventTile_button"
<button
aria-label="Expand"
class="mx_AccessibleButton mx_EventTile_button"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
@@ -481,7 +484,7 @@ num_sqrt = num **
d="M21 3.997a1 1 0 0 0-.29-.702l-.005-.004A1 1 0 0 0 20 3h-8a1 1 0 1 0 0 2h5.586L5 17.586V12a1 1 0 1 0-2 0v8.003a1 1 0 0 0 .29.702l.005.004c.18.18.43.291.705.291h8a1 1 0 1 0 0-2H6.414L19 6.414V12a1 1 0 1 0 2 0z"
/>
</svg>
</span>
</button>
<button
aria-label="Copy"
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton mx_EventTile_buttonBottom"
@@ -114,11 +114,13 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
class="mx_PollListItem"
data-testid="pollListItem-$2"
>
<span
<div
aria-label="View poll"
class="mx_AccessibleButton mx_PollListItemEnded_content"
role="button"
tabindex="0"
>
<div
aria-labelledby="react-use-id-1"
class="mx_PollListItem_content"
>
<span>
@@ -142,17 +144,19 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
Where?
</span>
</div>
</span>
</div>
</li>
<li
class="mx_PollListItem"
data-testid="pollListItem-$1"
>
<span
<div
aria-label="View poll"
class="mx_AccessibleButton mx_PollListItemEnded_content"
role="button"
tabindex="0"
>
<div
aria-labelledby="react-use-id-2"
class="mx_PollListItem_content"
>
<span>
@@ -176,7 +180,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
Question?
</span>
</div>
</span>
</div>
</li>
</ol>
</div>
@@ -6,11 +6,13 @@ exports[`<PollListItem /> renders a poll 1`] = `
class="mx_PollListItem"
data-testid="pollListItem-$mypoll"
>
<span
<div
aria-label="View poll"
class="mx_AccessibleButton mx_PollListItemEnded_content"
role="button"
tabindex="0"
>
<div
aria-labelledby="react-use-id-1"
class="mx_PollListItem_content"
>
<span>
@@ -34,7 +36,7 @@ exports[`<PollListItem /> renders a poll 1`] = `
Question?
</span>
</div>
</span>
</div>
</li>
</div>
`;
@@ -6,53 +6,51 @@ exports[`<PollListItemEnded /> renders a poll with no responses 1`] = `
class="mx_PollListItemEnded"
data-testid="pollListItem-1"
>
<span
<div
aria-label="View poll"
class="mx_AccessibleButton mx_PollListItemEnded_content"
role="button"
tabindex="0"
>
<div
aria-labelledby="react-use-id-1"
class="mx_PollListItemEnded_content"
class="mx_PollListItemEnded_title"
>
<div
class="mx_PollListItemEnded_title"
<svg
class="mx_PollListItemEnded_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<svg
class="mx_PollListItemEnded_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21 10.659V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21H5q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h8.341A6 6 0 0 0 13 5H5v14h14v-8a6 6 0 0 0 2-.341"
/>
<path
d="M13.803 8a6 6 0 0 0 1.88 2H13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 9q0-.424.287-.713A.97.97 0 0 1 13 8zm2.91 7.713A.97.97 0 0 1 16 16h-3a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 15q0-.424.287-.713A.97.97 0 0 1 13 14h3q.424 0 .712.287.288.288.288.713 0 .424-.288.713m-6.299-5.3A1.93 1.93 0 0 1 9 11q-.825 0-1.412-.588A1.93 1.93 0 0 1 7 9q0-.825.588-1.412A1.93 1.93 0 0 1 9 7q.825 0 1.412.588Q11 8.175 11 9t-.588 1.412m.001 6.001A1.93 1.93 0 0 1 9 17q-.825 0-1.412-.587A1.93 1.93 0 0 1 7 15q0-.825.588-1.412A1.93 1.93 0 0 1 9 13q.825 0 1.412.588Q11 14.175 11 15q0 .824-.588 1.413m12.295-14.12a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L18 5.586l3.293-3.293a1 1 0 0 1 1.414 0"
/>
</svg>
<span
class="mx_PollListItemEnded_question"
>
Question?
</span>
<span
class="mx_Caption"
>
02/02/23
</span>
</div>
<div
class="mx_PollListItemEnded_voteCount"
<path
d="M21 10.659V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21H5q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h8.341A6 6 0 0 0 13 5H5v14h14v-8a6 6 0 0 0 2-.341"
/>
<path
d="M13.803 8a6 6 0 0 0 1.88 2H13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 9q0-.424.287-.713A.97.97 0 0 1 13 8zm2.91 7.713A.97.97 0 0 1 16 16h-3a.97.97 0 0 1-.713-.287A.97.97 0 0 1 12 15q0-.424.287-.713A.97.97 0 0 1 13 14h3q.424 0 .712.287.288.288.288.713 0 .424-.288.713m-6.299-5.3A1.93 1.93 0 0 1 9 11q-.825 0-1.412-.588A1.93 1.93 0 0 1 7 9q0-.825.588-1.412A1.93 1.93 0 0 1 9 7q.825 0 1.412.588Q11 8.175 11 9t-.588 1.412m.001 6.001A1.93 1.93 0 0 1 9 17q-.825 0-1.412-.587A1.93 1.93 0 0 1 7 15q0-.825.588-1.412A1.93 1.93 0 0 1 9 13q.825 0 1.412.588Q11 14.175 11 15q0 .824-.588 1.413m12.295-14.12a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L18 5.586l3.293-3.293a1 1 0 0 1 1.414 0"
/>
</svg>
<span
class="mx_PollListItemEnded_question"
>
<span
class="mx_Caption"
>
Final result based on 0 votes
</span>
</div>
Question?
</span>
<span
class="mx_Caption"
>
02/02/23
</span>
</div>
</span>
<div
class="mx_PollListItemEnded_voteCount"
>
<span
class="mx_Caption"
>
Final result based on 0 votes
</span>
</div>
</div>
</li>
</div>
`;
@@ -12,6 +12,7 @@ exports[`<BaseCard /> should close when clicking X button 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -12,6 +12,7 @@ exports[`<ExtensionsCard /> should render empty state 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -113,6 +114,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -12,6 +12,7 @@ exports[`<PinnedMessagesCard /> should show the empty state when there are no pi
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -94,6 +95,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -322,6 +324,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -12,6 +12,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -314,6 +315,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -12,6 +12,7 @@ exports[`<ThirdPartyMemberInfo /> should render invite 1`] = `
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -58,6 +59,7 @@ exports[`<ThirdPartyMemberInfo /> should render invite 1`] = `
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
>
<span
aria-level="2"
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
role="heading"
>
@@ -87,6 +89,7 @@ exports[`<ThirdPartyMemberInfo /> should render invite when room in not availabl
class="mx_BaseCard_header_title"
>
<p
aria-level="1"
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
role="heading"
>
@@ -133,6 +136,7 @@ exports[`<ThirdPartyMemberInfo /> should render invite when room in not availabl
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
>
<span
aria-level="2"
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
role="heading"
>
@@ -6,6 +6,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
<div
aria-label="@userId:matrix.org"
aria-posinset="1"
aria-selected="false"
aria-setsize="1"
class="mx_AccessibleButton mx_MemberTileView"
role="option"
@@ -84,6 +85,7 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
<div
aria-label="@userId:matrix.org"
aria-posinset="1"
aria-selected="false"
aria-setsize="1"
class="mx_AccessibleButton mx_MemberTileView"
role="option"
@@ -162,6 +164,7 @@ exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when th
<div
aria-label="@userId:matrix.org"
aria-posinset="1"
aria-selected="false"
aria-setsize="1"
class="mx_AccessibleButton mx_MemberTileView"
role="option"
@@ -222,6 +225,7 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
<div
aria-label="Foobar"
aria-posinset="1"
aria-selected="false"
aria-setsize="1"
class="mx_AccessibleButton mx_MemberTileView"
role="option"
@@ -4,7 +4,6 @@ exports[`<DeviceDetailHeading /> displays name edit form on rename button click
{
"container": <div>
<form
aria-disabled="false"
class="mx_DeviceDetailHeading_renameForm"
method="post"
>
+6 -6
View File
@@ -163,18 +163,14 @@ export default defineConfig({
"react-perf/jsx-no-new-array-as-prop": "off",
"react/no-did-update-set-state": "off",
"react/no-did-mount-set-state": "off",
"jsx-a11y/interactive-supports-focus": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/click-events-have-key-events": "off",
"vitest/no-conditional-tests": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"react/no-array-index-key": "off",
"jsx-a11y/control-has-associated-label": "off",
"jsx-a11y/role-supports-aria-props": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
"jsx-a11y/aria-activedescendant-has-tabindex": "off",
"jsx-a11y/role-has-required-aria-props": "off",
},
overrides: [
{
@@ -417,13 +413,17 @@ export default defineConfig({
],
"jsdoc/check-tag-names": "off",
"react/jsx-no-constructed-context-values": "off",
// Disable a11y rules for components in tests
"jsx-a11y/role-has-required-aria-props": "off",
"jsx-a11y/interactive-supports-focus": "off",
"jsx-a11y/no-static-element-interactions": "off",
"react/no-array-index-key": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
"jsx-a11y/role-supports-aria-props": "off",
"react/jsx-no-constructed-context-values": "off",
"react/no-array-index-key": "off",
"react/forbid-elements": "off",
// This would be good to enable in the future
"typescript/await-thenable": "off",
@@ -104,6 +104,8 @@ export const RoomListPrimaryFilters = memo(function RoomListPrimaryFilters({
<ChatFilter
key={`${filterId}-${index}`}
role="option"
tabIndex={0}
aria-selected={filterId === activeFilterId}
selected={filterId === activeFilterId}
onClick={() => onToggleFilter(filterId)}
>
@@ -376,6 +376,8 @@ export function ActionBarView({ vm, className }: Readonly<ActionBarViewProps>):
return (
<RovingTabIndexProvider key={rovingProviderKey} handleLeftRight handleHomeEnd handleLoop>
{({ onKeyDownHandler }) => (
// This may be wrong but seems to work, this is a roving-toolbar, so the focus follows one of the children
// oxlint-disable-next-line jsx-a11y/interactive-supports-focus
<Flex
display="inline-flex"
direction="row"