Skip to content

Commit c788eda

Browse files
committed
Implement RCTViewAccessibilityElement and RCTUIAccessibilityTraits
1 parent 1682657 commit c788eda

File tree

6 files changed

+247
-131
lines changed

6 files changed

+247
-131
lines changed

packages/react-native/React/Base/RCTUIKit.h

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ UIKIT_STATIC_INLINE CGRect CGRectValue(NSValue *value)
9999

100100
#define RCTUIColor UIColor
101101

102+
// RCTUIAccessibilityTraits - typedef to UIAccessibilityTraits on iOS
103+
static const UIAccessibilityTraits RCTUIAccessibilityTraitSwitch = 0x20000000000001;
104+
105+
typedef UIAccessibilityTraits RCTUIAccessibilityTraits;
106+
#define RCTUIAccessibilityTraitNone UIAccessibilityTraitNone
107+
#define RCTUIAccessibilityTraitButton UIAccessibilityTraitButton
108+
#define RCTUIAccessibilityTraitLink UIAccessibilityTraitLink
109+
#define RCTUIAccessibilityTraitImage UIAccessibilityTraitImage
110+
#define RCTUIAccessibilityTraitSelected UIAccessibilityTraitSelected
111+
#define RCTUIAccessibilityTraitPlaysSound UIAccessibilityTraitPlaysSound
112+
#define RCTUIAccessibilityTraitKeyboardKey UIAccessibilityTraitKeyboardKey
113+
#define RCTUIAccessibilityTraitStaticText UIAccessibilityTraitStaticText
114+
#define RCTUIAccessibilityTraitSummaryElement UIAccessibilityTraitSummaryElement
115+
#define RCTUIAccessibilityTraitNotEnabled UIAccessibilityTraitNotEnabled
116+
#define RCTUIAccessibilityTraitUpdatesFrequently UIAccessibilityTraitUpdatesFrequently
117+
#define RCTUIAccessibilityTraitSearchField UIAccessibilityTraitSearchField
118+
#define RCTUIAccessibilityTraitStartsMediaSession UIAccessibilityTraitStartsMediaSession
119+
#define RCTUIAccessibilityTraitAdjustable UIAccessibilityTraitAdjustable
120+
#define RCTUIAccessibilityTraitAllowsDirectInteraction UIAccessibilityTraitAllowsDirectInteraction
121+
#define RCTUIAccessibilityTraitCausesPageTurn UIAccessibilityTraitCausesPageTurn
122+
#define RCTUIAccessibilityTraitHeader UIAccessibilityTraitHeader
123+
#define RCTUIAccessibilityTraitTabBar UIAccessibilityTraitTabBar
124+
102125
UIKIT_STATIC_INLINE UIFont *UIFontWithSize(UIFont *font, CGFloat pointSize)
103126
{
104127
return [font fontWithSize:pointSize];
@@ -281,6 +304,74 @@ CGContextRef UIGraphicsGetCurrentContext(void);
281304
// UIAccessibility.h/NSAccessibility.h
282305
@compatibility_alias UIAccessibilityCustomAction NSAccessibilityCustomAction;
283306

307+
// RCTUIAccessibilityTraits - define as bitmask type for macOS
308+
// On macOS these don't directly map to behavior, but allow code to compile
309+
// The actual accessibility role mapping is done in RCTViewAccessibilityElement
310+
typedef uint64_t RCTUIAccessibilityTraits;
311+
312+
// Trait constants matching iOS UIAccessibilityConstants.h
313+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitNone = 0;
314+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitButton = (1ULL << 0);
315+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitLink = (1ULL << 1);
316+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitImage = (1ULL << 2);
317+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitSelected = (1ULL << 3);
318+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitPlaysSound = (1ULL << 4);
319+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitKeyboardKey = (1ULL << 5);
320+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitStaticText = (1ULL << 6);
321+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitSummaryElement = (1ULL << 7);
322+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitNotEnabled = (1ULL << 8);
323+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitUpdatesFrequently = (1ULL << 9);
324+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitSearchField = (1ULL << 10);
325+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitStartsMediaSession = (1ULL << 11);
326+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitAdjustable = (1ULL << 12);
327+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitAllowsDirectInteraction = (1ULL << 13);
328+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitCausesPageTurn = (1ULL << 14);
329+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitHeader = (1ULL << 15);
330+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitTabBar = (1ULL << 16);
331+
static const RCTUIAccessibilityTraits RCTUIAccessibilityTraitSwitch = (1ULL << 17);
332+
333+
// Convert RCTUIAccessibilityTraits to NSAccessibilityRole for macOS
334+
NS_INLINE NSAccessibilityRole RCTAccessibilityRoleFromTraits(RCTUIAccessibilityTraits traits)
335+
{
336+
if (traits & RCTUIAccessibilityTraitSwitch) {
337+
return NSAccessibilityCheckBoxRole;
338+
}
339+
if (traits & RCTUIAccessibilityTraitButton) {
340+
return NSAccessibilityButtonRole;
341+
}
342+
if (traits & RCTUIAccessibilityTraitLink) {
343+
return NSAccessibilityLinkRole;
344+
}
345+
if (traits & RCTUIAccessibilityTraitImage) {
346+
return NSAccessibilityImageRole;
347+
}
348+
if (traits & RCTUIAccessibilityTraitKeyboardKey) {
349+
return NSAccessibilityButtonRole;
350+
}
351+
if (traits & RCTUIAccessibilityTraitHeader) {
352+
return NSAccessibilityStaticTextRole;
353+
}
354+
if (traits & RCTUIAccessibilityTraitStaticText) {
355+
return NSAccessibilityStaticTextRole;
356+
}
357+
if (traits & RCTUIAccessibilityTraitSummaryElement) {
358+
return NSAccessibilityStaticTextRole;
359+
}
360+
if (traits & RCTUIAccessibilityTraitSearchField) {
361+
return NSAccessibilityTextFieldRole;
362+
}
363+
if (traits & RCTUIAccessibilityTraitAdjustable) {
364+
return NSAccessibilitySliderRole;
365+
}
366+
if (traits & RCTUIAccessibilityTraitUpdatesFrequently) {
367+
return NSAccessibilityProgressIndicatorRole;
368+
}
369+
if (traits & RCTUIAccessibilityTraitTabBar) {
370+
return NSAccessibilityTabGroupRole;
371+
}
372+
return NSAccessibilityUnknownRole;
373+
}
374+
284375
// UIColor.h/NSColor.h
285376
#define RCTUIColor NSColor
286377

@@ -438,6 +529,11 @@ void UIBezierPathAppendPath(UIBezierPath *path, UIBezierPath *appendPath);
438529
*/
439530
@property (nonatomic, assign) BOOL enableFocusRing;
440531

532+
/**
533+
* iOS compatibility shim. On macOS, this forwards to accessibilityChildren.
534+
*/
535+
@property (nonatomic, copy) NSArray *accessibilityElements;
536+
441537
@end
442538

443539
// UIScrollView

packages/react-native/React/Base/macOS/RCTUIKit.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,17 @@ - (BOOL)becomeFirstResponder
333333

334334
@synthesize userInteractionEnabled = _userInteractionEnabled;
335335

336+
// iOS compatibility: accessibilityElements forwards to accessibilityChildren on macOS
337+
- (NSArray *)accessibilityElements
338+
{
339+
return self.accessibilityChildren;
340+
}
341+
342+
- (void)setAccessibilityElements:(NSArray *)accessibilityElements
343+
{
344+
self.accessibilityChildren = accessibilityElements;
345+
}
346+
336347
- (NSView *)hitTest:(CGPoint)point withEvent:(__unused UIEvent *)event
337348
{
338349
// [macOS

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewAccessibilityElement.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ NS_ASSUME_NONNULL_BEGIN
2424
#endif // macOS]
2525

2626
@property (readonly) RCTViewComponentView *view;
27+
#if TARGET_OS_OSX // [macOS
28+
@property (nonatomic, assign) RCTUIAccessibilityTraits accessibilityTraits;
29+
/**
30+
* iOS compatibility shim. On macOS, this forwards to accessibilityElement.
31+
*/
32+
@property (nonatomic, assign) BOOL isAccessibilityElement;
33+
#endif // macOS]
2734

2835
- (instancetype)initWithView:(RCTViewComponentView *)view;
2936

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewAccessibilityElement.mm

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,42 @@
88
#import "RCTViewAccessibilityElement.h"
99

1010
@implementation RCTViewAccessibilityElement
11+
{
12+
#if TARGET_OS_OSX // [macOS
13+
RCTUIAccessibilityTraits _accessibilityTraits;
14+
#endif // macOS]
15+
}
1116

12-
#if !TARGET_OS_OSX // [macOS]
1317
- (instancetype)initWithView:(RCTViewComponentView *)view
1418
{
19+
#if !TARGET_OS_OSX // [macOS]
1520
if (self = [super initWithAccessibilityContainer:view]) {
21+
#else // [macOS
22+
if (self = [super init]) {
23+
[self setAccessibilityParent:view];
24+
_accessibilityTraits = RCTUIAccessibilityTraitNone;
25+
#endif // macOS]
1626
_view = view;
1727
}
18-
1928
return self;
2029
}
2130

2231
- (CGRect)accessibilityFrame
2332
{
33+
#if !TARGET_OS_OSX // [macOS]
2434
return UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view);
35+
#else // [macOS
36+
NSRect boundsInWindow = [_view convertRect:_view.bounds toView:nil];
37+
return [_view.window convertRectToScreen:boundsInWindow];
38+
#endif // macOS]
39+
}
40+
41+
#if TARGET_OS_OSX // [macOS
42+
- (id)accessibilityParent
43+
{
44+
return _view;
2545
}
46+
#endif // macOS]
2647

2748
#pragma mark - Forwarding to _view
2849

@@ -36,6 +57,7 @@ - (NSString *)accessibilityValue
3657
return _view.accessibilityValue;
3758
}
3859

60+
#if !TARGET_OS_OSX // [macOS]
3961
- (UIAccessibilityTraits)accessibilityTraits
4062
{
4163
return _view.accessibilityTraits;
@@ -80,6 +102,84 @@ - (BOOL)accessibilityRespondsToUserInteraction
80102
{
81103
return _view.accessibilityRespondsToUserInteraction;
82104
}
83-
#endif // [macOS]
105+
#else // [macOS
106+
- (RCTUIAccessibilityTraits)accessibilityTraits
107+
{
108+
return _accessibilityTraits;
109+
}
110+
111+
- (void)setAccessibilityTraits:(RCTUIAccessibilityTraits)accessibilityTraits
112+
{
113+
if (_accessibilityTraits == accessibilityTraits) {
114+
return;
115+
}
116+
_accessibilityTraits = accessibilityTraits;
117+
118+
// Map traits to AppKit accessibility role
119+
// Order matters - more specific traits should be checked first
120+
NSAccessibilityRole role = NSAccessibilityUnknownRole;
121+
122+
if (accessibilityTraits & RCTUIAccessibilityTraitSwitch) {
123+
role = NSAccessibilityCheckBoxRole;
124+
} else if (accessibilityTraits & RCTUIAccessibilityTraitButton) {
125+
role = NSAccessibilityButtonRole;
126+
} else if (accessibilityTraits & RCTUIAccessibilityTraitLink) {
127+
role = NSAccessibilityLinkRole;
128+
} else if (accessibilityTraits & RCTUIAccessibilityTraitImage) {
129+
role = NSAccessibilityImageRole;
130+
} else if (accessibilityTraits & RCTUIAccessibilityTraitKeyboardKey) {
131+
role = NSAccessibilityButtonRole;
132+
} else if (accessibilityTraits & RCTUIAccessibilityTraitHeader) {
133+
role = NSAccessibilityStaticTextRole;
134+
} else if (accessibilityTraits & RCTUIAccessibilityTraitStaticText) {
135+
role = NSAccessibilityStaticTextRole;
136+
} else if (accessibilityTraits & RCTUIAccessibilityTraitSummaryElement) {
137+
role = NSAccessibilityStaticTextRole;
138+
} else if (accessibilityTraits & RCTUIAccessibilityTraitSearchField) {
139+
role = NSAccessibilityTextFieldRole;
140+
} else if (accessibilityTraits & RCTUIAccessibilityTraitAdjustable) {
141+
role = NSAccessibilitySliderRole;
142+
} else if (accessibilityTraits & RCTUIAccessibilityTraitUpdatesFrequently) {
143+
role = NSAccessibilityProgressIndicatorRole;
144+
} else if (accessibilityTraits & RCTUIAccessibilityTraitTabBar) {
145+
role = NSAccessibilityTabGroupRole;
146+
}
147+
148+
[self setAccessibilityRole:role];
149+
150+
// State traits
151+
// Selected trait -> accessibilitySelected
152+
[self setAccessibilitySelected:(accessibilityTraits & RCTUIAccessibilityTraitSelected) != 0];
153+
154+
// NotEnabled trait -> accessibilityEnabled (inverted)
155+
[self setAccessibilityEnabled:(accessibilityTraits & RCTUIAccessibilityTraitNotEnabled) == 0];
156+
157+
// Behavior traits that don't need AppKit mapping:
158+
// - RCTUIAccessibilityTraitPlaysSound
159+
// - RCTUIAccessibilityTraitStartsMediaSession
160+
// - RCTUIAccessibilityTraitAllowsDirectInteraction
161+
// - RCTUIAccessibilityTraitCausesPageTurn
162+
}
163+
164+
- (NSString *)accessibilityHelp
165+
{
166+
return _view.accessibilityHelp;
167+
}
168+
169+
- (BOOL)isAccessibilityElement
170+
{
171+
return _view.isAccessibilityElement;
172+
}
173+
174+
- (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement
175+
{
176+
self.accessibilityElement = isAccessibilityElement;
177+
}
178+
179+
- (NSArray *)accessibilityChildren
180+
{
181+
return _view.accessibilityChildren;
182+
}
183+
#endif // macOS]
84184

85185
@end

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,14 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
495495

496496
// `accessibilityTraits`
497497
if (oldViewProps.accessibilityTraits != newViewProps.accessibilityTraits) {
498+
RCTUIAccessibilityTraits traits = RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits);
498499
#if !TARGET_OS_OSX // [macOS]
499-
self.accessibilityElement.accessibilityTraits =
500-
RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits);
500+
self.accessibilityElement.accessibilityTraits = traits;
501501
#else // [macOS
502-
self.accessibilityElement.accessibilityRole = RCTUIAccessibilityRoleFromAccessibilityTraits(newViewProps.accessibilityTraits);
502+
// On macOS, accessibilityElement returns self (NSView*) which doesn't have accessibilityTraits.
503+
// Set role directly on self based on traits.
504+
self.accessibilityRole = RCTAccessibilityRoleFromTraits(traits);
505+
self.accessibilityEnabled = (traits & RCTUIAccessibilityTraitNotEnabled) == 0;
503506
#endif // macOS]
504507
}
505508

@@ -1436,8 +1439,7 @@ - (NSView *)accessibilityElement // macOS]
14361439
return self;
14371440
}
14381441

