Provide a way to activate GIFs via the keyboard for a11y (#28611)
* Provide a way to activate GIFs via the keyboard for a11y Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove dead code Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>pull/28585/merge
parent
84709df3c9
commit
2c3e01a31c
|
@ -51,6 +51,7 @@ interface IState {
|
||||||
naturalHeight: number;
|
naturalHeight: number;
|
||||||
};
|
};
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
|
focus: boolean;
|
||||||
showImage: boolean;
|
showImage: boolean;
|
||||||
placeholder: Placeholder;
|
placeholder: Placeholder;
|
||||||
}
|
}
|
||||||
|
@ -71,6 +72,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
imgError: false,
|
imgError: false,
|
||||||
imgLoaded: false,
|
imgLoaded: false,
|
||||||
hover: false,
|
hover: false,
|
||||||
|
focus: false,
|
||||||
showImage: SettingsStore.getValue("showImages"),
|
showImage: SettingsStore.getValue("showImages"),
|
||||||
placeholder: Placeholder.NoImage,
|
placeholder: Placeholder.NoImage,
|
||||||
};
|
};
|
||||||
|
@ -120,30 +122,29 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
protected onImageEnter = (e: React.MouseEvent<HTMLImageElement>): void => {
|
private get shouldAutoplay(): boolean {
|
||||||
this.setState({ hover: true });
|
return !(
|
||||||
|
|
||||||
if (
|
|
||||||
!this.state.contentUrl ||
|
!this.state.contentUrl ||
|
||||||
!this.state.showImage ||
|
!this.state.showImage ||
|
||||||
!this.state.isAnimated ||
|
!this.state.isAnimated ||
|
||||||
SettingsStore.getValue("autoplayGifs")
|
SettingsStore.getValue("autoplayGifs")
|
||||||
) {
|
);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
const imgElement = e.currentTarget;
|
protected onImageEnter = (): void => {
|
||||||
imgElement.src = this.state.contentUrl;
|
this.setState({ hover: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
protected onImageLeave = (e: React.MouseEvent<HTMLImageElement>): void => {
|
protected onImageLeave = (): void => {
|
||||||
this.setState({ hover: false });
|
this.setState({ hover: false });
|
||||||
|
};
|
||||||
|
|
||||||
const url = this.state.thumbUrl ?? this.state.contentUrl;
|
private onFocus = (): void => {
|
||||||
if (!url || !this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) {
|
this.setState({ focus: true });
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
const imgElement = e.currentTarget;
|
private onBlur = (): void => {
|
||||||
imgElement.src = url;
|
this.setState({ focus: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
private reconnectedListener = createReconnectedListener((): void => {
|
private reconnectedListener = createReconnectedListener((): void => {
|
||||||
|
@ -470,14 +471,20 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
|
|
||||||
let showPlaceholder = Boolean(placeholder);
|
let showPlaceholder = Boolean(placeholder);
|
||||||
|
|
||||||
|
const hoverOrFocus = this.state.hover || this.state.focus;
|
||||||
if (thumbUrl && !this.state.imgError) {
|
if (thumbUrl && !this.state.imgError) {
|
||||||
|
let url = thumbUrl;
|
||||||
|
if (hoverOrFocus && this.shouldAutoplay) {
|
||||||
|
url = this.state.contentUrl!;
|
||||||
|
}
|
||||||
|
|
||||||
// Restrict the width of the thumbnail here, otherwise it will fill the container
|
// Restrict the width of the thumbnail here, otherwise it will fill the container
|
||||||
// which has the same width as the timeline
|
// which has the same width as the timeline
|
||||||
// mx_MImageBody_thumbnail resizes img to exactly container size
|
// mx_MImageBody_thumbnail resizes img to exactly container size
|
||||||
img = (
|
img = (
|
||||||
<img
|
<img
|
||||||
className="mx_MImageBody_thumbnail"
|
className="mx_MImageBody_thumbnail"
|
||||||
src={thumbUrl}
|
src={url}
|
||||||
ref={this.image}
|
ref={this.image}
|
||||||
alt={content.body}
|
alt={content.body}
|
||||||
onError={this.onImageError}
|
onError={this.onImageError}
|
||||||
|
@ -493,13 +500,13 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
showPlaceholder = false; // because we're hiding the image, so don't show the placeholder.
|
showPlaceholder = false; // because we're hiding the image, so don't show the placeholder.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.isAnimated && !SettingsStore.getValue("autoplayGifs") && !this.state.hover) {
|
if (this.state.isAnimated && !SettingsStore.getValue("autoplayGifs") && !hoverOrFocus) {
|
||||||
// XXX: Arguably we may want a different label when the animated image is WEBP and not GIF
|
// XXX: Arguably we may want a different label when the animated image is WEBP and not GIF
|
||||||
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let banner: ReactNode | undefined;
|
let banner: ReactNode | undefined;
|
||||||
if (this.state.showImage && this.state.hover) {
|
if (this.state.showImage && hoverOrFocus) {
|
||||||
banner = this.getBanner(content);
|
banner = this.getBanner(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +575,13 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
protected wrapImage(contentUrl: string | null | undefined, children: JSX.Element): ReactNode {
|
protected wrapImage(contentUrl: string | null | undefined, children: JSX.Element): ReactNode {
|
||||||
if (contentUrl) {
|
if (contentUrl) {
|
||||||
return (
|
return (
|
||||||
<a href={contentUrl} target={this.props.forExport ? "_blank" : undefined} onClick={this.onClick}>
|
<a
|
||||||
|
href={contentUrl}
|
||||||
|
target={this.props.forExport ? "_blank" : undefined}
|
||||||
|
onClick={this.onClick}
|
||||||
|
onFocus={this.onFocus}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -657,17 +670,14 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaceholderIProps {
|
interface PlaceholderIProps {
|
||||||
hover?: boolean;
|
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProps> {
|
export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProps> {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const maxWidth = this.props.maxWidth ? this.props.maxWidth + "px" : null;
|
const maxWidth = this.props.maxWidth ? this.props.maxWidth + "px" : null;
|
||||||
let className = "mx_HiddenImagePlaceholder";
|
|
||||||
if (this.props.hover) className += " mx_HiddenImagePlaceholder_hover";
|
|
||||||
return (
|
return (
|
||||||
<div className={className} style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
|
<div className="mx_HiddenImagePlaceholder" style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
|
||||||
<div className="mx_HiddenImagePlaceholder_button">
|
<div className="mx_HiddenImagePlaceholder_button">
|
||||||
<span className="mx_HiddenImagePlaceholder_eye" />
|
<span className="mx_HiddenImagePlaceholder_eye" />
|
||||||
<span>{_t("timeline|m.image|show_image")}</span>
|
<span>{_t("timeline|m.image|show_image")}</span>
|
||||||
|
|
Loading…
Reference in New Issue