Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Make the timeline less noisy for screen readers #3007

Merged
merged 3 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/views/elements/AccessibleButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export default function AccessibleButton(props) {
restProps.ref = restProps.inputRef;
delete restProps.inputRef;

restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
restProps.tabIndex = restProps.tabIndex === undefined ? "0" : restProps.tabIndex;
restProps.role = restProps.role === undefined ? "button" : restProps.role;
restProps.className = (restProps.className ? restProps.className + " " : "") +
"mx_AccessibleButton";

Expand Down
8 changes: 7 additions & 1 deletion src/components/views/elements/Flair.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,18 @@ class FlairAvatar extends React.Component {
const tooltip = this.props.groupProfile.name ?
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
this.props.groupProfile.groupId;

// Note: we hide flair from screen readers but ideally we'd support
// reading something out on hover. There's no easy way to do this though,
// so instead we just hide it completely.
return <img
src={httpUrl}
width="16"
height="16"
onClick={this.onClick}
title={tooltip} />;
title={tooltip}
aria-hidden={true}
/>;
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/components/views/messages/MessageTimestamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ export default class MessageTimestamp extends React.Component {
static propTypes = {
ts: PropTypes.number.isRequired,
showTwelveHour: PropTypes.bool,
ariaHidden: PropTypes.bool,
};

render() {
const date = new Date(this.props.ts);
return (
<span className="mx_MessageTimestamp" title={formatFullDate(date, this.props.showTwelveHour)}>
<span
className="mx_MessageTimestamp"
title={formatFullDate(date, this.props.showTwelveHour)}
aria-hidden={this.props.ariaHidden}
>
{ formatTime(date, this.props.showTwelveHour) }
</span>
);
Expand Down
22 changes: 16 additions & 6 deletions src/components/views/rooms/EventTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,8 @@ module.exports = withMatrixClient(React.createClass({
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();

const muteScreenReader = isSending || !this.props.eventSendStatus;

const classes = classNames({
mx_EventTile: true,
mx_EventTile_isEditing: this.props.isEditing,
Expand Down Expand Up @@ -597,9 +599,13 @@ module.exports = withMatrixClient(React.createClass({
if (this.props.mxEvent.sender && avatarSize) {
avatar = (
<div className="mx_EventTile_avatar">
<MemberAvatar member={this.props.mxEvent.sender}
<MemberAvatar
member={this.props.mxEvent.sender}
width={avatarSize} height={avatarSize}
viewUserOnClick={true}
aria-hidden={true} /* silence screen readers */
buttonRole={null} /* trick screen readers into thinking this is not a button */
tabIndex={null} /* trick screen readers into thinking this is not a button */
/>
</div>
);
Expand Down Expand Up @@ -630,8 +636,12 @@ module.exports = withMatrixClient(React.createClass({
onFocusChange={this.onActionBarFocusChange}
/> : undefined;

const timestamp = this.props.mxEvent.getTs() ?
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
const timestamp = this.props.mxEvent.getTs()
? <MessageTimestamp
showTwelveHour={this.props.isTwelveHour}
ts={this.props.mxEvent.getTs()}
ariaHidden={muteScreenReader}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, if it's muted when sending or sending is complete, when will it be read...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The life of an EventTile is 3 parts, where the third is when things are read out. The first stage is the sending state, which is easy to filter. When the event switches to sent, the tile unmounts and a new one is put in place with a null sending state. That state is replaced shortly after with a property update, which triggers a re-render where the screen reader picks up the text to read (the div is replaced).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my own understanding, what does "that state is replaced shortly after with a property update" mean? Which property updates...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mxEvent changes in the component's properties, which leads to an eventSendState change of null->sent

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I thought sent was first (while waiting for remote echo), then null was the last and final status...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently it's just not my day - you're right. Will open another PR to flip it around.

/> : null;

const keyRequestHelpText =
<div className="mx_EventTile_keyRequestInfo_tooltip_contents">
Expand Down Expand Up @@ -769,13 +779,13 @@ module.exports = withMatrixClient(React.createClass({
'replyThread',
);
return (
<div className={classes}>
<div className={classes} aria-hidden={muteScreenReader}>
<div className="mx_EventTile_msgOption">
{ readAvatars }
</div>
{ sender }
<div className="mx_EventTile_line">
<a href={permalink} onClick={this.onPermalinkClicked}>
<a href={permalink} onClick={this.onPermalinkClicked} aria-hidden={muteScreenReader}>
{ timestamp }
</a>
{ this._renderE2EPadlock() }
Expand All @@ -793,7 +803,7 @@ module.exports = withMatrixClient(React.createClass({
{ actionBar }
</div>
{
// The avatar goes after the event tile as it's absolutly positioned to be over the
// The avatar goes after the event tile as it's absolutely positioned to be over the
// event tile line, so needs to be later in the DOM so it appears on top (this avoids
// the need for further z-indexing chaos)
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/views/rooms/ReadReceiptMarker.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,13 @@ module.exports = React.createClass({
<MemberAvatar
member={this.props.member}
fallbackUserId={this.props.fallbackUserId}
aria-hidden="true"
width={14} height={14} resizeMethod="crop"
style={style}
title={title}
onClick={this.props.onClick}
aria-hidden={true} /* silence screen readers */
buttonRole={null} /* trick screen readers into thinking this is not a button */
tabIndex={null} /* trick screen readers into thinking this is not a button */
/>
</Velociraptor>
);
Expand Down