Iterate landmarks around the app in order to improve a11y (#12064)
* Iterate landmarks around the app in order to improve a11y Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add missing aria-label Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshots which have changed a fraction due to default heading margins being different in different landmarks Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>pull/28788/head^2
|
@ -25,7 +25,7 @@ test.describe("UserView", () => {
|
|||
test("should render the user view as expected", async ({ page, homeserver, user, bot }) => {
|
||||
await page.goto(`/#/user/${bot.credentials.userId}`);
|
||||
|
||||
const rightPanel = page.getByRole("complementary");
|
||||
const rightPanel = page.locator("#mx_RightPanel");
|
||||
await expect(rightPanel.getByRole("heading", { name: bot.credentials.displayName, exact: true })).toBeVisible();
|
||||
await expect(rightPanel.getByText("1 session")).toBeVisible();
|
||||
await expect(rightPanel).toMatchScreenshot("user-info.png", {
|
||||
|
|
|
@ -90,11 +90,11 @@ export class Settings {
|
|||
}
|
||||
|
||||
/**
|
||||
* Open room settings (via room menu), returns a locator to the dialog
|
||||
* Open room settings (via room header menu), returns a locator to the dialog
|
||||
* @param tab the name of the tab to switch to after opening, optional.
|
||||
*/
|
||||
public async openRoomSettings(tab?: string): Promise<Locator> {
|
||||
await this.page.getByRole("main").getByRole("button", { name: "Room options", exact: true }).click();
|
||||
await this.page.getByRole("banner").getByRole("button", { name: "Room options", exact: true }).click();
|
||||
await this.page.locator(".mx_RoomTile_contextMenu").getByRole("menuitem", { name: "Settings" }).click();
|
||||
if (tab) await this.switchTab(tab);
|
||||
return this.page.locator(".mx_Dialog").filter({ has: this.page.locator(".mx_RoomSettingsDialog") });
|
||||
|
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 563 KiB After Width: | Height: | Size: 563 KiB |
|
@ -315,6 +315,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
if (this.state.showBreadcrumbs === BreadcrumbsMode.Legacy && !this.props.isMinimized) {
|
||||
return (
|
||||
<IndicatorScrollbar
|
||||
role="navigation"
|
||||
aria-label={_t("a11y|recent_rooms")}
|
||||
className="mx_LeftPanel_breadcrumbsContainer mx_AutoHideScrollbar"
|
||||
verticalScrollsHorizontally={true}
|
||||
>
|
||||
|
@ -356,6 +358,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
role="search"
|
||||
>
|
||||
<RoomSearch isMinimized={this.props.isMinimized} />
|
||||
|
||||
|
@ -397,7 +400,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
selected={this.props.pageType === PageType.HomePage}
|
||||
minimized={this.props.isMinimized}
|
||||
/>
|
||||
<div className="mx_LeftPanel_roomListWrapper">
|
||||
<nav className="mx_LeftPanel_roomListWrapper" aria-label={_t("common|rooms")}>
|
||||
<div
|
||||
className={roomListClasses}
|
||||
ref={this.listContainerRef}
|
||||
|
@ -407,7 +410,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
>
|
||||
{roomList}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -683,7 +683,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
<div className={bodyClasses}>
|
||||
<div className="mx_LeftPanel_outerWrapper">
|
||||
<LeftPanelLiveShareWarning isMinimized={this.props.collapseLhs || false} />
|
||||
<nav className="mx_LeftPanel_wrapper">
|
||||
<div className="mx_LeftPanel_wrapper">
|
||||
<BackdropPanel blurMultiplier={0.5} backgroundImage={this.state.backgroundImage} />
|
||||
<SpacePanel />
|
||||
<BackdropPanel backgroundImage={this.state.backgroundImage} />
|
||||
|
@ -698,7 +698,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />
|
||||
<div className="mx_RoomView_wrapper">{pageElement}</div>
|
||||
|
|
|
@ -406,7 +406,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
private unmounted = false;
|
||||
private permalinkCreators: Record<string, RoomPermalinkCreator> = {};
|
||||
|
||||
private roomView = createRef<HTMLElement>();
|
||||
private roomView = createRef<HTMLDivElement>();
|
||||
private searchResultsPanel = createRef<ScrollPanel>();
|
||||
private messagePanel: TimelinePanel | null = null;
|
||||
private roomViewBody = createRef<HTMLDivElement>();
|
||||
|
@ -2302,7 +2302,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// if statusBar does not exist then statusBarArea is blank and takes up unnecessary space on the screen
|
||||
// show statusBarArea only if statusBar is present
|
||||
const statusBarArea = statusBar && (
|
||||
<div className={statusBarAreaClass}>
|
||||
<div role="region" className={statusBarAreaClass} aria-label={_t("a11y|room_status_bar")}>
|
||||
<div className="mx_RoomView_statusAreaBox">
|
||||
<div className="mx_RoomView_statusAreaBox_line" />
|
||||
{statusBar}
|
||||
|
@ -2528,13 +2528,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
<Measured sensor={this.roomViewBody.current} onMeasurement={this.onMeasurement} />
|
||||
)}
|
||||
{auxPanel}
|
||||
<div className={timelineClasses}>
|
||||
<main className={timelineClasses}>
|
||||
<FileDropTarget parent={this.roomView.current} onFileDrop={this.onFileDrop} />
|
||||
{topUnreadMessagesBar}
|
||||
{jumpToBottom}
|
||||
{messagePanel}
|
||||
{searchResultsPanel}
|
||||
</div>
|
||||
</main>
|
||||
{statusBarArea}
|
||||
{previewBar}
|
||||
{messageComposer}
|
||||
|
@ -2550,6 +2550,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
userId={this.context.client.getSafeUserId()}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
showApps={true}
|
||||
role="main"
|
||||
/>
|
||||
{previewBar}
|
||||
</>
|
||||
|
@ -2563,6 +2564,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
room={this.state.room}
|
||||
resizing={this.state.resizing}
|
||||
waitForCall={isVideoRoom(this.state.room)}
|
||||
role="main"
|
||||
/>
|
||||
{previewBar}
|
||||
</>
|
||||
|
@ -2603,7 +2605,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
return (
|
||||
<RoomContext.Provider value={this.state}>
|
||||
<main
|
||||
<div
|
||||
className={mainClasses}
|
||||
ref={this.roomView}
|
||||
onKeyDown={this.onReactKeyDown}
|
||||
|
@ -2655,7 +2657,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
</div>
|
||||
</MainSplit>
|
||||
</ErrorBoundary>
|
||||
</main>
|
||||
</div>
|
||||
</RoomContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -180,11 +180,11 @@ const EmptyThread: React.FC<EmptyThreadIProps> = ({ hasThreads, filterOption, sh
|
|||
}
|
||||
|
||||
return (
|
||||
<aside className="mx_ThreadPanel_empty">
|
||||
<div className="mx_ThreadPanel_empty">
|
||||
<div className="mx_ThreadPanel_largeIcon" />
|
||||
<h2>{_t("threads|empty_heading")}</h2>
|
||||
{body}
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -362,7 +362,12 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
|||
{({ onKeyDownHandler }) => {
|
||||
let heightBefore = 0;
|
||||
return (
|
||||
<div className="mx_EmojiPicker" data-testid="mx_EmojiPicker" onKeyDown={onKeyDownHandler}>
|
||||
<section
|
||||
className="mx_EmojiPicker"
|
||||
data-testid="mx_EmojiPicker"
|
||||
onKeyDown={onKeyDownHandler}
|
||||
aria-label={_t("a11y|emoji_picker")}
|
||||
>
|
||||
<Header categories={this.categories} onAnchorClick={this.scrollToCategory} />
|
||||
<Search
|
||||
query={this.state.filter}
|
||||
|
@ -407,7 +412,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
|||
selectedEmojis={this.props.selectedEmojis}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}}
|
||||
</RovingTabIndexProvider>
|
||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { AriaRole } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Resizable, Size } from "re-resizable";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
@ -42,6 +42,7 @@ interface IProps {
|
|||
resizeNotifier: ResizeNotifier;
|
||||
showApps?: boolean; // Should apps be rendered
|
||||
maxHeight: number;
|
||||
role?: AriaRole;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -294,7 +295,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div role={this.props.role} className={classes}>
|
||||
{drawer}
|
||||
{spinner}
|
||||
</div>
|
||||
|
|
|
@ -65,7 +65,7 @@ export default class AuxPanel extends React.Component<IProps> {
|
|||
}
|
||||
|
||||
return (
|
||||
<AutoHideScrollbar className="mx_AuxPanel">
|
||||
<AutoHideScrollbar role="region" className="mx_AuxPanel">
|
||||
{this.props.children}
|
||||
{appsDrawer}
|
||||
{callView}
|
||||
|
|
|
@ -602,6 +602,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||
className={classes}
|
||||
ref={this.ref}
|
||||
aria-describedby={this.state.recordingTimeLeftSeconds ? this.tooltipId : undefined}
|
||||
role="region"
|
||||
aria-label={_t("a11y|message_composer")}
|
||||
>
|
||||
{recordingTooltip}
|
||||
<div className="mx_MessageComposer_wrapper">
|
||||
|
|
|
@ -182,7 +182,7 @@ export default function RoomHeader({
|
|||
)}
|
||||
</Box>
|
||||
</button>
|
||||
<Flex as="nav" align="center" gap="var(--cpd-space-2x)">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
{additionalButtons?.map((props) => {
|
||||
const label = props.label();
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomListHeader">
|
||||
<aside className="mx_RoomListHeader" aria-label={_t("room|context_menu|title")}>
|
||||
{contextMenuButton}
|
||||
{pendingActionSummary ? (
|
||||
<TooltipTarget label={pendingActionSummary}>
|
||||
|
@ -427,7 +427,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
|||
)}
|
||||
|
||||
{contextMenu}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -720,7 +720,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div role="complementary" className={classes}>
|
||||
<div className="mx_RoomPreviewBar_message">
|
||||
{titleElement}
|
||||
{subTitleElements}
|
||||
|
|
|
@ -364,10 +364,11 @@ const SpacePanel: React.FC = () => {
|
|||
onDragEndHandler();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<nav
|
||||
className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })}
|
||||
onKeyDown={onKeyDownHandler}
|
||||
ref={ref}
|
||||
aria-label={_t("common|spaces")}
|
||||
>
|
||||
<UserMenu isPanelCollapsed={isPanelCollapsed}>
|
||||
<AccessibleTooltipButton
|
||||
|
@ -406,7 +407,7 @@ const SpacePanel: React.FC = () => {
|
|||
</Droppable>
|
||||
|
||||
<QuickSettingsButton isPanelCollapsed={isPanelCollapsed} />
|
||||
</div>
|
||||
</nav>
|
||||
</DragDropContext>
|
||||
)}
|
||||
</RovingTabIndexProvider>
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactNode, useState, useContext, useEffect, useMemo, useRef, useCallback } from "react";
|
||||
import React, { FC, ReactNode, useState, useContext, useEffect, useMemo, useRef, useCallback, AriaRole } from "react";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
|
@ -297,9 +297,10 @@ interface StartCallViewProps {
|
|||
resizing: boolean;
|
||||
call: Call | null;
|
||||
setStartingCall: (value: boolean) => void;
|
||||
role?: AriaRole;
|
||||
}
|
||||
|
||||
const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStartingCall }) => {
|
||||
const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStartingCall, role }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
// Since connection has to be split across two different callbacks, we
|
||||
|
@ -348,7 +349,7 @@ const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStarti
|
|||
}, [call, connectDeferred]);
|
||||
|
||||
return (
|
||||
<div className="mx_CallView">
|
||||
<div className="mx_CallView" role={role}>
|
||||
{connected ? null : <Lobby room={room} connect={connect} />}
|
||||
{call !== null && (
|
||||
<AppTile
|
||||
|
@ -369,9 +370,10 @@ interface JoinCallViewProps {
|
|||
room: Room;
|
||||
resizing: boolean;
|
||||
call: Call;
|
||||
role?: AriaRole;
|
||||
}
|
||||
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call }) => {
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, role }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const connected = isConnected(useConnectionState(call));
|
||||
const members = useParticipatingMembers(call);
|
||||
|
@ -415,7 +417,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="mx_CallView">
|
||||
<div className="mx_CallView" role={role}>
|
||||
{lobby}
|
||||
{/* We render the widget even if we're disconnected, so it stays loaded */}
|
||||
<AppTile
|
||||
|
@ -439,16 +441,19 @@ interface CallViewProps {
|
|||
* button will create a call if there isn't already one.
|
||||
*/
|
||||
waitForCall: boolean;
|
||||
role?: AriaRole;
|
||||
}
|
||||
|
||||
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall }) => {
|
||||
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, role }) => {
|
||||
const call = useCall(room.roomId);
|
||||
const [startingCall, setStartingCall] = useState(false);
|
||||
|
||||
if (call === null || startingCall) {
|
||||
if (waitForCall) return null;
|
||||
return <StartCallView room={room} resizing={resizing} call={call} setStartingCall={setStartingCall} />;
|
||||
return (
|
||||
<StartCallView room={room} resizing={resizing} call={call} setStartingCall={setStartingCall} role={role} />
|
||||
);
|
||||
} else {
|
||||
return <JoinCallView room={room} resizing={resizing} call={call} />;
|
||||
return <JoinCallView room={room} resizing={resizing} call={call} role={role} />;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"a11y": {
|
||||
"emoji_picker": "Emoji picker",
|
||||
"jump_first_invite": "Jump to first invite.",
|
||||
"message_composer": "Message composer",
|
||||
"n_unread_messages": {
|
||||
"one": "1 unread message.",
|
||||
"other": "%(count)s unread messages."
|
||||
|
@ -9,7 +11,9 @@
|
|||
"one": "1 unread mention.",
|
||||
"other": "%(count)s unread messages including mentions."
|
||||
},
|
||||
"recent_rooms": "Recent rooms",
|
||||
"room_name": "Room %(name)s",
|
||||
"room_status_bar": "Room status bar",
|
||||
"unread_messages": "Unread messages.",
|
||||
"user_menu": "User menu"
|
||||
},
|
||||
|
|
|
@ -164,7 +164,7 @@ export default class HTMLExporter extends Exporter {
|
|||
<title>${_t("export_chat|html_title")}</title>
|
||||
</head>
|
||||
<body style="height: 100vh;">
|
||||
<section
|
||||
<div
|
||||
id="matrixchat"
|
||||
style="height: 100%; overflow: auto"
|
||||
class="notranslate"
|
||||
|
@ -237,7 +237,7 @@ export default class HTMLExporter extends Exporter {
|
|||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="snackbar"/>
|
||||
</body>
|
||||
</html>`;
|
||||
|
|
|
@ -373,7 +373,9 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Message composer"
|
||||
class="mx_MessageComposer"
|
||||
role="region"
|
||||
>
|
||||
<div
|
||||
class="mx_MessageComposer_wrapper"
|
||||
|
@ -614,7 +616,9 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Message composer"
|
||||
class="mx_MessageComposer"
|
||||
role="region"
|
||||
>
|
||||
<div
|
||||
class="mx_MessageComposer_wrapper"
|
||||
|
@ -740,6 +744,7 @@ exports[`RoomView should show error view if failed to look up room alias 1`] = `
|
|||
>
|
||||
<div
|
||||
class="mx_RoomPreviewBar mx_RoomPreviewBar_RoomNotFound mx_RoomPreviewBar_dialog"
|
||||
role="complementary"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomPreviewBar_message"
|
||||
|
|
|
@ -39,7 +39,7 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<nav
|
||||
<div
|
||||
class="mx_Flex"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
|
||||
>
|
||||
|
@ -75,7 +75,7 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
|
|||
>
|
||||
<div />
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -13,7 +13,7 @@ exports[`HTMLExport should export 1`] = `
|
|||
<title>Exported Data</title>
|
||||
</head>
|
||||
<body style="height: 100vh;">
|
||||
<section
|
||||
<div
|
||||
id="matrixchat"
|
||||
style="height: 100%; overflow: auto"
|
||||
class="notranslate"
|
||||
|
@ -82,7 +82,7 @@ exports[`HTMLExport should export 1`] = `
|
|||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="snackbar"/>
|
||||
</body>
|
||||
</html>"
|
||||
|
|