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:
Родитель
e9791d24c7
Коммит
8b8fc61969
|
@ -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'),
|
||||
|
|
Загрузка…
Ссылка в новой задаче