Skip to content

Commit 2c3e01a

Browse files
authoredDec 2, 2024
Provide a way to activate GIFs via the keyboard for a11y (element-hq#28611)
* Provide a way to activate GIFs via the keyboard for a11y Signed-off-by: Michael Telatynski <[email protected]> * Remove dead code Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent 84709df commit 2c3e01a

File tree

1 file changed

+34
-24
lines changed

1 file changed

+34
-24
lines changed
 

‎src/components/views/messages/MImageBody.tsx

+34-24
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ interface IState {
5151
naturalHeight: number;
5252
};
5353
hover: boolean;
54+
focus: boolean;
5455
showImage: boolean;
5556
placeholder: Placeholder;
5657
}
@@ -71,6 +72,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
7172
imgError: false,
7273
imgLoaded: false,
7374
hover: false,
75+
focus: false,
7476
showImage: SettingsStore.getValue("showImages"),
7577
placeholder: Placeholder.NoImage,
7678
};
@@ -120,30 +122,29 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
120122
}
121123
};
122124

123-
protected onImageEnter = (e: React.MouseEvent<HTMLImageElement>): void => {
124-
this.setState({ hover: true });
125-
126-
if (
125+
private get shouldAutoplay(): boolean {
126+
return !(
127127
!this.state.contentUrl ||
128128
!this.state.showImage ||
129129
!this.state.isAnimated ||
130130
SettingsStore.getValue("autoplayGifs")
131-
) {
132-
return;
133-
}
134-
const imgElement = e.currentTarget;
135-
imgElement.src = this.state.contentUrl;
131+
);
132+
}
133+
134+
protected onImageEnter = (): void => {
135+
this.setState({ hover: true });
136136
};
137137

138-
protected onImageLeave = (e: React.MouseEvent<HTMLImageElement>): void => {
138+
protected onImageLeave = (): void => {
139139
this.setState({ hover: false });
140+
};
140141

141-
const url = this.state.thumbUrl ?? this.state.contentUrl;
142-
if (!url || !this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) {
143-
return;
144-
}
145-
const imgElement = e.currentTarget;
146-
imgElement.src = url;
142+
private onFocus = (): void => {
143+
this.setState({ focus: true });
144+
};
145+
146+
private onBlur = (): void => {
147+
this.setState({ focus: false });
147148
};
148149

149150
private reconnectedListener = createReconnectedListener((): void => {
@@ -470,14 +471,20 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
470471

471472
let showPlaceholder = Boolean(placeholder);
472473

474+
const hoverOrFocus = this.state.hover || this.state.focus;
473475
if (thumbUrl && !this.state.imgError) {
476+
let url = thumbUrl;
477+
if (hoverOrFocus && this.shouldAutoplay) {
478+
url = this.state.contentUrl!;
479+
}
480+
474481
// Restrict the width of the thumbnail here, otherwise it will fill the container
475482
// which has the same width as the timeline
476483
// mx_MImageBody_thumbnail resizes img to exactly container size
477484
img = (
478485
<img
479486
className="mx_MImageBody_thumbnail"
480-
src={thumbUrl}
487+
src={url}
481488
ref={this.image}
482489
alt={content.body}
483490
onError={this.onImageError}
@@ -493,13 +500,13 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
493500
showPlaceholder = false; // because we're hiding the image, so don't show the placeholder.
494501
}
495502

496-
if (this.state.isAnimated && !SettingsStore.getValue("autoplayGifs") && !this.state.hover) {
503+
if (this.state.isAnimated && !SettingsStore.getValue("autoplayGifs") && !hoverOrFocus) {
497504
// XXX: Arguably we may want a different label when the animated image is WEBP and not GIF
498505
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
499506
}
500507

501508
let banner: ReactNode | undefined;
502-
if (this.state.showImage && this.state.hover) {
509+
if (this.state.showImage && hoverOrFocus) {
503510
banner = this.getBanner(content);
504511
}
505512

@@ -568,7 +575,13 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
568575
protected wrapImage(contentUrl: string | null | undefined, children: JSX.Element): ReactNode {
569576
if (contentUrl) {
570577
return (
571-
<a href={contentUrl} target={this.props.forExport ? "_blank" : undefined} onClick={this.onClick}>
578+
<a
579+
href={contentUrl}
580+
target={this.props.forExport ? "_blank" : undefined}
581+
onClick={this.onClick}
582+
onFocus={this.onFocus}
583+
onBlur={this.onBlur}
584+
>
572585
{children}
573586
</a>
574587
);
@@ -657,17 +670,14 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
657670
}
658671

659672
interface PlaceholderIProps {
660-
hover?: boolean;
661673
maxWidth?: number;
662674
}
663675

664676
export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProps> {
665677
public render(): React.ReactNode {
666678
const maxWidth = this.props.maxWidth ? this.props.maxWidth + "px" : null;
667-
let className = "mx_HiddenImagePlaceholder";
668-
if (this.props.hover) className += " mx_HiddenImagePlaceholder_hover";
669679
return (
670-
<div className={className} style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
680+
<div className="mx_HiddenImagePlaceholder" style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
671681
<div className="mx_HiddenImagePlaceholder_button">
672682
<span className="mx_HiddenImagePlaceholder_eye" />
673683
<span>{_t("timeline|m.image|show_image")}</span>

0 commit comments

Comments
 (0)
Please sign in to comment.