diff --git a/CHANGELOG.md b/CHANGELOG.md index 3304cea8fe..3bf922394b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Legends: ### Fixed +- Fixed screen reader primary-action activation to move focus into interactive transcript activities, in PR [#5867](https://github.com/microsoft/BotFramework-WebChat/pull/5867), by [@ahonamatata](https://github.com/ahonamatata) - Fixed an error when a failed activity is present when Web Chat mounts, resolving [#5812](https://github.com/microsoft/BotFramework-WebChat/issues/5812), in PR [#5848](https://github.com/microsoft/BotFramework-WebChat/pull/5848), by [@OEvgeny](https://github.com/OEvgeny) ## [4.19.1] - 2026-06-09 diff --git a/__tests__/html2/part-grouping/primaryAction.html b/__tests__/html2/part-grouping/primaryAction.html new file mode 100644 index 0000000000..b039ffa8a7 --- /dev/null +++ b/__tests__/html2/part-grouping/primaryAction.html @@ -0,0 +1,97 @@ + + + + Part grouping: screen reader primary action + + + + + + +
+ + + diff --git a/__tests__/html2/transcript/navigation/focusAttachment.screenReaderPrimaryAction.html b/__tests__/html2/transcript/navigation/focusAttachment.screenReaderPrimaryAction.html new file mode 100644 index 0000000000..a11cd7542d --- /dev/null +++ b/__tests__/html2/transcript/navigation/focusAttachment.screenReaderPrimaryAction.html @@ -0,0 +1,90 @@ + + + + + + + + + +
+ + + diff --git a/packages/component/src/Middleware/ActivityGrouping/ui/PartGrouping/private/PartGroupingActivity.tsx b/packages/component/src/Middleware/ActivityGrouping/ui/PartGrouping/private/PartGroupingActivity.tsx index 4b221a11c4..cae5539991 100644 --- a/packages/component/src/Middleware/ActivityGrouping/ui/PartGrouping/private/PartGroupingActivity.tsx +++ b/packages/component/src/Middleware/ActivityGrouping/ui/PartGrouping/private/PartGroupingActivity.tsx @@ -2,7 +2,7 @@ import { reactNode, validateProps } from '@msinternal/botframework-webchat-react import { useStyles } from '@msinternal/botframework-webchat-styles/react'; import { hooks } from 'botframework-webchat-api'; import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core'; -import React, { Fragment, memo, useCallback, useMemo, useState, type MouseEventHandler } from 'react'; +import React, { Fragment, memo, useCallback, useMemo, useRef, useState, type MouseEventHandler } from 'react'; import cx from 'classnames'; import { array, custom, is, minLength, object, optional, pipe, readonly, string, type InferOutput } from 'valibot'; @@ -67,6 +67,7 @@ const FocusablePartGroupingActivity = memo(function FocusablePartGroupingActivit const { activity, children, className, groupKey } = validateProps(partGroupingFocusableActivityPropsSchema, props); const [activeDescendantId] = useActiveDescendantId(); + const focusTrapTargetRef = useRef(); const getGroupDescendantIdByActivityKey = useGetGroupDescendantIdByActivityKey(); const focusByGroupKey = useFocusByGroupKey(); @@ -85,6 +86,9 @@ const FocusablePartGroupingActivity = memo(function FocusablePartGroupingActivit // When receive Escape key from descendant, focus back to the group. const handleLeaveFocusTrap = useCallback(() => focusSelf(), [focusSelf]); + // Screen readers dispatch a click to the active descendant when users invoke its primary action. + const handleScreenReaderPrimaryAction = useCallback(() => focusTrapTargetRef.current?.focus(), [focusTrapTargetRef]); + // When the user press UP/DOWN arrow keys, we put a visual focus indicator around the focused group. // We should do the same for mouse, when the user click on the activity, we should also put a visual focus indicator around the activity. // We are doing it in event capture phase to prevent descendants from stopping event propagation to us. @@ -115,11 +119,17 @@ const FocusablePartGroupingActivity = memo(function FocusablePartGroupingActivit onFocus={handleDescendantFocus} onLeave={handleLeaveFocusTrap} targetClassName="webchat__basic-transcript__group-focus-target" + targetRef={focusTrapTargetRef} > {children} - {!android && } + {!android && ( + + )} diff --git a/packages/component/src/Transcript/ActivityRow.tsx b/packages/component/src/Transcript/ActivityRow.tsx index 697f6d3e5f..04ebb5585c 100644 --- a/packages/component/src/Transcript/ActivityRow.tsx +++ b/packages/component/src/Transcript/ActivityRow.tsx @@ -32,6 +32,7 @@ const ActivityRow = forwardRef(({ activity, child const [readActivityKeys] = useActivityKeysByRead(); const activityRef = useRefFrom(activity); const bodyRef = useRef(); + const focusTrapTargetRef = useRef(); const focusByActivityKey = useFocusByActivityKey(); const getKeyByActivity = useGetKeyByActivity(); // TODO: [P2] #2858 We should use core/definitions/speakingActivity for this predicate instead @@ -59,6 +60,9 @@ const ActivityRow = forwardRef(({ activity, child // When receive Escape key from descendant, focus back to the activity. const handleLeaveFocusTrap = useCallback(() => focusSelf(), [focusSelf]); + // Screen readers dispatch a click to the active descendant when users invoke its primary action. + const handleScreenReaderPrimaryAction = useCallback(() => focusTrapTargetRef.current?.focus(), [focusTrapTargetRef]); + // When the user press UP/DOWN arrow keys, we put a visual focus indicator around the focused activity. // We should do the same for mouse, when the user click on the activity, we should also put a visual focus indicator around the activity. // We are doing it in event capture phase to prevent descendants from stopping event propagation to us. @@ -146,6 +150,7 @@ const ActivityRow = forwardRef(({ activity, child onFocus={handleDescendantFocus} onLeave={handleLeaveFocusTrap} targetClassName="webchat__basic-transcript__activity-focus-target" + targetRef={focusTrapTargetRef} > {focusTrapChildren} @@ -160,6 +165,7 @@ const ActivityRow = forwardRef(({ activity, child className="webchat__basic-transcript__activity-active-descendant" // "id" is required for "aria-labelledby" id={descendantId} + onClick={handleScreenReaderPrimaryAction} role="article" > diff --git a/packages/component/src/Transcript/FocusTrap.tsx b/packages/component/src/Transcript/FocusTrap.tsx index 73b7ac7688..1d29e81ef7 100644 --- a/packages/component/src/Transcript/FocusTrap.tsx +++ b/packages/component/src/Transcript/FocusTrap.tsx @@ -5,6 +5,7 @@ import React, { type FocusEvent, type KeyboardEventHandler, type ReactNode, + type Ref, useCallback, useRef } from 'react'; @@ -16,12 +17,14 @@ const FocusTrap = ({ children, onFocus, onLeave, - targetClassName + targetClassName, + targetRef }: Readonly<{ children: ReactNode; onFocus: () => void; onLeave: () => void; targetClassName?: string | undefined; + targetRef?: Ref | undefined; }>) => { const bodyRef = useRef(); const lastFocused = useRef(); @@ -122,7 +125,7 @@ const FocusTrap = ({ > {children} -