diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js index 9a99eb6fb6bae9..07556bf488d3c8 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js @@ -52,6 +52,13 @@ const AccessibilityInfo = { return Promise.resolve(false); }, + /** + * macOS only + */ + isHighContrastEnabled: function(): Promise { + return Promise.resolve(false); + }, + /** * iOS only */ diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js index 9e721a776b75cc..66080bfecca0fa 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js @@ -85,6 +85,13 @@ const AccessibilityInfo = { }); }, + /** + * macOS only + */ + isHighContrastEnabled: function(): Promise { + return Promise.resolve(false); + }, + /** * Query whether inverted colors are currently enabled. * diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js index 095cf67c30860b..620f1e33e83d21 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js @@ -18,6 +18,7 @@ const RCTDeviceEventEmitter = require('../../EventEmitter/RCTDeviceEventEmitter' import NativeAccessibilityManager from './NativeAccessibilityManager'; const CHANGE_EVENT_NAME = { + highContrastChanged: 'highContrastChanged', invertColorsChanged: 'invertColorsChanged', reduceMotionChanged: 'reduceMotionChanged', reduceTransparencyChanged: 'reduceTransparencyChanged', @@ -26,6 +27,7 @@ const CHANGE_EVENT_NAME = { type ChangeEventName = $Keys<{ change: string, + highContrastChanged: string, invertColorsChanged: string, reduceMotionChanged: string, reduceTransparencyChanged: string, @@ -58,6 +60,22 @@ const AccessibilityInfo = { return Promise.resolve(false); }, + /** + * Query whether high contrast is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when invert color is enabled and `false` otherwise. + */ + isHighContrastEnabled: function(): Promise { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentHighContrastState(resolve, reject); + } else { + reject(reject); + } + }); + }, + /** * Query whether inverted colors are currently enabled. * diff --git a/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js b/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js index 0a1abe5792492b..cade5c63acec77 100644 --- a/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js +++ b/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js @@ -22,6 +22,12 @@ export interface Spec extends TurboModule { onSuccess: (isGrayscaleEnabled: boolean) => void, onError: (error: Object) => void, ) => void; + // [TODO(macOS ISS#2323203) + +getCurrentHighContrastState: ( + onSuccess: (isHighContrastEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + // ]TODO(macOS ISS#2323203) +getCurrentInvertColorsState: ( onSuccess: (isInvertColorsEnabled: boolean) => void, onError: (error: Object) => void, diff --git a/RNTester/js/examples/Accessibility/AccessibilityExample.js b/RNTester/js/examples/Accessibility/AccessibilityExample.js index bf92e28a0c9699..3b68f8884cc023 100644 --- a/RNTester/js/examples/Accessibility/AccessibilityExample.js +++ b/RNTester/js/examples/Accessibility/AccessibilityExample.js @@ -739,6 +739,16 @@ class DisplayOptionsStatusExample extends React.Component<{}> { state = {}; componentDidMount() { + AccessibilityInfo.addEventListener( + 'highContrastChanged', + this._handleHighContrastToggled, + ); + AccessibilityInfo.isHighContrastEnabled().done(isEnabled => { + this.setState({ + highContrastEnabled: isEnabled, + }); + }); + AccessibilityInfo.addEventListener( 'invertColorsChanged', this._handleInvertColorsToggled, @@ -771,6 +781,10 @@ class DisplayOptionsStatusExample extends React.Component<{}> { } componentWillUnmount() { + AccessibilityInfo.removeEventListener( + 'highContrastChanged', + this._handleHighContrastToggled, + ); AccessibilityInfo.removeEventListener( 'invertColorsChanged', this._handleInvertColorsToggled, @@ -785,6 +799,12 @@ class DisplayOptionsStatusExample extends React.Component<{}> { ); } + _handleHighContrastToggled = isEnabled => { + this.setState({ + highContrastEnabled: isEnabled, + }); + }; + _handleInvertColorsToggled = isEnabled => { this.setState({ invertColorsEnabled: isEnabled, @@ -806,6 +826,12 @@ class DisplayOptionsStatusExample extends React.Component<{}> { render() { return ( + + + High contrast is{' '} + {this.state.highContrastEnabled ? 'enabled' : 'disabled'}. + + Invert colors is{' '} diff --git a/React/CoreModules/RCTAccessibilityManager.h b/React/CoreModules/RCTAccessibilityManager.h index 7b5a132d262621..310dd8d0600424 100644 --- a/React/CoreModules/RCTAccessibilityManager.h +++ b/React/CoreModules/RCTAccessibilityManager.h @@ -21,6 +21,7 @@ extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; / @property (nonatomic, assign) BOOL isBoldTextEnabled; @property (nonatomic, assign) BOOL isGrayscaleEnabled; +@property (nonatomic, assign) BOOL isHighContrastEnabled; // TODO(macOS ISS#2323203) - maps to shouldIncreaseContrast on macOS @property (nonatomic, assign) BOOL isInvertColorsEnabled; @property (nonatomic, assign) BOOL isReduceMotionEnabled; @property (nonatomic, assign) BOOL isReduceTransparencyEnabled; diff --git a/React/Modules/MacOS/RCTAccessibilityManager.m b/React/Modules/MacOS/RCTAccessibilityManager.m index 416048a278c748..58974ffbb2afcd 100644 --- a/React/Modules/MacOS/RCTAccessibilityManager.m +++ b/React/Modules/MacOS/RCTAccessibilityManager.m @@ -23,12 +23,12 @@ @implementation RCTAccessibilityManager static void *AccessibilityVoiceOverChangeContext = &AccessibilityVoiceOverChangeContext; -+ (BOOL)requiresMainQueueSetup ++ (BOOL)requiresMainQueueSetup { - return NO; + return YES; } -- (instancetype)init +- (instancetype)init { if (self = [super init]) { [[NSWorkspace sharedWorkspace] addObserver:self @@ -39,11 +39,11 @@ - (instancetype)init selector:@selector(accessibilityDisplayOptionsChange:) name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification object:nil]; + _isHighContrastEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldIncreaseContrast]; _isInvertColorsEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors]; _isReduceMotionEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]; _isReduceTransparencyEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency]; _isVoiceOverEnabled = [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]; - } return self; } @@ -53,7 +53,6 @@ - (void)dealloc [[NSWorkspace sharedWorkspace] removeObserver:self forKeyPath:@"voiceOverEnabled" context:AccessibilityVoiceOverChangeContext]; - [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; } RCT_EXPORT_METHOD(announceForAccessibility:(NSString *)announcement) @@ -67,6 +66,12 @@ - (void)dealloc ); } +RCT_EXPORT_METHOD(getCurrentHighContrastState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isHighContrastEnabled)]); +} + RCT_EXPORT_METHOD(getCurrentInvertColorsState:(RCTResponseSenderBlock)callback error:(__unused RCTResponseSenderBlock)error) { @@ -124,9 +129,16 @@ - (void)observeValueForKeyPath:(NSString *)keyPath - (void)accessibilityDisplayOptionsChange:(NSNotification *)notification { + BOOL newHighContrastEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldIncreaseContrast]; BOOL newInvertColorsEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldInvertColors]; BOOL newReduceMotionEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]; BOOL newReduceTransparencyEnabled = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency]; + + if (_isHighContrastEnabled != newHighContrastEnabled) { + _isHighContrastEnabled = newHighContrastEnabled; + [_bridge.eventDispatcher sendDeviceEventWithName:@"highContrastChanged" + body:@(_isHighContrastEnabled)]; + } if (_isInvertColorsEnabled != newInvertColorsEnabled) { _isInvertColorsEnabled = newInvertColorsEnabled; [_bridge.eventDispatcher sendDeviceEventWithName:@"invertColorsChanged"