Skip to content

Commit 0f92aa6

Browse files
sbuggaymeta-codesync[bot]
authored andcommitted
Allow views to become focusable (#56141)
Summary: Pull Request resolved: #56141 Changelog: [Internal] Wires up the native side of `focusable` to fabric views. Reviewed By: joevilches Differential Revision: D96751956 fbshipit-source-id: 9867755a11ab0df6faef8e7cdff4eb168345097f
1 parent bea42e5 commit 0f92aa6

3 files changed

Lines changed: 98 additions & 2 deletions

File tree

packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ NS_ASSUME_NONNULL_BEGIN
3939
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDuration;
4040
@property (nonatomic, assign) CGSize minimumSize;
4141

42+
#if TARGET_OS_TV
43+
@property (nonatomic, copy, nullable) NSArray<id<UIFocusEnvironment>> *reactPreferredFocusEnvironments;
44+
@property (nonatomic, weak, nullable) UIView *reactPreferredFocusedView;
45+
#endif
46+
4247
- (instancetype)init NS_UNAVAILABLE;
4348
+ (instancetype)new NS_UNAVAILABLE;
4449
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;

packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ - (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage
121121
[super surface:surface didChangeStage:stage];
122122
if (RCTSurfaceStageIsRunning(stage)) {
123123
[_bridge.performanceLogger markStopForTag:RCTPLTTI];
124+
#if TARGET_OS_TV
125+
dispatch_async(dispatch_get_main_queue(), ^{
126+
self.reactPreferredFocusedView = nil;
127+
[self setNeedsFocusUpdate];
128+
[self updateFocusIfNeeded];
129+
});
130+
#endif
124131
}
125132
}
126133

@@ -154,4 +161,23 @@ - (void)cancelTouches
154161
// Not supported.
155162
}
156163

164+
#if TARGET_OS_TV
165+
#pragma mark - UIFocusEnvironment
166+
167+
- (NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments
168+
{
169+
if (self.reactPreferredFocusEnvironments != nil && self.reactPreferredFocusedView.window != nil) {
170+
NSArray<id<UIFocusEnvironment>> *tempReactPreferredFocusEnvironments = self.reactPreferredFocusEnvironments;
171+
self.reactPreferredFocusEnvironments = nil;
172+
return tempReactPreferredFocusEnvironments;
173+
}
174+
175+
if (self.reactPreferredFocusedView && self.reactPreferredFocusedView.window != nil) {
176+
return @[ self.reactPreferredFocusedView ];
177+
}
178+
179+
return [super preferredFocusEnvironments];
180+
}
181+
#endif
182+
157183
@end

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

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#import "RCTViewComponentView.h"
9+
#import <React/RCTSurfaceHostingProxyRootView.h>
910

1011
#import <CoreGraphics/CoreGraphics.h>
1112
#import <QuartzCore/QuartzCore.h>
@@ -53,6 +54,7 @@ @implementation RCTViewComponentView {
5354
BOOL _useCustomContainerView;
5455
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
5556
RCTSwiftUIContainerViewWrapper *_swiftUIWrapper;
57+
BOOL _focusable;
5658
}
5759

5860
#ifdef RCT_DYNAMIC_FRAMEWORKS
@@ -481,6 +483,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
481483
needsInvalidateLayer = YES;
482484
}
483485

486+
// `focusable`
487+
#if TARGET_OS_TV
488+
if (oldViewProps.focusable != newViewProps.focusable) {
489+
_focusable = (bool)newViewProps.focusable;
490+
}
491+
#endif
492+
484493
// `mixBlendMode`
485494
if (oldViewProps.mixBlendMode != newViewProps.mixBlendMode) {
486495
switch (newViewProps.mixBlendMode) {
@@ -1422,6 +1431,11 @@ - (BOOL)wantsToCooptLabel
14221431
return !super.accessibilityLabel && super.isAccessibilityElement;
14231432
}
14241433

1434+
- (BOOL)canBecomeFocused
1435+
{
1436+
return _focusable;
1437+
}
1438+
14251439
- (BOOL)isAccessibilityElement
14261440
{
14271441
if (self.contentView != nil) {
@@ -1671,6 +1685,8 @@ - (void)transferVisualPropertiesFromView:(UIView *)sourceView toView:(UIView *)d
16711685
}
16721686
}
16731687

1688+
#pragma mark - Focus Events
1689+
16741690
- (BOOL)canBecomeFirstResponder
16751691
{
16761692
return YES;
@@ -1689,18 +1705,43 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
16891705
}
16901706
}
16911707

1708+
#if TARGET_OS_TV
1709+
/// Finds the containing RCTSurfaceHostingProxyRootView by walking up the view
1710+
/// hierarchy.
1711+
- (RCTSurfaceHostingProxyRootView *)containingRootView
1712+
{
1713+
UIView *view = self;
1714+
while (view != nil) {
1715+
if ([view isKindOfClass:[RCTSurfaceHostingProxyRootView class]]) {
1716+
return (RCTSurfaceHostingProxyRootView *)view;
1717+
}
1718+
view = view.superview;
1719+
}
1720+
return nil;
1721+
}
1722+
#endif
1723+
16921724
- (void)focus
16931725
{
16941726
[self becomeFirstResponder];
1727+
1728+
#if TARGET_OS_TV
1729+
RCTSurfaceHostingProxyRootView *rootView = [self containingRootView];
1730+
if (rootView == nil) {
1731+
return;
1732+
}
1733+
1734+
rootView.reactPreferredFocusedView = self;
1735+
[rootView setNeedsFocusUpdate];
1736+
[rootView updateFocusIfNeeded];
1737+
#endif
16951738
}
16961739

16971740
- (void)blur
16981741
{
16991742
[self resignFirstResponder];
17001743
}
17011744

1702-
#pragma mark - Focus Events
1703-
17041745
- (BOOL)becomeFirstResponder
17051746
{
17061747
if (![super becomeFirstResponder]) {
@@ -1727,6 +1768,30 @@ - (BOOL)resignFirstResponder
17271768
return YES;
17281769
}
17291770

1771+
#if TARGET_OS_TV
1772+
1773+
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context
1774+
withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
1775+
{
1776+
if (context.previouslyFocusedView == context.nextFocusedView) {
1777+
return;
1778+
}
1779+
1780+
if (context.nextFocusedView == self) {
1781+
if (_eventEmitter) {
1782+
_eventEmitter->onFocus();
1783+
}
1784+
} else {
1785+
if (_eventEmitter) {
1786+
_eventEmitter->onBlur();
1787+
}
1788+
}
1789+
1790+
[super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
1791+
}
1792+
1793+
#endif
1794+
17301795
@end
17311796

17321797
#ifdef __cplusplus

0 commit comments

Comments
 (0)