From 7f2e4a1c25ff31e216473db135cbd445406d1f5c Mon Sep 17 00:00:00 2001 From: Lawrence Win Date: Thu, 19 Oct 2023 12:27:32 -0700 Subject: [PATCH] [0.71] Add support for announceForAccessibilityWithOptions (#12256) * [Win32] Add support for announceForAccessibilityWithOptions (#12237) * initial commit * add NativeAccessibilityInfoWin32.js * update example * Change files * add override and .d.ts file * add another override * add comma * extend options type instead of redefine * try [prop-missing] to fix lint error * fix comment in RNTesterList.win32 * try ignoring AccessibilityInfo.js in .flowconfig * Revert flowconfig version * Lint script runs succesfully locally --------- Co-authored-by: Krystal Kramer * Fix pipeline failing * Fix overrides.json * Change beachball change type * Correctly suppress prop-missing error --------- Co-authored-by: Krystal Kramer --- ...-22bcae82-8c54-41e1-88f1-6aeb9deafbe3.json | 7 + .../AccessibilityExampleWin32.tsx | 23 ++- .../src/js/utils/RNTesterList.win32.js | 9 + .../react-native-win32/.flowconfig | 1 + .../react-native-win32/overrides.json | 17 +- .../AccessibilityInfo/AccessibilityInfo.d.ts | 161 ++++++++++++++++++ .../AccessibilityInfo.win32.js | 60 ++++++- .../NativeAccessibilityInfoWin32.js | 37 ++++ 8 files changed, 300 insertions(+), 15 deletions(-) create mode 100644 change/@office-iss-react-native-win32-22bcae82-8c54-41e1-88f1-6aeb9deafbe3.json create mode 100644 packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts create mode 100644 packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfoWin32.js diff --git a/change/@office-iss-react-native-win32-22bcae82-8c54-41e1-88f1-6aeb9deafbe3.json b/change/@office-iss-react-native-win32-22bcae82-8c54-41e1-88f1-6aeb9deafbe3.json new file mode 100644 index 0000000000..ef0f6126d6 --- /dev/null +++ b/change/@office-iss-react-native-win32-22bcae82-8c54-41e1-88f1-6aeb9deafbe3.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "add support for announceForAccessibilityWithOptions", + "packageName": "@office-iss/react-native-win32", + "email": "krsiler@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@office-iss/react-native-win32-tester/src/js/examples-win32/Accessibility/AccessibilityExampleWin32.tsx b/packages/@office-iss/react-native-win32-tester/src/js/examples-win32/Accessibility/AccessibilityExampleWin32.tsx index 0e47c07666..e14373f239 100644 --- a/packages/@office-iss/react-native-win32-tester/src/js/examples-win32/Accessibility/AccessibilityExampleWin32.tsx +++ b/packages/@office-iss/react-native-win32-tester/src/js/examples-win32/Accessibility/AccessibilityExampleWin32.tsx @@ -474,10 +474,27 @@ const AccessibilityInfoExample: React.FunctionComponent<{}> =() => { const onClick = React.useCallback(() => { AccessibilityInfo.announceForAccessibility('AccessibilityInfo announcement succeeded!'); }, []); + + const onClickDelayed = React.useCallback(() => { + setTimeout(() => { + AccessibilityInfo.announceForAccessibilityWithOptions( + 'AccessibilityInfo announcement succeeded!', + { nativeID: 'AnnouncementTarget' }); + }, 3000); + }, []); + return ( - - - AccessibilityInfo.announceForAccessibility + + + + AccessibilityInfo.announceForAccessibility + + + + + + AccessibilityInfo.announceForAccessibilityWithOptions + ); diff --git a/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js b/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js index 50a6dc4462..426c99c2e5 100644 --- a/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js +++ b/packages/@office-iss/react-native-win32-tester/src/js/utils/RNTesterList.win32.js @@ -15,6 +15,15 @@ import type {RNTesterModuleInfo} from '../types/RNTesterTypes'; import ReactNativeFeatureFlags from 'react-native/Libraries/ReactNative/ReactNativeFeatureFlags'; const Components: Array = [ + /*{ + key: 'DrawerLayoutAndroid', + category: 'UI', + module: require('../examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample'), + },*/ + { + key: 'AccessibilityExampleWin32', + module: require('../examples-win32/Accessibility/AccessibilityExampleWin32'), + }, { key: 'ActivityIndicatorExample', category: 'UI', diff --git a/packages/@office-iss/react-native-win32/.flowconfig b/packages/@office-iss/react-native-win32/.flowconfig index 48a93233fa..159e304ea6 100644 --- a/packages/@office-iss/react-native-win32/.flowconfig +++ b/packages/@office-iss/react-native-win32/.flowconfig @@ -10,6 +10,7 @@ ; initRNLibraries build step /index.js /Libraries/Alert/Alert.js +/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js /Libraries/Components/Pressable/Pressable.js /Libraries/Components/SafeAreaView/SafeAreaView.js /Libraries/Components/TextInput/TextInput.js diff --git a/packages/@office-iss/react-native-win32/overrides.json b/packages/@office-iss/react-native-win32/overrides.json index ec7f224421..7d239b30ae 100644 --- a/packages/@office-iss/react-native-win32/overrides.json +++ b/packages/@office-iss/react-native-win32/overrides.json @@ -32,11 +32,16 @@ "baseHash": "897569d77df852480332b7ce7ec1b594cf40aa28" }, { - "type": "patch", + "type": "derived", + "file": "src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts", + "baseFile": "Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts", + "baseHash": "7b4cf9114df53038aeb1508f77d868b45e09049b" + }, + { + "type": "derived", "file": "src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js", "baseFile": "Libraries/Components/AccessibilityInfo/AccessibilityInfo.js", - "baseHash": "8ed7c87f3165558abe517218143dc6f73d91cc46", - "issue": 4578 + "baseHash": "8ed7c87f3165558abe517218143dc6f73d91cc46" }, { "type": "copy", @@ -45,6 +50,12 @@ "baseHash": "d37b2f72125246ababf3260e99ef790ce76fe3bb", "issue": 4578 }, + { + "type": "derived", + "file": "src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfoWin32.js", + "baseFile": "Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js", + "baseHash": "9427a7feebfbe3de606b2d100439cabf2faa8661" + }, { "type": "platform", "file": "src/Libraries/Components/Button/ButtonWin32.Props.ts" diff --git a/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts b/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts new file mode 100644 index 0000000000..dcce13a0cd --- /dev/null +++ b/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts @@ -0,0 +1,161 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import type * as React from 'react'; +import {HostComponent} from '../../../types/public/ReactNativeTypes'; +import {EmitterSubscription} from '../../vendor/emitter/EventEmitter'; + +type AccessibilityChangeEventName = + | 'change' // deprecated, maps to screenReaderChanged + | 'boldTextChanged' // iOS-only Event + | 'grayscaleChanged' // iOS-only Event + | 'invertColorsChanged' // iOS-only Event + | 'reduceMotionChanged' + | 'screenReaderChanged' + | 'reduceTransparencyChanged'; // iOS-only Event + +type AccessibilityChangeEvent = boolean; + +type AccessibilityChangeEventHandler = ( + event: AccessibilityChangeEvent, +) => void; + +type AccessibilityAnnouncementEventName = 'announcementFinished'; // iOS-only Event + +type AccessibilityAnnouncementFinishedEvent = { + announcement: string; + success: boolean; +}; + +type AccessibilityAnnouncementFinishedEventHandler = ( + event: AccessibilityAnnouncementFinishedEvent, +) => void; + +type AccessibilityEventTypes = 'click' | 'focus' | 'viewHoverEnter'; + +/** + * @see https://reactnative.dev/docs/accessibilityinfo + */ +export interface AccessibilityInfoStatic { + /** + * Query whether bold text is currently enabled. + * + * @platform ios + */ + isBoldTextEnabled: () => Promise; + + /** + * Query whether grayscale is currently enabled. + * + * @platform ios + */ + isGrayscaleEnabled: () => Promise; + + /** + * Query whether invert colors is currently enabled. + * + * @platform ios + */ + isInvertColorsEnabled: () => Promise; + + /** + * Query whether reduce motion is currently enabled. + */ + isReduceMotionEnabled: () => Promise; + + /** + * Query whether reduce motion and prefer cross-fade transitions settings are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when prefer cross-fade transitions is enabled and `false` otherwise. + */ + prefersCrossFadeTransitions(): Promise; + + /** + * Query whether reduce transparency is currently enabled. + * + * @platform ios + */ + isReduceTransparencyEnabled: () => Promise; + + /** + * Query whether a screen reader is currently enabled. + */ + isScreenReaderEnabled: () => Promise; + + /** + * Query whether Accessibility Service is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when any service is enabled and `false` otherwise. + * + * @platform android + */ + isAccessibilityServiceEnabled(): Promise; + + /** + * Add an event handler. Supported events: + * - announcementFinished: iOS-only event. Fires when the screen reader has finished making an announcement. + * The argument to the event handler is a dictionary with these keys: + * - announcement: The string announced by the screen reader. + * - success: A boolean indicating whether the announcement was successfully made. + * - AccessibilityEventName constants other than announcementFinished: Fires on accessibility feature change. + * The argument to the event handler is a boolean. + * The boolean is true when the related event's feature is enabled and false otherwise. + * + */ + addEventListener( + eventName: AccessibilityChangeEventName, + handler: AccessibilityChangeEventHandler, + ): EmitterSubscription; + addEventListener( + eventName: AccessibilityAnnouncementEventName, + handler: AccessibilityAnnouncementFinishedEventHandler, + ): EmitterSubscription; + + /** + * Set accessibility focus to a react component. + */ + setAccessibilityFocus: (reactTag: number) => void; + + /** + * Post a string to be announced by the screen reader. + */ + announceForAccessibility: (announcement: string) => void; + + /** + * Post a string to be announced by the screen reader. + * - `announcement`: The string announced by the screen reader. + * - `options`: An object that configures the reading options. + * - `queue`: The announcement will be queued behind existing announcements. iOS only. + * - `nativeID`: The nativeID of the element to send the announcement from. win32 only. + */ + announceForAccessibilityWithOptions( + announcement: string, + options: { + queue?: boolean | undefined; + nativeID?: string | undefined; // win32 + }, + ): void; + + /** + * Gets the timeout in millisecond that the user needs. + * This value is set in "Time to take action (Accessibility timeout)" of "Accessibility" settings. + * + * @platform android + */ + getRecommendedTimeoutMillis: (originalTimeout: number) => Promise; + sendAccessibilityEvent: ( + handle: React.ElementRef>, + eventType: AccessibilityEventTypes, + ) => void; +} + +export const AccessibilityInfo: AccessibilityInfoStatic; +export type AccessibilityInfo = AccessibilityInfoStatic; diff --git a/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js b/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js index 19a2dad3b1..2cacc3a257 100644 --- a/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js +++ b/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js @@ -18,6 +18,7 @@ import {sendAccessibilityEvent} from '../../ReactNative/RendererProxy'; import Platform from '../../Utilities/Platform'; import legacySendAccessibilityEvent from './legacySendAccessibilityEvent'; import NativeAccessibilityInfo from './NativeAccessibilityInfo'; +import NativeAccessibilityInfoWin32 from './NativeAccessibilityInfoWin32'; import NativeAccessibilityManagerIOS from './NativeAccessibilityManager'; // Events that are only supported on Android. @@ -167,12 +168,18 @@ const AccessibilityInfo: AccessibilityInfoType = { */ isReduceMotionEnabled(): Promise { return new Promise((resolve, reject) => { - if (Platform.OS === 'android' || Platform.OS === 'win32') { + if (Platform.OS === 'android') { if (NativeAccessibilityInfo != null) { NativeAccessibilityInfo.isReduceMotionEnabled(resolve); } else { reject(null); } + } else if (Platform.OS === 'win32') { + if (NativeAccessibilityInfoWin32 != null) { + NativeAccessibilityInfoWin32.isReduceMotionEnabled(resolve); + } else { + reject(null); + } } else { if (NativeAccessibilityManagerIOS != null) { NativeAccessibilityManagerIOS.getCurrentReduceMotionState( @@ -249,12 +256,18 @@ const AccessibilityInfo: AccessibilityInfoType = { */ isScreenReaderEnabled(): Promise { return new Promise((resolve, reject) => { - if (Platform.OS === 'android' || Platform.OS === 'win32') { + if (Platform.OS === 'android') { if (NativeAccessibilityInfo != null) { NativeAccessibilityInfo.isTouchExplorationEnabled(resolve); } else { reject(null); } + } else if (Platform.OS === 'win32') { + if (NativeAccessibilityInfoWin32 != null) { + NativeAccessibilityInfoWin32.isTouchExplorationEnabled(resolve); + } else { + reject(null); + } } else { if (NativeAccessibilityManagerIOS != null) { NativeAccessibilityManagerIOS.getCurrentVoiceOverState( @@ -371,8 +384,10 @@ const AccessibilityInfo: AccessibilityInfoType = { * See https://reactnative.dev/docs/accessibilityinfo#announceforaccessibility */ announceForAccessibility(announcement: string): void { - if (Platform.OS === 'android' || Platform.OS === 'win32') { + if (Platform.OS === 'android') { NativeAccessibilityInfo?.announceForAccessibility(announcement); + } else if (Platform.OS === 'win32') { + NativeAccessibilityInfoWin32?.announceForAccessibility(announcement); } else { NativeAccessibilityManagerIOS?.announceForAccessibility(announcement); } @@ -383,19 +398,35 @@ const AccessibilityInfo: AccessibilityInfoType = { * - `announcement`: The string announced by the screen reader. * - `options`: An object that configures the reading options. * - `queue`: The announcement will be queued behind existing announcements. iOS only. + * - `nativeID`: The nativeID of the element to send the announcement from. win32 only. */ + // $FlowIgnore[prop-missing] announceForAccessibilityWithOptions( announcement: string, - options: {queue?: boolean}, + options: { + queue?: boolean, + nativeID?: string, // win32 + }, ): void { - if (Platform.OS === 'android' || Platform.OS === 'win32') { + if (Platform.OS === 'android') { NativeAccessibilityInfo?.announceForAccessibility(announcement); - } else { - if (NativeAccessibilityManagerIOS?.announceForAccessibilityWithOptions) { - NativeAccessibilityManagerIOS?.announceForAccessibilityWithOptions( + } else if (Platform.OS === 'win32') { + if (NativeAccessibilityInfoWin32?.announceForAccessibilityWithOptions) { + NativeAccessibilityInfoWin32?.announceForAccessibilityWithOptions( announcement, options, ); + } else { + NativeAccessibilityInfoWin32?.announceForAccessibility(announcement); + } + } else { + if (NativeAccessibilityManagerIOS?.announceForAccessibilityWithOptions) { + const {nativeID: _, ...iosOptions} = options; + // $FlowFixMe[prop-missing] + NativeAccessibilityManagerIOS?.announceForAccessibilityWithOptions( + announcement, + iosOptions, + ); } else { NativeAccessibilityManagerIOS?.announceForAccessibility(announcement); } @@ -408,7 +439,7 @@ const AccessibilityInfo: AccessibilityInfoType = { * See https://reactnative.dev/docs/accessibilityinfo#getrecommendedtimeoutmillis */ getRecommendedTimeoutMillis(originalTimeout: number): Promise { - if (Platform.OS === 'android' || Platform.OS === 'win32') { + if (Platform.OS === 'android') { return new Promise((resolve, reject) => { if (NativeAccessibilityInfo?.getRecommendedTimeoutMillis) { NativeAccessibilityInfo.getRecommendedTimeoutMillis( @@ -419,6 +450,17 @@ const AccessibilityInfo: AccessibilityInfoType = { resolve(originalTimeout); } }); + } else if (Platform.OS === 'win32') { + return new Promise((resolve, reject) => { + if (NativeAccessibilityInfoWin32?.getRecommendedTimeoutMillis) { + NativeAccessibilityInfoWin32.getRecommendedTimeoutMillis( + originalTimeout, + resolve, + ); + } else { + resolve(originalTimeout); + } + }); } else { return Promise.resolve(originalTimeout); } diff --git a/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfoWin32.js b/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfoWin32.js new file mode 100644 index 0000000000..b1a3476281 --- /dev/null +++ b/packages/@office-iss/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfoWin32.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * + * @format + * @flow + */ + +import type {TurboModule} from '../../TurboModule/RCTExport'; + +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +isReduceMotionEnabled: ( + onSuccess: (isReduceMotionEnabled: boolean) => void, + ) => void; + +isTouchExplorationEnabled: ( + onSuccess: (isScreenReaderEnabled: boolean) => void, + ) => void; + +isAccessibilityServiceEnabled?: ?( + onSuccess: (isAccessibilityServiceEnabled: boolean) => void, + ) => void; + +setAccessibilityFocus: (reactTag: number) => void; + +announceForAccessibility: (announcement: string) => void; + // [Win32 + +announceForAccessibilityWithOptions?: ( + announcement: string, + options: {queue?: boolean, nativeID?: string}, + ) => void; + // Win32] + +getRecommendedTimeoutMillis?: ( + mSec: number, + onSuccess: (recommendedTimeoutMillis: number) => void, + ) => void; +} + +export default (TurboModuleRegistry.get('AccessibilityInfo'): ?Spec);