feat(a11y): message semantics for screen readers (P3-4)
- Each message is role="article"; collapsed messages (consecutive from one sender) now carry an aria-label with sender + time — previously a screen reader heard only the body with no attribution (the biggest a11y gap). Pure messageAriaLabel() reuses the existing time utils (+3 tests). - Editing a message announces "Editing message from <sender>" (ariaLabel threaded MessageEditor → CustomEditor; the main composer is unaffected). - System emoji get role="img" + aria-label from the shortcode; custom emoticons always have an accessible name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -229,13 +229,21 @@ export const scaleSystemEmoji = (text: string): (string | JSX.Element)[] =>
|
||||
findAndReplace(
|
||||
text,
|
||||
EMOJI_REG_G,
|
||||
(match, pushIndex) => (
|
||||
<span key={`scaleSystemEmoji-${pushIndex}`} className={css.EmoticonBase}>
|
||||
<span className={css.Emoticon()} title={getShortcodeFor(getHexcodeForEmoji(match[0]))}>
|
||||
{match[0]}
|
||||
(match, pushIndex) => {
|
||||
const shortcode = getShortcodeFor(getHexcodeForEmoji(match[0]));
|
||||
return (
|
||||
<span key={`scaleSystemEmoji-${pushIndex}`} className={css.EmoticonBase}>
|
||||
<span
|
||||
className={css.Emoticon()}
|
||||
title={shortcode}
|
||||
aria-label={shortcode || undefined}
|
||||
role={shortcode ? 'img' : undefined}
|
||||
>
|
||||
{match[0]}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
);
|
||||
},
|
||||
(txt) => txt,
|
||||
);
|
||||
|
||||
@@ -574,10 +582,20 @@ export const getReactCustomHtmlParser = (
|
||||
);
|
||||
}
|
||||
if (htmlSrc && 'data-mx-emoticon' in props) {
|
||||
const emoticonAlt =
|
||||
(typeof props.alt === 'string' && props.alt) ||
|
||||
(typeof props.title === 'string' && props.title) ||
|
||||
'emoji';
|
||||
return (
|
||||
<span className={css.EmoticonBase}>
|
||||
<span className={css.Emoticon()}>
|
||||
<img {...props} className={css.EmoticonImg} src={htmlSrc} loading="lazy" />
|
||||
<img
|
||||
{...props}
|
||||
alt={emoticonAlt}
|
||||
className={css.EmoticonImg}
|
||||
src={htmlSrc}
|
||||
loading="lazy"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
@@ -611,7 +629,6 @@ export const getReactCustomHtmlParser = (
|
||||
<>
|
||||
{segments.map((segment, index) => {
|
||||
if (segment.type === 'text') {
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
return (
|
||||
<React.Fragment key={index}>{renderTextChunk(segment.value)}</React.Fragment>
|
||||
);
|
||||
@@ -619,7 +636,6 @@ export const getReactCustomHtmlParser = (
|
||||
const raw =
|
||||
segment.type === 'block' ? `$$${segment.value}$$` : `$${segment.value}$`;
|
||||
return (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<React.Fragment key={index}>
|
||||
{renderMath(segment.value, segment.type === 'block', raw, raw)}
|
||||
</React.Fragment>
|
||||
|
||||
Reference in New Issue
Block a user