mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
8a5460ce80
Summary: when we call `focus()` upon a TextInput ref which has prop `editable=false` it marks the textinput as focused in `TextInputState` even though the focus is rejected by textinput itself because it is not editable. then, when you change `editable` prop to `true` and call `focus` again, [this condition](https://github.com/facebook/react-native/blob/e912c462eb0b7166ca5947bb5a3ee20761d910b6/Libraries/Components/TextInput/TextInputState.js#L46) or rather [this one](https://github.com/facebook/react-native/blob/1b2b2198e1b2383523b4655dc8c220d251b057d6/Libraries/Components/TextInput/TextInputState.js#L89) will evaluate to `false` and focus will not happen even though it can and should happen. see also https://github.com/facebook/react-native/blob/0.64-stable/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js#L3895 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [General] [Fixed] - `focus()` on TextInput to respect its `editable` state Pull Request resolved: https://github.com/facebook/react-native/pull/30695 Test Plan: Create a `TextInput` with prop `editable=false` and call `ref.current.focus()` upon its ref. TextInput should not be marked as focused in `TextInputState`. Reviewed By: yungsters Differential Revision: D34357913 Pulled By: lunaleaps fbshipit-source-id: 9a2fb819bbb05ef213c9b5d739dec583ae0a3e6f
198 lines
5.3 KiB
JavaScript
198 lines
5.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
* @flow strict-local
|
|
*/
|
|
|
|
// This class is responsible for coordinating the "focused" state for
|
|
// TextInputs. All calls relating to the keyboard should be funneled
|
|
// through here.
|
|
|
|
const React = require('react');
|
|
const Platform = require('../../Utilities/Platform');
|
|
const {findNodeHandle} = require('../../Renderer/shims/ReactNative');
|
|
import {Commands as AndroidTextInputCommands} from '../../Components/TextInput/AndroidTextInputNativeComponent';
|
|
import {Commands as iOSTextInputCommands} from '../../Components/TextInput/RCTSingelineTextInputNativeComponent';
|
|
|
|
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
|
type ComponentRef = React.ElementRef<HostComponent<mixed>>;
|
|
|
|
let currentlyFocusedInputRef: ?ComponentRef = null;
|
|
const inputs = new Set();
|
|
|
|
function currentlyFocusedInput(): ?ComponentRef {
|
|
return currentlyFocusedInputRef;
|
|
}
|
|
|
|
/**
|
|
* Returns the ID of the currently focused text field, if one exists
|
|
* If no text field is focused it returns null
|
|
*/
|
|
function currentlyFocusedField(): ?number {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'currentlyFocusedField is deprecated and will be removed in a future release. Use currentlyFocusedInput',
|
|
);
|
|
}
|
|
|
|
return findNodeHandle(currentlyFocusedInputRef);
|
|
}
|
|
|
|
function focusInput(textField: ?ComponentRef): void {
|
|
if (currentlyFocusedInputRef !== textField && textField != null) {
|
|
currentlyFocusedInputRef = textField;
|
|
}
|
|
}
|
|
|
|
function blurInput(textField: ?ComponentRef): void {
|
|
if (currentlyFocusedInputRef === textField && textField != null) {
|
|
currentlyFocusedInputRef = null;
|
|
}
|
|
}
|
|
|
|
function focusField(textFieldID: ?number): void {
|
|
if (__DEV__) {
|
|
console.error('focusField no longer works. Use focusInput');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
function blurField(textFieldID: ?number) {
|
|
if (__DEV__) {
|
|
console.error('blurField no longer works. Use blurInput');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @param {number} TextInputID id of the text field to focus
|
|
* Focuses the specified text field
|
|
* noop if the text field was already focused or if the field is not editable
|
|
*/
|
|
function focusTextInput(textField: ?ComponentRef) {
|
|
if (typeof textField === 'number') {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'focusTextInput must be called with a host component. Passing a react tag is deprecated.',
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (textField != null) {
|
|
const fieldCanBeFocused =
|
|
currentlyFocusedInputRef !== textField &&
|
|
// $FlowFixMe - `currentProps` is missing in `NativeMethods`
|
|
textField.currentProps?.editable !== false;
|
|
|
|
if (!fieldCanBeFocused) {
|
|
return;
|
|
}
|
|
focusInput(textField);
|
|
if (Platform.OS === 'ios') {
|
|
// This isn't necessarily a single line text input
|
|
// But commands don't actually care as long as the thing being passed in
|
|
// actually has a command with that name. So this should work with single
|
|
// and multiline text inputs. Ideally we'll merge them into one component
|
|
// in the future.
|
|
iOSTextInputCommands.focus(textField);
|
|
} else if (Platform.OS === 'android') {
|
|
AndroidTextInputCommands.focus(textField);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} textFieldID id of the text field to unfocus
|
|
* Unfocuses the specified text field
|
|
* noop if it wasn't focused
|
|
*/
|
|
function blurTextInput(textField: ?ComponentRef) {
|
|
if (typeof textField === 'number') {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'blurTextInput must be called with a host component. Passing a react tag is deprecated.',
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (currentlyFocusedInputRef === textField && textField != null) {
|
|
blurInput(textField);
|
|
if (Platform.OS === 'ios') {
|
|
// This isn't necessarily a single line text input
|
|
// But commands don't actually care as long as the thing being passed in
|
|
// actually has a command with that name. So this should work with single
|
|
// and multiline text inputs. Ideally we'll merge them into one component
|
|
// in the future.
|
|
iOSTextInputCommands.blur(textField);
|
|
} else if (Platform.OS === 'android') {
|
|
AndroidTextInputCommands.blur(textField);
|
|
}
|
|
}
|
|
}
|
|
|
|
function registerInput(textField: ComponentRef) {
|
|
if (typeof textField === 'number') {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'registerInput must be called with a host component. Passing a react tag is deprecated.',
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
inputs.add(textField);
|
|
}
|
|
|
|
function unregisterInput(textField: ComponentRef) {
|
|
if (typeof textField === 'number') {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'unregisterInput must be called with a host component. Passing a react tag is deprecated.',
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
inputs.delete(textField);
|
|
}
|
|
|
|
function isTextInput(textField: ComponentRef): boolean {
|
|
if (typeof textField === 'number') {
|
|
if (__DEV__) {
|
|
console.error(
|
|
'isTextInput must be called with a host component. Passing a react tag is deprecated.',
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return inputs.has(textField);
|
|
}
|
|
|
|
module.exports = {
|
|
currentlyFocusedInput,
|
|
focusInput,
|
|
blurInput,
|
|
|
|
currentlyFocusedField,
|
|
focusField,
|
|
blurField,
|
|
focusTextInput,
|
|
blurTextInput,
|
|
registerInput,
|
|
unregisterInput,
|
|
isTextInput,
|
|
};
|