...
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fd2c880fc..1f656ee89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Fixed + +- Fixes [#4020](https://github.com/microsoft/BotFramework-WebChat/issues/4020). With or without scan mode turned on, screen reader users should be able to press ENTER to focus on interactive activity, by [@compulim](https://github.com/compulim), in PR [#4041](https://github.com/microsoft/BotFramework-WebChat/pull/4041) +- Fixes [#4021](https://github.com/microsoft/BotFramework-WebChat/issues/4021). For screen reader usability, suggested actions container should not render "Is empty" alt text initially, by [@compulim](https://github.com/compulim), in PR [#4041](https://github.com/microsoft/BotFramework-WebChat/pull/4041) + ## [4.14.1] - 2021-09-07 ### Fixed diff --git a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-adaptive-card-speak-property-js-accessibility-requirement-attachments-in-live-region-should-narrate-speak-property-adaptive-card-1-snap.png b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-adaptive-card-speak-property-js-accessibility-requirement-attachments-in-live-region-should-narrate-speak-property-adaptive-card-1-snap.png index f749788252..298b9fe9aa 100644 Binary files a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-adaptive-card-speak-property-js-accessibility-requirement-attachments-in-live-region-should-narrate-speak-property-adaptive-card-1-snap.png and b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-adaptive-card-speak-property-js-accessibility-requirement-attachments-in-live-region-should-narrate-speak-property-adaptive-card-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-file-js-accessibility-requirement-attachments-in-live-region-file-1-snap.png b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-file-js-accessibility-requirement-attachments-in-live-region-file-1-snap.png index 18abe042b0..7b51304d81 100644 Binary files a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-file-js-accessibility-requirement-attachments-in-live-region-file-1-snap.png and b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-file-js-accessibility-requirement-attachments-in-live-region-file-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-hero-card-js-accessibility-requirement-attachments-in-live-region-hero-card-1-snap.png b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-hero-card-js-accessibility-requirement-attachments-in-live-region-hero-card-1-snap.png index 392f859315..2543c7d8a8 100644 Binary files a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-hero-card-js-accessibility-requirement-attachments-in-live-region-hero-card-1-snap.png and b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-hero-card-js-accessibility-requirement-attachments-in-live-region-hero-card-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-sign-in-card-js-accessibility-requirement-attachments-in-live-region-sign-in-card-1-snap.png b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-sign-in-card-js-accessibility-requirement-attachments-in-live-region-sign-in-card-1-snap.png index db191f649f..028f8d132e 100644 Binary files a/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-sign-in-card-js-accessibility-requirement-attachments-in-live-region-sign-in-card-1-snap.png and b/__tests__/__image_snapshots__/html/accessibility-live-region-attachment-sign-in-card-js-accessibility-requirement-attachments-in-live-region-sign-in-card-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-enter-key-js-transcript-navigation-should-focus-inside-the-attachment-when-enter-key-is-pressed-1-snap.png b/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-enter-key-js-transcript-navigation-should-focus-inside-the-attachment-when-enter-key-is-pressed-1-snap.png new file mode 100644 index 0000000000..4c5bc5478a Binary files /dev/null and b/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-enter-key-js-transcript-navigation-should-focus-inside-the-attachment-when-enter-key-is-pressed-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-screen-reader-primary-action-nvda-js-transcript-navigation-with-nvda-in-browse-mode-should-focus-inside-the-attachment-when-enter-is-pressed-1-snap.png b/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-screen-reader-primary-action-nvda-js-transcript-navigation-with-nvda-in-browse-mode-should-focus-inside-the-attachment-when-enter-is-pressed-1-snap.png new file mode 100644 index 0000000000..4c5bc5478a Binary files /dev/null and b/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-screen-reader-primary-action-nvda-js-transcript-navigation-with-nvda-in-browse-mode-should-focus-inside-the-attachment-when-enter-is-pressed-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-screen-reader-primary-action-windows-narrator-js-transcript-navigation-with-windows-narrator-should-focus-inside-the-attachment-when-do-primary-action-is-performed-1-snap.png b/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-screen-reader-primary-action-windows-narrator-js-transcript-navigation-with-windows-narrator-should-focus-inside-the-attachment-when-do-primary-action-is-performed-1-snap.png new file mode 100644 index 0000000000..4c5bc5478a Binary files /dev/null and b/__tests__/__image_snapshots__/html/transcript-navigation-focus-attachment-screen-reader-primary-action-windows-narrator-js-transcript-navigation-with-windows-narrator-should-focus-inside-the-attachment-when-do-primary-action-is-performed-1-snap.png differ diff --git a/__tests__/html/accessibility.usability.suggestedActions.hideOnInitial.html b/__tests__/html/accessibility.usability.suggestedActions.hideOnInitial.html new file mode 100644 index 0000000000..50e877b983 --- /dev/null +++ b/__tests__/html/accessibility.usability.suggestedActions.hideOnInitial.html @@ -0,0 +1,101 @@ + + +
+ + + + + + + + + + diff --git a/__tests__/html/accessibility.usability.suggestedActions.hideOnInitial.js b/__tests__/html/accessibility.usability.suggestedActions.hideOnInitial.js new file mode 100644 index 0000000000..f1e12ee10c --- /dev/null +++ b/__tests__/html/accessibility.usability.suggestedActions.hideOnInitial.js @@ -0,0 +1,6 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('accessibility usability', () => { + test('should not render suggested actions container at initial', () => + runHTML('accessibility.usability.suggestedActions.hideOnInitial.html')); +}); diff --git a/__tests__/html/transcript.navigation.focusAttachment.enterKey.html b/__tests__/html/transcript.navigation.focusAttachment.enterKey.html new file mode 100644 index 0000000000..36f0433e2d --- /dev/null +++ b/__tests__/html/transcript.navigation.focusAttachment.enterKey.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + diff --git a/__tests__/html/transcript.navigation.focusAttachment.enterKey.js b/__tests__/html/transcript.navigation.focusAttachment.enterKey.js new file mode 100644 index 0000000000..5df3319c57 --- /dev/null +++ b/__tests__/html/transcript.navigation.focusAttachment.enterKey.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('transcript navigation', () => { + test('should focus inside the attachment when ENTER key is pressed', () => runHTML('transcript.navigation.focusAttachment.enterKey')); +}); diff --git a/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda.html b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda.html new file mode 100644 index 0000000000..8550f551a6 --- /dev/null +++ b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + diff --git a/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda.js b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda.js new file mode 100644 index 0000000000..f06d7c1836 --- /dev/null +++ b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('transcript navigation with NVDA in browse mode', () => { + test('should focus inside the attachment when ENTER is pressed', () => runHTML('transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda')); +}); diff --git a/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator.html b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator.html new file mode 100644 index 0000000000..9c022f594d --- /dev/null +++ b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + diff --git a/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator.js b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator.js new file mode 100644 index 0000000000..05c706e854 --- /dev/null +++ b/__tests__/html/transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('transcript navigation with Windows Narrator', () => { + test('should focus inside the attachment when "do primary action" is performed', () => runHTML('transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator')); +}); diff --git a/packages/api/package.json b/packages/api/package.json index 1f91c9d975..07b4f23325 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -30,7 +30,7 @@ "prestart": "npm run build:babel", "start": "concurrently --kill-others --names \"babel,globalize,tsc\" \"npm run start:babel\" \"npm run start:globalize\" \"npm run start:typescript\"", "start:babel": "npm run build:babel -- --skip-initial-build --watch", - "start:globalize": "node-dev --respawn scripts/createPrecompiledGlobalize.js", + "start:globalize": "node-dev --respawn scripts/createPrecompiledGlobalize.mjs", "start:typescript": "npm run build:typescript -- --watch" }, "devDependencies": { diff --git a/packages/api/src/localization/en-US.json b/packages/api/src/localization/en-US.json index eddead2d17..a6065be981 100644 --- a/packages/api/src/localization/en-US.json +++ b/packages/api/src/localization/en-US.json @@ -11,7 +11,7 @@ "ACTIVITY_BOT_ATTACHED_ALT": "Bot attached:", "_ACTIVITY_BOT_ATTACHED_ALT.comment": "This is for screen reader and is narrated before each attachments sent by the bot.", "ACTIVITY_ERROR_BOX_TITLE": "Error message", - "ACTIVITY_INTERACTIVE_LABEL_ALT": "Press ENTER to interact.", + "ACTIVITY_INTERACTIVE_LABEL_ALT": "Click to interact.", "_ACTIVITY_INTERACTIVE_LABEL_ALT.comment": "This is for screen reader. When the user is navigating on the transcript, it give hints if the current activity have interactive contents.", "ACTIVITY_YOU_ATTACHED_ALT": "You attached:", "_ACTIVITY_YOU_ATTACHED_ALT.comment": "This is for screen reader and is narrated before each attachments sent by the user.", diff --git a/packages/api/src/localization/yue.json b/packages/api/src/localization/yue.json index cff49133b8..ff9944a3d1 100644 --- a/packages/api/src/localization/yue.json +++ b/packages/api/src/localization/yue.json @@ -5,7 +5,7 @@ "ACTIVITY_BOT_ATTACHED_ALT": "Bot 嘅附件:", "ACTIVITY_BOT_SAID_ALT": "Bot $1 話:", "ACTIVITY_ERROR_BOX_TITLE": "錯嘅信息", - "ACTIVITY_INTERACTIVE_LABEL_ALT": "襟 ENTER 嚟進行互動。", + "ACTIVITY_INTERACTIVE_LABEL_ALT": "襟呢度嚟進行互動。", "ACTIVITY_NUM_ATTACHMENTS_FEW_ALT": "$1 件附件。", "ACTIVITY_NUM_ATTACHMENTS_MANY_ALT": "$1 件附件。", "ACTIVITY_NUM_ATTACHMENTS_ONE_ALT": "一件附件。", diff --git a/packages/component/src/BasicTranscript.js b/packages/component/src/BasicTranscript.js index dc1f3a72dd..8e7da44dac 100644 --- a/packages/component/src/BasicTranscript.js +++ b/packages/component/src/BasicTranscript.js @@ -298,6 +298,16 @@ const InternalTranscript = ({ activityElementsRef, className }) => { rootElement && rootElement.focus(); }; + // Focus on the first tabbable element inside the activity. + // This will be triggered by pressing ENTER while focusing on an interactive activity. + const focusInside = () => { + const [firstTabbableElement] = tabbableElements( + activityElementsRef.current.find(activityElement => activityElement.key === key)?.element + ).filter(({ className }) => className !== 'webchat__basic-transcript__activity-sentinel'); + + firstTabbableElement?.focus(); + }; + renderingElements.push({ activity, @@ -313,6 +323,52 @@ const InternalTranscript = ({ activityElementsRef, className }) => { // Calling this function will put the focus on the transcript and the activity. focusActivity, + // Calling this function will focus on the first tabbable element in the activity. + focusInside, + + handleClick: event => { + // (Related to #4020) + // + // This is called while screen reader is running: + // + // 1. When scan mode is on (Windows Narrator) or in browse mode (NVDA), ENTER key is pressed, or; + // 2. When scan mode is off (Windows Narrator) or in focus mode (NVDA), CAPSLOCK + ENTER is pressed + // + // Although `document.activeElement` (a.k.a. primary focus) is on the transcript, + // when ENTER key is pressed with screen reader in scan mode, screen reader will + // "do primary action", which ask the browser to send a `click` event to the + // active descendant (a.k.a. focused activity). + // + // While outside of scan mode, this will also capture CAPSLOCK + ENTER, + // which is a key combo for "do primary action" or "activates the current navigator object". + // + // We cannot capture plain ENTER key outside of scan mode here. + // We can only capture it on `keydown` event fired to the transcript element. + // + // Also see https://github.com/nvaccess/nvda/issues/7898. + + const { currentTarget, target } = event; + + // The followings are for Windows Narrator: + // - When scan mode is on + // - Press ENTER will dispatch "click" event to the...
)
+ // - Perhaps, we should add role="application" to container of Web Chat to disable browse mode, as we are not a web document and already offered a full-fledge navigation experience
+ if (document.getElementById(currentTarget.getAttribute('aria-labelledby')).contains(target)) {
+ return focusInside();
+ }
+ },
+
// When a child of the activity receives focus, notify the transcript to set the aria-activedescendant to this activity.
handleFocus: () => {
setFocusedActivityKey(getActivityUniqueId(activity));
@@ -627,22 +683,10 @@ const InternalTranscript = ({ activityElementsRef, className }) => {
break;
case 'Enter':
- if (!fromEndOfTranscriptIndicator) {
- const focusedActivityEntry = renderingElements.find(({ key }) => key === focusedActivityKey);
-
- if (focusedActivityEntry) {
- const { element: focusedActivityElement } =
- activityElementsRef.current.find(({ activity }) => activity === focusedActivityEntry.activity) || {};
-
- if (focusedActivityElement) {
- const [firstTabbableElement] = tabbableElements(focusedActivityElement).filter(
- ({ className }) => className !== 'webchat__basic-transcript__activity-sentinel'
- );
-
- firstTabbableElement && firstTabbableElement.focus();
- }
- }
- }
+ // This is capturing plain ENTER.
+ // When screen reader is not running, or screen reader is running outside of scan mode, the ENTER key will be captured here.
+ fromEndOfTranscriptIndicator ||
+ renderingElements.find(({ key }) => key === focusedActivityKey)?.focusInside();
break;
@@ -666,7 +710,7 @@ const InternalTranscript = ({ activityElementsRef, className }) => {
event.stopPropagation();
}
},
- [focusedActivityKey, activityElementsRef, focusRelativeActivity, focus, terminatorRef, renderingElements]
+ [focusedActivityKey, focusRelativeActivity, focus, renderingElements, terminatorRef]
);
const labelId = useUniqueId('webchat__basic-transcript__label');
@@ -793,6 +837,7 @@ const InternalTranscript = ({ activityElementsRef, className }) => {
activity,
callbackRef,
focusActivity,
+ handleClick,
handleFocus,
handleKeyDown,
handleMouseDownCapture,
@@ -810,7 +855,7 @@ const InternalTranscript = ({ activityElementsRef, className }) => {
) => {
const { ariaLabelID, element } =
activityElementsRef.current.find(entry => entry.activity === activity) || {};
- const activeDescendant = focusedActivityKey === key;
+ const isActiveDescendant = focusedActivityKey === key;
const isContentInteractive = !!(element
? tabbableElements(element.querySelector('.webchat__basic-transcript__activity-box')).length
: 0);
@@ -823,12 +868,18 @@ const InternalTranscript = ({ activityElementsRef, className }) => {
'webchat__basic-transcript__activity--from-bot': role !== 'user',
'webchat__basic-transcript__activity--from-user': role === 'user'
})}
- // Set "id" for valid for accessibility.
+ // Set "id" is required for accessibility active descendant feature.
/* eslint-disable-next-line react/forbid-dom-props */
- id={activeDescendant ? activeDescendantElementId : undefined}
+ id={isActiveDescendant ? activeDescendantElementId : undefined}
key={key}
+ // This is for capturing "do primary action" done by the screen reader.
+ // With screen reader, will narrate "Press ENTER to interact". But in scan mode, ENTER means "do primary action".
+ // If `onClick` is set, screen reader will send click event when "do primary action".
+ // Related to #4020.
+ onClick={handleClick}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
+ // When NVDA is in browse mode, using up/down arrow key to "browse" will dispatch "click" and "mousedown" events for