diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e80af1d2d..534a0fd0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -359,6 +359,10 @@ Breaking changes in this release: ### Fixed +- Fixed screen reader (Narrator/NVDA) not announcing Adaptive Card content when focused in stacked layout, by [@uzirthapa](https://github.com/uzirthapa) + - Made stacked layout attachment rows keyboard-focusable with `tabIndex={0}` + - Added focus-visible styling for stacked attachment rows + - Adaptive Cards without `speak` property now derive `aria-label` from visible text content - Fixed [#5256](https://github.com/microsoft/BotFramework-WebChat/issues/5256). `styleOptions.maxMessageLength` should support any JavaScript number value including `Infinity`, by [@compulim](https://github.com/compulim), in PR [#5255](https://github.com/microsoft/BotFramework-WebChat/issues/pull/5255) - Fixes [#4965](https://github.com/microsoft/BotFramework-WebChat/issues/4965). Removed keyboard helper screen in [#5234](https://github.com/microsoft/BotFramework-WebChat/pull/5234), by [@amirmursal](https://github.com/amirmursal) and [@OEvgeny](https://github.com/OEvgeny) - Fixes [#5268](https://github.com/microsoft/BotFramework-WebChat/issues/5268). Concluded livestream is sealed and activities received afterwards are ignored, and `streamSequence` is not required in final activity, in PR [#5273](https://github.com/microsoft/BotFramework-WebChat/pull/5273), by [@compulim](https://github.com/compulim) diff --git a/__tests__/html2/accessibility/adaptiveCard/attachmentRow.focusable.html b/__tests__/html2/accessibility/adaptiveCard/attachmentRow.focusable.html new file mode 100644 index 0000000000..91a017087d --- /dev/null +++ b/__tests__/html2/accessibility/adaptiveCard/attachmentRow.focusable.html @@ -0,0 +1,87 @@ + + +
+ + + + + + + + + + diff --git a/__tests__/html2/accessibility/adaptiveCard/hack.roleMod.ariaLabelFromTextContent.html b/__tests__/html2/accessibility/adaptiveCard/hack.roleMod.ariaLabelFromTextContent.html new file mode 100644 index 0000000000..88e3abb681 --- /dev/null +++ b/__tests__/html2/accessibility/adaptiveCard/hack.roleMod.ariaLabelFromTextContent.html @@ -0,0 +1,95 @@ + + + + + + + + + + + + + diff --git a/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/useRoleModEffect.ts b/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/useRoleModEffect.ts index 2c84be189d..aba552b565 100644 --- a/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/useRoleModEffect.ts +++ b/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/useRoleModEffect.ts @@ -5,6 +5,8 @@ import useAdaptiveCardModEffect from './private/useAdaptiveCardModEffect'; import type { AdaptiveCard } from 'adaptivecards'; +const ARIA_LABEL_MAX_LENGTH = 200; + /** * Accessibility: "role" attribute must be set if "aria-label" is set. * @@ -34,8 +36,25 @@ export default function useRoleModEffect( adaptiveCard: AdaptiveCard ): readonly [(cardElement: HTMLElement) => void, () => void] { const modder = useMemo( - () => (_, cardElement: HTMLElement) => - setOrRemoveAttributeIfFalseWithUndo( + () => (_, cardElement: HTMLElement) => { + // If the card doesn't have an aria-label (i.e. no "speak" property was set), + // derive one from the card's visible text content so screen readers can announce it. + let undoAriaLabel: (() => void) | undefined; + + if (!cardElement.getAttribute('aria-label')) { + const textContent = (cardElement.textContent || '').replace(/\s+/gu, ' ').trim(); + + if (textContent) { + const label = + textContent.length > ARIA_LABEL_MAX_LENGTH + ? textContent.slice(0, ARIA_LABEL_MAX_LENGTH) + '\u2026' + : textContent; + + undoAriaLabel = setOrRemoveAttributeIfFalseWithUndo(cardElement, 'aria-label', label); + } + } + + const undoRole = setOrRemoveAttributeIfFalseWithUndo( cardElement, 'role', // "form" role requires either "aria-label", "aria-labelledby", or "title". @@ -44,7 +63,13 @@ export default function useRoleModEffect( cardElement.getAttribute('title') ? 'form' : 'figure' - ), + ); + + return () => { + undoRole(); + undoAriaLabel?.(); + }; + }, [] ); diff --git a/packages/component/src/Activity/AttachmentRow.tsx b/packages/component/src/Activity/AttachmentRow.tsx index 5200185893..f4bd85b43a 100644 --- a/packages/component/src/Activity/AttachmentRow.tsx +++ b/packages/component/src/Activity/AttachmentRow.tsx @@ -35,7 +35,12 @@ function AttachmentRow(props: AttachmentRowProps) { const classNames = useStyles(styles); return ( -