Fix div-in-p issues

This commit is contained in:
Michael Telatynski 2026-03-27 13:08:22 +00:00
parent e5e4507411
commit a4998e3fe6
No known key found for this signature in database
GPG Key ID: A2B008A5F49F5D0D
11 changed files with 77 additions and 50 deletions

View File

@ -9,6 +9,11 @@ Please see LICENSE files in the repository root for full details.
.mx_AccessibleButton {
cursor: pointer;
button& {
/* Clear default button styling */
all: unset;
}
&.mx_AccessibleButton_disabled {
cursor: not-allowed;

View File

@ -28,7 +28,6 @@ Please see LICENSE files in the repository root for full details.
/* using em here to adapt to the local font size */
width: 1em;
height: 1em;
cursor: pointer;
padding-left: 12px;
padding-right: 10px;
display: block;

View File

@ -459,8 +459,8 @@ export class EmailIdentityAuthEntry extends React.Component<
{
a: (text: string) => (
<Fragment>
<AccessibleButton kind="link_inline" onClick={null} disabled>
{text} <Spinner size={14} />
<AccessibleButton element="a" kind="link_inline" onClick={null} disabled>
{text} <Spinner as="span" size={14} />
</AccessibleButton>
</Fragment>
),
@ -475,6 +475,7 @@ export class EmailIdentityAuthEntry extends React.Component<
{
a: (text: string) => (
<AccessibleButton
element="a"
kind="link_inline"
title={
this.state.requested ? _t("auth|uia|email_resent") : _t("action|resend")

View File

@ -152,46 +152,49 @@ const AccessibleButton = function AccessibleButton<T extends ElementType = typeo
} else {
newProps.onClick = onClick ?? undefined;
}
// We need to consume enter onKeyDown and space onKeyUp
// otherwise we are risking also activating other keyboard focusable elements
// that might receive focus as a result of the AccessibleButtonClick action
// It's because we are using html buttons at a few places e.g. inside dialogs
// And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
break;
default:
onKeyDown?.(e);
}
};
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
if (element !== "button") {
// We need to consume enter onKeyDown and space onKeyUp
// otherwise we are risking also activating other keyboard focusable elements
// that might receive focus as a result of the AccessibleButtonClick action
// It's because we are using html buttons at a few places e.g. inside dialogs
// And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
break;
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
default:
onKeyUp?.(e);
break;
}
};
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
break;
default:
onKeyDown?.(e);
}
};
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
break;
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
default:
onKeyUp?.(e);
break;
}
};
}
}
// Pass through the ref - used for keyboard shortcut access to some buttons

View File

@ -44,6 +44,7 @@ export const CopyTextButton: React.FC<Pick<IProps, "getTextToCopy" | "className"
return (
<AccessibleButton
element="button"
title={tooltip ?? _t("action|copy")}
onClick={onCopyClickInternal}
className={className}
@ -62,12 +63,12 @@ const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true
});
return (
<div className={combinedClassName} {...props}>
<span className={combinedClassName} {...props}>
{children}
<CopyTextButton getTextToCopy={getTextToCopy} className="mx_CopyableText_copyButton">
<CopyIcon />
</CopyTextButton>
</div>
</span>
);
};

View File

@ -29,7 +29,13 @@ const LearnMore: React.FC<Props> = ({ title, description, ...rest }) => {
};
return (
<AccessibleButton {...rest} kind="link_inline" onClick={onClick} className="mx_LearnMore_button">
<AccessibleButton
{...rest}
element="button"
kind="link_inline"
onClick={onClick}
className="mx_LearnMore_button"
>
{_t("action|learn_more")}
</AccessibleButton>
);

View File

@ -15,6 +15,11 @@ interface IProps {
size?: number;
message?: string;
onFinished: any; // XXX: Spinner pretends to be a dialog so it must accept an onFinished, but it never calls it
/**
* Whether to render the content in a div or span.
* @default "div"
*/
as?: "span" | "div";
}
export default class Spinner extends React.PureComponent<IProps> {
@ -23,16 +28,16 @@ export default class Spinner extends React.PureComponent<IProps> {
};
public render(): React.ReactNode {
const { size, message } = this.props;
const { size, message, as: Component = "div" } = this.props;
return (
<div className="mx_Spinner">
<Component className="mx_Spinner">
{message && (
<React.Fragment>
<div className="mx_Spinner_Msg">{message}</div>&nbsp;
</React.Fragment>
)}
<InlineSpinner size={size} aria-label={_t("common|loading")} role="progressbar" data-testid="spinner" />
</div>
</Component>
);
}
}

View File

@ -255,7 +255,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
} else {
body = (
<p>
<Spinner />
<Spinner as="span" />
</p>
);
}

View File

@ -91,7 +91,7 @@ function Description({ isEncrypted }: DescriptionProps): JSX.Element {
} else {
const button = {
a: (sub: string) => (
<AccessibleButton kind="link_inline" onClick={onClickUserSettings}>
<AccessibleButton element="a" kind="link_inline" onClick={onClickUserSettings}>
{sub}
</AccessibleButton>
),

View File

@ -220,7 +220,12 @@ export default class EventIndexPanel extends React.Component<EmptyObject, IState
: _t("error|unknown")}
</code>
<p>
<AccessibleButton key="delete" kind="danger" onClick={this.confirmEventStoreReset}>
<AccessibleButton
element="button"
key="delete"
kind="danger"
onClick={this.confirmEventStoreReset}
>
{_t("action|reset")}
</AccessibleButton>
</p>

View File

@ -137,6 +137,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{
a: (sub) => (
<AccessibleButton
element="a"
kind="link_inline"
onClick={() => {
dialog.close();
@ -334,6 +335,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{_t("room_settings|security|encrypted_room_public_confirm_description_2", undefined, {
a: (sub) => (
<AccessibleButton
element="a"
kind="link_inline"
onClick={(): void => {
dialog.close();