Fixing setting focus when RCTView is not yet in window (#1398)

* Fixing setting focus when RCTView is not yet in window

I have a scenario where I send the `focus` command to a component from it's `componentDidMount` lifecycle, which leads native code to try to set focus to a RCTView with a zero frame (okay...) that isn't in a window (also seems unexpected).

I see there's existing code that tries to deal with this for a particular RCTBaseTextInputView, but nothing generically for RCTView. I'm trying to extend that to the RCTView. As part of that, I considered using the existing pending focus flag, but that seems suspect because we could have multiple views competing for focus -- so I am opting to change it to a single, weak ref for the currently *pending* focus view (which we clear if someone else grabs, or attempts to grab focus, or if the pending view later resigns focus, in addition to the scenario already handled viz. pending focus view gets focus).

I have been able to validate these changes against 0.66 for my scenario.

* add diff tags

* add tester app page to demo (and test) focus issue

* update tags to reference new issue

* fix flow + lint

* Fix tags

Co-authored-by: Navneet Kambo <nakambo@nakambo-mm.guest.corp.microsoft.com>
Co-authored-by: Saad Najmi <sanajmi@microsoft.com>
This commit is contained in:
Navneet Kambo 2022-09-07 16:11:53 -07:00 коммит произвёл GitHub
Родитель e9791d24c7
Коммит 8b8fc61969
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 86 добавлений и 13 удалений

Просмотреть файл

@ -779,6 +779,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
name:NSViewBoundsDidChangeNotification
object:[[self enclosingScrollView] contentView]];
}
[self reactViewDidMoveToWindow]; // TODO(macOS GH#1412)
[super viewDidMoveToWindow];
}

Просмотреть файл

@ -91,6 +91,8 @@
- (void)reactAddControllerToClosestParent:(UIViewController *)controller;
#endif // TODO(macOS GH#774)
- (void)reactViewDidMoveToWindow; // TODO(macOS GH#1412)
/**
* Focus manipulation.
*/

Просмотреть файл

@ -267,18 +267,17 @@
}
#endif // TODO(macOS GH#774)
// [TODO(macOS GH#1412)
- (void)reactViewDidMoveToWindow
{
[self reactFocusIfNeeded];
}
// TODO(macOS GH#1412)]
/**
* Focus manipulation.
*/
- (BOOL)reactIsFocusNeeded
{
return [(NSNumber *)objc_getAssociatedObject(self, @selector(reactIsFocusNeeded)) boolValue];
}
- (void)setReactIsFocusNeeded:(BOOL)isFocusNeeded
{
objc_setAssociatedObject(self, @selector(reactIsFocusNeeded), @(isFocusNeeded), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static __weak RCTPlatformView *_pendingFocusView; // TODO(macOS GH#1412)
- (void)reactFocus
{
@ -287,19 +286,23 @@
#else
if (![self becomeFirstResponder]) {
#endif // TODO(macOS GH#774)]
self.reactIsFocusNeeded = YES;
}
// [TODO(macOS GH#1412)
_pendingFocusView = self;
} else {
_pendingFocusView = nil;
}
// TODO(macOS GH#1412)]
}
- (void)reactFocusIfNeeded
{
if (self.reactIsFocusNeeded) {
if ([self isEqual:_pendingFocusView]) { // TODO(macOS GH#1412)
#if TARGET_OS_OSX // [TODO(macOS GH#774)
if ([[self window] makeFirstResponder:self]) {
#else
if ([self becomeFirstResponder]) {
#endif // TODO(macOS GH#774)]
self.reactIsFocusNeeded = NO;
_pendingFocusView = nil; // TODO(macOS GH#1412)
}
}
}
@ -313,6 +316,12 @@
#else
[self resignFirstResponder];
#endif // TODO(macOS GH#774)]
// [TODO(macOS GH#1412)
if ([self isEqual:_pendingFocusView]) {
_pendingFocusView = nil;
}
// TODO(macOS GH#1412)]
}
#pragma mark - Layout

Просмотреть файл

@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
// [TODO(macOS GH #1412)
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {Button, findNodeHandle, UIManager} = ReactNative;
class FocusOnMountExample extends React.Component<{}> {
ref = React.createRef();
componentDidMount() {
if (this.ref.current) {
const commands = UIManager.getViewManagerConfig('RCTView').Commands;
if ('focus' in commands) {
UIManager.dispatchViewManagerCommand(
findNodeHandle(this.ref.current),
UIManager.getViewManagerConfig('RCTView').Commands.focus,
undefined,
);
}
}
}
render() {
return (
<Button
ref={this.ref}
title="This button will be focsued on mount"
onPress={() => {}}
/>
);
}
}
exports.title = 'Focus On Mount';
exports.description = 'Example for focusing a component on mount';
exports.examples = [
{
title: 'FocusOnMountExample',
render: function (): React.Element<any> {
return <FocusOnMountExample />;
},
},
];
// TODO(macOS GH #1412)]

Просмотреть файл

@ -46,6 +46,11 @@ const Components: Array<RNTesterModuleInfo> = [
key: 'FocusEvents',
module: require('../examples/FocusEventsExample/FocusEventsExample'),
}, // ]TODO(OSS Candidate ISS#2710739)
// [TODO(macOS GH #1412)
{
key: 'FocusOnMount',
module: require('../examples/FocusOnMount/FocusOnMount'),
}, // ]TODO(macOS GH #1412)
{
key: 'KeyboardEvents',
module: require('../examples/KeyboardEventsExample/KeyboardEventsExample'),