1439-
#if !TARGET_OS_OSX // [macOS
1440-
- (NSArray<NSObject *> *)accessibilityElements
1442+
- (NSArray *)accessibilityElements // [macOS]
14411443
{
14421444
if ([_accessibilityOrderNativeIDs count] <= 0) {
14431445
return super.accessibilityElements;
@@ -1450,7 +1452,7 @@ - (NSView *)accessibilityElement // macOS]
14501452
return _accessibilityElements;
14511453
}
14521454

1453-
NSMutableDictionary<NSString *, UIView *> *nativeIdToView = [NSMutableDictionary new];
1455+
NSMutableDictionary<NSString *, RCTPlatformView *> *nativeIdToView = [NSMutableDictionary new]; // [macOS]
14541456

14551457
[RCTViewComponentView collectAccessibilityElements:self
14561458
intoDictionary:nativeIdToView
@@ -1466,7 +1468,7 @@ - (NSView *)accessibilityElement // macOS]
14661468
_axElementDescribingSelf.isAccessibilityElement = [super isAccessibilityElement];
14671469
[_accessibilityElements addObject:_axElementDescribingSelf];
14681470
} else {
1469-
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
1471+
RCTPlatformView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId]; // [macOS]
14701472
if (viewWithMatchingNativeId) {
14711473
[_accessibilityElements addObject:viewWithMatchingNativeId];
14721474
}
@@ -1475,7 +1477,6 @@ - (NSView *)accessibilityElement // macOS]
14751477

14761478
return _accessibilityElements;
14771479
}
1478-
#endif // macOS]
14791480

14801481
+ (void)collectAccessibilityElements:(RCTPlatformView *)view // [macOS]
14811482
intoDictionary:(NSMutableDictionary<NSString *, RCTPlatformView *> *)dict // [macOS]

0 commit comments

Comments
 (0)