first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,735 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import type {HostComponent} from '../../../src/private/types/HostComponent';
import type {PartialViewConfig} from '../../Renderer/shims/ReactNativeTypes';
import type {ColorValue, TextStyleProp} from '../../StyleSheet/StyleSheet';
import type {
BubblingEventHandler,
DirectEventHandler,
Double,
Float,
Int32,
WithDefault,
} from '../../Types/CodegenTypes';
import type {ViewProps} from '../View/ViewPropTypes';
import type {TextInputNativeCommands} from './TextInputNativeCommands';
import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry';
import codegenNativeCommands from '../../Utilities/codegenNativeCommands';
export type KeyboardType =
// Cross Platform
| 'default'
| 'email-address'
| 'numeric'
| 'phone-pad'
| 'number-pad'
| 'decimal-pad'
| 'url'
// iOS-only
| 'ascii-capable'
| 'numbers-and-punctuation'
| 'name-phone-pad'
| 'twitter'
| 'web-search'
// Android-only
| 'visible-password';
export type ReturnKeyType =
// Cross Platform
| 'done'
| 'go'
| 'next'
| 'search'
| 'send'
// Android-only
| 'none'
| 'previous'
// iOS-only
| 'default'
| 'emergency-call'
| 'google'
| 'join'
| 'route'
| 'yahoo';
export type SubmitBehavior = 'submit' | 'blurAndSubmit' | 'newline';
export type AndroidTextInputNativeProps = $ReadOnly<{
// This allows us to inherit everything from ViewProps except for style (see below)
// This must be commented for Fabric codegen to work.
...Omit<ViewProps, 'style'>,
/**
* Android props after this
*/
/**
* Specifies autocomplete hints for the system, so it can provide autofill. On Android, the system will always attempt to offer autofill by using heuristics to identify the type of content.
* To disable autocomplete, set `autoComplete` to `off`.
*
* *Android Only*
*
* Possible values for `autoComplete` are:
*
* - `birthdate-day`
* - `birthdate-full`
* - `birthdate-month`
* - `birthdate-year`
* - `cc-csc`
* - `cc-exp`
* - `cc-exp-day`
* - `cc-exp-month`
* - `cc-exp-year`
* - `cc-number`
* - `email`
* - `gender`
* - `name`
* - `name-family`
* - `name-given`
* - `name-middle`
* - `name-middle-initial`
* - `name-prefix`
* - `name-suffix`
* - `password`
* - `password-new`
* - `postal-address`
* - `postal-address-country`
* - `postal-address-extended`
* - `postal-address-extended-postal-code`
* - `postal-address-locality`
* - `postal-address-region`
* - `postal-code`
* - `street-address`
* - `sms-otp`
* - `tel`
* - `tel-country-code`
* - `tel-national`
* - `tel-device`
* - `username`
* - `username-new`
* - `off`
*
* @platform android
*/
autoComplete?: WithDefault<
| 'birthdate-day'
| 'birthdate-full'
| 'birthdate-month'
| 'birthdate-year'
| 'cc-csc'
| 'cc-exp'
| 'cc-exp-day'
| 'cc-exp-month'
| 'cc-exp-year'
| 'cc-number'
| 'email'
| 'gender'
| 'name'
| 'name-family'
| 'name-given'
| 'name-middle'
| 'name-middle-initial'
| 'name-prefix'
| 'name-suffix'
| 'password'
| 'password-new'
| 'postal-address'
| 'postal-address-country'
| 'postal-address-extended'
| 'postal-address-extended-postal-code'
| 'postal-address-locality'
| 'postal-address-region'
| 'postal-code'
| 'street-address'
| 'sms-otp'
| 'tel'
| 'tel-country-code'
| 'tel-national'
| 'tel-device'
| 'username'
| 'username-new'
| 'off',
'off',
>,
/**
* Sets the return key to the label. Use it instead of `returnKeyType`.
* @platform android
*/
returnKeyLabel?: ?string,
/**
* Sets the number of lines for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
* @platform android
*/
numberOfLines?: ?Int32,
/**
* When `false`, if there is a small amount of space available around a text input
* (e.g. landscape orientation on a phone), the OS may choose to have the user edit
* the text inside of a full screen text input mode. When `true`, this feature is
* disabled and users will always edit the text directly inside of the text input.
* Defaults to `false`.
* @platform android
*/
disableFullscreenUI?: ?boolean,
/**
* Set text break strategy on Android API Level 23+, possible values are `simple`, `highQuality`, `balanced`
* The default value is `simple`.
* @platform android
*/
textBreakStrategy?: WithDefault<
'simple' | 'highQuality' | 'balanced',
'simple',
>,
/**
* The color of the `TextInput` underline.
* @platform android
*/
underlineColorAndroid?: ?ColorValue,
/**
* If defined, the provided image resource will be rendered on the left.
* The image resource must be inside `/android/app/src/main/res/drawable` and referenced
* like
* ```
* <TextInput
* inlineImageLeft='search_icon'
* />
* ```
* @platform android
*/
inlineImageLeft?: ?string,
/**
* Padding between the inline image, if any, and the text input itself.
* @platform android
*/
inlineImagePadding?: ?Int32,
importantForAutofill?: string /*?(
| 'auto'
| 'no'
| 'noExcludeDescendants'
| 'yes'
| 'yesExcludeDescendants'
),*/,
/**
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
* Defaults to `true`.
*/
showSoftInputOnFocus?: ?boolean,
/**
* TextInput props after this
*/
/**
* Can tell `TextInput` to automatically capitalize certain characters.
*
* - `characters`: all characters.
* - `words`: first letter of each word.
* - `sentences`: first letter of each sentence (*default*).
* - `none`: don't auto capitalize anything.
*/
autoCapitalize?: WithDefault<
'none' | 'sentences' | 'words' | 'characters',
'none',
>,
/**
* If `false`, disables auto-correct. The default value is `true`.
*/
autoCorrect?: ?boolean,
/**
* If `true`, focuses the input on `componentDidMount`.
* The default value is `false`.
*/
autoFocus?: ?boolean,
/**
* Specifies whether fonts should scale to respect Text Size accessibility settings. The
* default is `true`.
*/
allowFontScaling?: ?boolean,
/**
* Specifies largest possible scale a font can reach when `allowFontScaling` is enabled.
* Possible values:
* `null/undefined` (default): inherit from the parent node or the global default (0)
* `0`: no max, ignore parent/global default
* `>= 1`: sets the maxFontSizeMultiplier of this node to this value
*/
maxFontSizeMultiplier?: ?Float,
/**
* If `false`, text is not editable. The default value is `true`.
*/
editable?: ?boolean,
/**
* Determines which keyboard to open, e.g.`numeric`.
*
* The following values work across platforms:
*
* - `default`
* - `numeric`
* - `number-pad`
* - `decimal-pad`
* - `email-address`
* - `phone-pad`
* - `url`
*
* *Android Only*
*
* The following values work on Android only:
*
* - `visible-password`
*/
keyboardType?: WithDefault<KeyboardType, 'default'>,
/**
* Determines how the return key should look. On Android you can also use
* `returnKeyLabel`.
*
* *Cross platform*
*
* The following values work across platforms:
*
* - `done`
* - `go`
* - `next`
* - `search`
* - `send`
*
* *Android Only*
*
* The following values work on Android only:
*
* - `none`
* - `previous`
*/
returnKeyType?: WithDefault<ReturnKeyType, 'done'>,
/**
* Limits the maximum number of characters that can be entered. Use this
* instead of implementing the logic in JS to avoid flicker.
*/
maxLength?: ?Int32,
/**
* If `true`, the text input can be multiple lines.
* The default value is `false`.
*/
multiline?: ?boolean,
/**
* Callback that is called when the text input is blurred.
* `target` is the reactTag of the element
*/
onBlur?: ?BubblingEventHandler<$ReadOnly<{target: Int32}>>,
/**
* Callback that is called when the text input is focused.
* `target` is the reactTag of the element
*/
onFocus?: ?BubblingEventHandler<$ReadOnly<{target: Int32}>>,
/**
* Callback that is called when the text input's text changes.
* `target` is the reactTag of the element
* TODO: differentiate between onChange and onChangeText
*/
onChange?: ?BubblingEventHandler<
$ReadOnly<{target: Int32, eventCount: Int32, text: string}>,
>,
/**
* Callback that is called when the text input's text changes.
* Changed text is passed as an argument to the callback handler.
* TODO: differentiate between onChange and onChangeText
*/
onChangeText?: ?BubblingEventHandler<
$ReadOnly<{target: Int32, eventCount: Int32, text: string}>,
>,
/**
* Callback that is called when the text input's content size changes.
* This will be called with
* `{ nativeEvent: { contentSize: { width, height } } }`.
*
* Only called for multiline text inputs.
*/
onContentSizeChange?: ?DirectEventHandler<
$ReadOnly<{
target: Int32,
contentSize: $ReadOnly<{width: Double, height: Double}>,
}>,
>,
/**
* Callback that is called when text input ends.
*/
onEndEditing?: ?BubblingEventHandler<
$ReadOnly<{target: Int32, text: string}>,
>,
/**
* Callback that is called when the text input selection is changed.
* This will be called with
* `{ nativeEvent: { selection: { start, end } } }`.
*/
onSelectionChange?: ?DirectEventHandler<
$ReadOnly<{
target: Int32,
selection: $ReadOnly<{start: Double, end: Double}>,
}>,
>,
/**
* Callback that is called when the text input's submit button is pressed.
* Invalid if `multiline={true}` is specified.
*/
onSubmitEditing?: ?BubblingEventHandler<
$ReadOnly<{target: Int32, text: string}>,
>,
/**
* Callback that is called when a key is pressed.
* This will be called with `{ nativeEvent: { key: keyValue } }`
* where `keyValue` is `'Enter'` or `'Backspace'` for respective keys and
* the typed-in character otherwise including `' '` for space.
* Fires before `onChange` callbacks.
*/
onKeyPress?: ?BubblingEventHandler<$ReadOnly<{target: Int32, key: string}>>,
/**
* Invoked on content scroll with `{ nativeEvent: { contentOffset: { x, y } } }`.
* May also contain other properties from ScrollEvent but on Android contentSize
* is not provided for performance reasons.
*/
onScroll?: ?DirectEventHandler<
$ReadOnly<{
target: Int32,
responderIgnoreScroll: boolean,
contentInset: $ReadOnly<{
top: Double, // always 0 on Android
bottom: Double, // always 0 on Android
left: Double, // always 0 on Android
right: Double, // always 0 on Android
}>,
contentOffset: $ReadOnly<{
x: Double,
y: Double,
}>,
contentSize: $ReadOnly<{
width: Double, // always 0 on Android
height: Double, // always 0 on Android
}>,
layoutMeasurement: $ReadOnly<{
width: Double,
height: Double,
}>,
velocity: $ReadOnly<{
x: Double, // always 0 on Android
y: Double, // always 0 on Android
}>,
}>,
>,
/**
* The string that will be rendered before text input has been entered.
*/
placeholder?: ?Stringish,
/**
* The text color of the placeholder string.
*/
placeholderTextColor?: ?ColorValue,
/**
* If `true`, the text input obscures the text entered so that sensitive text
* like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'.
*/
secureTextEntry?: ?boolean,
/**
* The highlight and cursor color of the text input.
*/
selectionColor?: ?ColorValue,
/**
* The text selection handle color.
*/
selectionHandleColor?: ?ColorValue,
/**
* The start and end of the text input's selection. Set start and end to
* the same value to position the cursor.
*/
selection?: ?$ReadOnly<{
start: Int32,
end?: ?Int32,
}>,
/**
* The value to show for the text input. `TextInput` is a controlled
* component, which means the native value will be forced to match this
* value prop if provided. For most uses, this works great, but in some
* cases this may cause flickering - one common cause is preventing edits
* by keeping value the same. In addition to simply setting the same value,
* either set `editable={false}`, or set/update `maxLength` to prevent
* unwanted edits without flicker.
*/
value?: ?string,
/**
* Provides an initial value that will change when the user starts typing.
* Useful for simple use-cases where you do not want to deal with listening
* to events and updating the value prop to keep the controlled state in sync.
*/
defaultValue?: ?string,
/**
* If `true`, all text will automatically be selected on focus.
*/
selectTextOnFocus?: ?boolean,
/**
* If `true`, the text field will blur when submitted.
* The default value is true for single-line fields and false for
* multiline fields. Note that for multiline fields, setting `blurOnSubmit`
* to `true` means that pressing return will blur the field and trigger the
* `onSubmitEditing` event instead of inserting a newline into the field.
*
* @deprecated
* Note that `submitBehavior` now takes the place of `blurOnSubmit` and will
* override any behavior defined by `blurOnSubmit`.
* @see submitBehavior
*/
blurOnSubmit?: ?boolean,
/**
* When the return key is pressed,
*
* For single line inputs:
*
* - `'newline`' defaults to `'blurAndSubmit'`
* - `undefined` defaults to `'blurAndSubmit'`
*
* For multiline inputs:
*
* - `'newline'` adds a newline
* - `undefined` defaults to `'newline'`
*
* For both single line and multiline inputs:
*
* - `'submit'` will only send a submit event and not blur the input
* - `'blurAndSubmit`' will both blur the input and send a submit event
*/
submitBehavior?: ?SubmitBehavior,
/**
* Note that not all Text styles are supported, an incomplete list of what is not supported includes:
*
* - `borderLeftWidth`
* - `borderTopWidth`
* - `borderRightWidth`
* - `borderBottomWidth`
* - `borderTopLeftRadius`
* - `borderTopRightRadius`
* - `borderBottomRightRadius`
* - `borderBottomLeftRadius`
*
* see [Issue#7070](https://github.com/facebook/react-native/issues/7070)
* for more detail.
*
* [Styles](docs/style.html)
*/
// TODO: figure out what to do with this style prop for codegen/Fabric purposes
// This must be commented for Fabric codegen to work; it's currently not possible
// to override the default View style prop in codegen.
style?: ?TextStyleProp,
/**
* If `true`, caret is hidden. The default value is `false`.
* This property is supported only for single-line TextInput component on iOS.
*/
caretHidden?: ?boolean,
/*
* If `true`, contextMenuHidden is hidden. The default value is `false`.
*/
contextMenuHidden?: ?boolean,
/**
* The following are props that `BaseTextShadowNode` takes. It is unclear if they
* are used by TextInput.
*/
textShadowColor?: ?ColorValue,
textShadowRadius?: ?Float,
textDecorationLine?: ?string,
fontStyle?: ?string,
textShadowOffset?: ?$ReadOnly<{width?: ?Double, height?: ?Double}>,
lineHeight?: ?Float,
textTransform?: ?string,
color?: ?Int32,
letterSpacing?: ?Float,
fontSize?: ?Float,
textAlign?: ?string,
includeFontPadding?: ?boolean,
fontWeight?: ?string,
fontFamily?: ?string,
/**
* I cannot find where these are defined but JS complains without them.
*/
textAlignVertical?: ?string,
cursorColor?: ?ColorValue,
/**
* "Private" fields used by TextInput.js and not users of this component directly
*/
mostRecentEventCount: Int32,
text?: ?string,
}>;
type NativeType = HostComponent<AndroidTextInputNativeProps>;
type NativeCommands = TextInputNativeCommands<NativeType>;
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['focus', 'blur', 'setTextAndSelection'],
});
export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
uiViewClassName: 'AndroidTextInput',
bubblingEventTypes: {
topEndEditing: {
phasedRegistrationNames: {
bubbled: 'onEndEditing',
captured: 'onEndEditingCapture',
},
},
topKeyPress: {
phasedRegistrationNames: {
bubbled: 'onKeyPress',
captured: 'onKeyPressCapture',
},
},
topSubmitEditing: {
phasedRegistrationNames: {
bubbled: 'onSubmitEditing',
captured: 'onSubmitEditingCapture',
},
},
},
directEventTypes: {
topScroll: {
registrationName: 'onScroll',
},
},
validAttributes: {
acceptDragAndDropTypes: true,
maxFontSizeMultiplier: true,
adjustsFontSizeToFit: true,
minimumFontScale: true,
autoFocus: true,
placeholder: true,
inlineImagePadding: true,
contextMenuHidden: true,
textShadowColor: {
process: require('../../StyleSheet/processColor').default,
},
maxLength: true,
selectTextOnFocus: true,
textShadowRadius: true,
underlineColorAndroid: {
process: require('../../StyleSheet/processColor').default,
},
textDecorationLine: true,
submitBehavior: true,
textAlignVertical: true,
fontStyle: true,
textShadowOffset: true,
selectionColor: {process: require('../../StyleSheet/processColor').default},
selectionHandleColor: {
process: require('../../StyleSheet/processColor').default,
},
placeholderTextColor: {
process: require('../../StyleSheet/processColor').default,
},
importantForAutofill: true,
lineHeight: true,
textTransform: true,
returnKeyType: true,
keyboardType: true,
multiline: true,
color: {process: require('../../StyleSheet/processColor').default},
autoComplete: true,
numberOfLines: true,
letterSpacing: true,
returnKeyLabel: true,
fontSize: true,
onKeyPress: true,
cursorColor: {process: require('../../StyleSheet/processColor').default},
text: true,
showSoftInputOnFocus: true,
textAlign: true,
autoCapitalize: true,
autoCorrect: true,
caretHidden: true,
secureTextEntry: true,
textBreakStrategy: true,
onScroll: true,
onContentSizeChange: true,
disableFullscreenUI: true,
includeFontPadding: true,
fontWeight: true,
fontFamily: true,
allowFontScaling: true,
onSelectionChange: true,
mostRecentEventCount: true,
inlineImageLeft: true,
editable: true,
fontVariant: true,
borderBottomRightRadius: true,
borderBottomColor: {
process: require('../../StyleSheet/processColor').default,
},
borderRadius: true,
borderRightColor: {
process: require('../../StyleSheet/processColor').default,
},
borderColor: {process: require('../../StyleSheet/processColor').default},
borderTopRightRadius: true,
borderStyle: true,
borderBottomLeftRadius: true,
borderLeftColor: {
process: require('../../StyleSheet/processColor').default,
},
borderTopLeftRadius: true,
borderTopColor: {process: require('../../StyleSheet/processColor').default},
},
};
let AndroidTextInputNativeComponent =
NativeComponentRegistry.get<AndroidTextInputNativeProps>(
'AndroidTextInput',
() => __INTERNAL_VIEW_CONFIG,
);
// flowlint-next-line unclear-type:off
export default ((AndroidTextInputNativeComponent: any): HostComponent<AndroidTextInputNativeProps>);

View File

@@ -0,0 +1,34 @@
/**
* 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
*/
import type * as React from 'react';
import {ColorValue, StyleProp} from '../../StyleSheet/StyleSheet';
import {ViewStyle} from '../../StyleSheet/StyleSheetTypes';
/**
* A component which enables customization of the keyboard input accessory view on iOS. The input accessory view is
* displayed above the keyboard whenever a TextInput has focus. This component can be used to create custom toolbars.
*
* To use this component wrap your custom toolbar with the InputAccessoryView component, and set a nativeID. Then, pass
* that nativeID as the inputAccessoryViewID of whatever TextInput you desire.
*/
export class InputAccessoryView extends React.Component<InputAccessoryViewProps> {}
export interface InputAccessoryViewProps {
backgroundColor?: ColorValue | undefined;
children?: React.ReactNode | undefined;
/**
* An ID which is used to associate this InputAccessoryView to specified TextInput(s).
*/
nativeID?: string | undefined;
style?: StyleProp<ViewStyle> | undefined;
}

View File

@@ -0,0 +1,125 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
import StyleSheet, {
type ColorValue,
type ViewStyleProp,
} from '../../StyleSheet/StyleSheet';
import Platform from '../../Utilities/Platform';
import useWindowDimensions from '../../Utilities/useWindowDimensions';
import RCTInputAccessoryViewNativeComponent from './RCTInputAccessoryViewNativeComponent';
import * as React from 'react';
/**
* Note: iOS only
*
* A component which enables customization of the keyboard input accessory view.
* The input accessory view is displayed above the keyboard whenever a TextInput
* has focus. This component can be used to create custom toolbars.
*
* To use this component wrap your custom toolbar with the
* InputAccessoryView component, and set a nativeID. Then, pass that nativeID
* as the inputAccessoryViewID of whatever TextInput you desire. A simple
* example:
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react';
* import { AppRegistry, TextInput, InputAccessoryView, Button } from 'react-native';
*
* export default class UselessTextInput extends Component {
* constructor(props) {
* super(props);
* this.state = {text: 'Placeholder Text'};
* }
*
* render() {
* const inputAccessoryViewID = "uniqueID";
* return (
* <View>
* <ScrollView keyboardDismissMode="interactive">
* <TextInput
* style={{
* padding: 10,
* paddingTop: 50,
* }}
* inputAccessoryViewID=inputAccessoryViewID
* onChangeText={text => this.setState({text})}
* value={this.state.text}
* />
* </ScrollView>
* <InputAccessoryView nativeID=inputAccessoryViewID>
* <Button
* onPress={() => this.setState({text: 'Placeholder Text'})}
* title="Reset Text"
* />
* </InputAccessoryView>
* </View>
* );
* }
* }
*
* // skip this line if using Create React Native App
* AppRegistry.registerComponent('AwesomeProject', () => UselessTextInput);
* ```
*
* This component can also be used to create sticky text inputs (text inputs
* which are anchored to the top of the keyboard). To do this, wrap a
* TextInput with the InputAccessoryView component, and don't set a nativeID.
* For an example, look at InputAccessoryViewExample.js in RNTester.
*/
export type InputAccessoryViewProps = $ReadOnly<{
+children: React.Node,
/**
* An ID which is used to associate this `InputAccessoryView` to
* specified TextInput(s).
*/
nativeID?: ?string,
style?: ?ViewStyleProp,
backgroundColor?: ?ColorValue,
}>;
const InputAccessoryView: React.ComponentType<InputAccessoryViewProps> = (
props: InputAccessoryViewProps,
) => {
const {width} = useWindowDimensions();
if (Platform.OS === 'ios') {
if (React.Children.count(props.children) === 0) {
return null;
}
return (
<RCTInputAccessoryViewNativeComponent
style={[props.style, styles.container]}
nativeID={props.nativeID}
backgroundColor={props.backgroundColor}>
<SafeAreaView style={[styles.safeAreaView, {width}]}>
{props.children}
</SafeAreaView>
</RCTInputAccessoryViewNativeComponent>
);
} else {
console.warn('<InputAccessoryView> is only supported on iOS.');
return null;
}
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
},
safeAreaView: {
flex: 1,
},
});
export default InputAccessoryView;

View File

@@ -0,0 +1,12 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
export * from '../../../src/private/specs_DEPRECATED/components/RCTInputAccessoryViewNativeComponent';
export {default} from '../../../src/private/specs_DEPRECATED/components/RCTInputAccessoryViewNativeComponent';

View File

@@ -0,0 +1,43 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import type {HostComponent} from '../../../src/private/types/HostComponent';
import type {PartialViewConfig} from '../../Renderer/shims/ReactNativeTypes';
import type {TextInputNativeCommands} from './TextInputNativeCommands';
import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry';
import codegenNativeCommands from '../../Utilities/codegenNativeCommands';
import RCTTextInputViewConfig from './RCTTextInputViewConfig';
type NativeType = HostComponent<{...}>;
type NativeCommands = TextInputNativeCommands<NativeType>;
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['focus', 'blur', 'setTextAndSelection'],
});
export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
uiViewClassName: 'RCTMultilineTextInputView',
...RCTTextInputViewConfig,
validAttributes: {
...RCTTextInputViewConfig.validAttributes,
dataDetectorTypes: true,
},
};
const MultilineTextInputNativeComponent: HostComponent<{...}> =
NativeComponentRegistry.get<{...}>(
'RCTMultilineTextInputView',
() => __INTERNAL_VIEW_CONFIG,
);
// flowlint-next-line unclear-type:off
export default ((MultilineTextInputNativeComponent: any): HostComponent<{...}>);

View File

@@ -0,0 +1,41 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import type {HostComponent} from '../../../src/private/types/HostComponent';
import type {PartialViewConfig} from '../../Renderer/shims/ReactNativeTypes';
import type {TextInputNativeCommands} from './TextInputNativeCommands';
import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry';
import codegenNativeCommands from '../../Utilities/codegenNativeCommands';
import RCTTextInputViewConfig from './RCTTextInputViewConfig';
type NativeType = HostComponent<{...}>;
type NativeCommands = TextInputNativeCommands<NativeType>;
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['focus', 'blur', 'setTextAndSelection'],
});
export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
uiViewClassName: 'RCTSinglelineTextInputView',
...RCTTextInputViewConfig,
};
const SinglelineTextInputNativeComponent: HostComponent<{...}> =
NativeComponentRegistry.get<{...}>(
'RCTSinglelineTextInputView',
() => __INTERNAL_VIEW_CONFIG,
);
// flowlint-next-line unclear-type:off
export default ((SinglelineTextInputNativeComponent: any): HostComponent<{
...
}>);

View File

@@ -0,0 +1,170 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import type {PartialViewConfig} from '../../Renderer/shims/ReactNativeTypes';
import {ConditionallyIgnoredEventHandlers} from '../../NativeComponent/ViewConfigIgnore';
type PartialViewConfigWithoutName = Omit<PartialViewConfig, 'uiViewClassName'>;
const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
bubblingEventTypes: {
topBlur: {
phasedRegistrationNames: {
bubbled: 'onBlur',
captured: 'onBlurCapture',
},
},
topChange: {
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture',
},
},
topEndEditing: {
phasedRegistrationNames: {
bubbled: 'onEndEditing',
captured: 'onEndEditingCapture',
},
},
topFocus: {
phasedRegistrationNames: {
bubbled: 'onFocus',
captured: 'onFocusCapture',
},
},
topKeyPress: {
phasedRegistrationNames: {
bubbled: 'onKeyPress',
captured: 'onKeyPressCapture',
},
},
topSubmitEditing: {
phasedRegistrationNames: {
bubbled: 'onSubmitEditing',
captured: 'onSubmitEditingCapture',
},
},
topTouchCancel: {
phasedRegistrationNames: {
bubbled: 'onTouchCancel',
captured: 'onTouchCancelCapture',
},
},
topTouchEnd: {
phasedRegistrationNames: {
bubbled: 'onTouchEnd',
captured: 'onTouchEndCapture',
},
},
topTouchMove: {
phasedRegistrationNames: {
bubbled: 'onTouchMove',
captured: 'onTouchMoveCapture',
},
},
},
directEventTypes: {
topScroll: {
registrationName: 'onScroll',
},
topSelectionChange: {
registrationName: 'onSelectionChange',
},
topContentSizeChange: {
registrationName: 'onContentSizeChange',
},
topChangeSync: {
registrationName: 'onChangeSync',
},
topKeyPressSync: {
registrationName: 'onKeyPressSync',
},
},
validAttributes: {
acceptDragAndDropTypes: true,
dynamicTypeRamp: true,
fontSize: true,
fontWeight: true,
fontVariant: true,
// flowlint-next-line untyped-import:off
textShadowOffset: {
diff: require('../../Utilities/differ/sizesDiffer').default,
},
allowFontScaling: true,
fontStyle: true,
textTransform: true,
textAlign: true,
fontFamily: true,
lineHeight: true,
isHighlighted: true,
writingDirection: true,
textDecorationLine: true,
textShadowRadius: true,
letterSpacing: true,
textDecorationStyle: true,
textDecorationColor: {
process: require('../../StyleSheet/processColor').default,
},
color: {process: require('../../StyleSheet/processColor').default},
maxFontSizeMultiplier: true,
textShadowColor: {
process: require('../../StyleSheet/processColor').default,
},
editable: true,
inputAccessoryViewID: true,
inputAccessoryViewButtonLabel: true,
caretHidden: true,
enablesReturnKeyAutomatically: true,
placeholderTextColor: {
process: require('../../StyleSheet/processColor').default,
},
clearButtonMode: true,
keyboardType: true,
selection: true,
returnKeyType: true,
submitBehavior: true,
mostRecentEventCount: true,
scrollEnabled: true,
selectionColor: {process: require('../../StyleSheet/processColor').default},
contextMenuHidden: true,
secureTextEntry: true,
placeholder: true,
autoCorrect: true,
multiline: true,
numberOfLines: true,
textContentType: true,
maxLength: true,
autoCapitalize: true,
keyboardAppearance: true,
passwordRules: true,
spellCheck: true,
selectTextOnFocus: true,
text: true,
clearTextOnFocus: true,
showSoftInputOnFocus: true,
autoFocus: true,
lineBreakStrategyIOS: true,
lineBreakModeIOS: true,
smartInsertDelete: true,
...ConditionallyIgnoredEventHandlers({
onChange: true,
onSelectionChange: true,
onContentSizeChange: true,
onScroll: true,
onChangeSync: true,
onKeyPressSync: true,
}),
disableKeyboardShortcuts: true,
},
};
export default RCTTextInputViewConfig as PartialViewConfigWithoutName;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,990 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import type {HostInstance} from '../../../src/private/types/HostInstance';
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
import type {
BlurEvent,
FocusEvent,
GestureResponderEvent,
ScrollEvent,
} from '../../Types/CoreEventTypes';
import type {
AutoCapitalize,
EnterKeyHintType,
EnterKeyHintTypeAndroid,
EnterKeyHintTypeIOS,
EnterKeyHintTypeOptions,
InputModeOptions,
KeyboardType,
KeyboardTypeAndroid,
KeyboardTypeIOS,
KeyboardTypeOptions,
ReturnKeyType,
ReturnKeyTypeAndroid,
ReturnKeyTypeIOS,
ReturnKeyTypeOptions,
Selection,
SubmitBehavior,
TextContentType,
TextInputAndroidProps,
TextInputBlurEvent,
TextInputChangeEvent,
TextInputContentSizeChangeEvent,
TextInputEditingEvent,
TextInputEndEditingEvent,
TextInputEvent,
TextInputFocusEvent,
TextInputInstance,
TextInputIOSProps,
TextInputKeyPressEvent,
TextInputProps,
TextInputSelectionChangeEvent,
TextInputSubmitEditingEvent,
TextInputType,
} from './TextInput.flow';
import usePressability from '../../Pressability/usePressability';
import flattenStyle from '../../StyleSheet/flattenStyle';
import StyleSheet, {type TextStyleProp} from '../../StyleSheet/StyleSheet';
import Text from '../../Text/Text';
import TextAncestorContext from '../../Text/TextAncestorContext';
import Platform from '../../Utilities/Platform';
import useMergeRefs from '../../Utilities/useMergeRefs';
import TextInputState from './TextInputState';
import invariant from 'invariant';
import nullthrows from 'nullthrows';
import * as React from 'react';
import {useCallback, useLayoutEffect, useMemo, useRef, useState} from 'react';
let AndroidTextInput;
let AndroidTextInputCommands;
let RCTSinglelineTextInputView;
let RCTSinglelineTextInputNativeCommands;
let RCTMultilineTextInputView;
let RCTMultilineTextInputNativeCommands;
if (Platform.OS === 'android') {
AndroidTextInput = require('./AndroidTextInputNativeComponent').default;
AndroidTextInputCommands =
require('./AndroidTextInputNativeComponent').Commands;
} else if (Platform.OS === 'ios') {
RCTSinglelineTextInputView =
require('./RCTSingelineTextInputNativeComponent').default;
RCTSinglelineTextInputNativeCommands =
require('./RCTSingelineTextInputNativeComponent').Commands;
RCTMultilineTextInputView =
require('./RCTMultilineTextInputNativeComponent').default;
RCTMultilineTextInputNativeCommands =
require('./RCTMultilineTextInputNativeComponent').Commands;
}
export type {
AutoCapitalize,
BlurEvent,
EnterKeyHintType,
EnterKeyHintTypeAndroid,
EnterKeyHintTypeIOS,
EnterKeyHintTypeOptions,
FocusEvent,
InputModeOptions,
KeyboardType,
KeyboardTypeAndroid,
KeyboardTypeIOS,
KeyboardTypeOptions,
ReturnKeyType,
ReturnKeyTypeAndroid,
ReturnKeyTypeIOS,
ReturnKeyTypeOptions,
SubmitBehavior,
TextContentType,
TextInputAndroidProps,
TextInputBlurEvent,
TextInputChangeEvent,
TextInputContentSizeChangeEvent,
TextInputEditingEvent,
TextInputEndEditingEvent,
TextInputEvent,
TextInputFocusEvent,
TextInputIOSProps,
TextInputKeyPressEvent,
TextInputProps,
TextInputSelectionChangeEvent,
TextInputSubmitEditingEvent,
};
type TextInputStateType = $ReadOnly<{
/**
* @deprecated Use currentlyFocusedInput
* Returns the ID of the currently focused text field, if one exists
* If no text field is focused it returns null
*/
currentlyFocusedField: () => ?number,
/**
* Returns the ref of the currently focused text field, if one exists
* If no text field is focused it returns null
*/
currentlyFocusedInput: () => ?HostInstance,
/**
* @param textField ref of the text field to focus
* Focuses the specified text field
* noop if the text field was already focused
*/
focusTextInput: (textField: ?HostInstance) => void,
/**
* @param textField ref of the text field to focus
* Unfocuses the specified text field
* noop if it wasn't focused
*/
blurTextInput: (textField: ?HostInstance) => void,
}>;
type ViewCommands = $NonMaybeType<
| typeof AndroidTextInputCommands
| typeof RCTMultilineTextInputNativeCommands
| typeof RCTSinglelineTextInputNativeCommands,
>;
type LastNativeSelection = {
selection: Selection,
mostRecentEventCount: number,
};
const emptyFunctionThatReturnsTrue = () => true;
/**
* This hook handles the synchronization between the state of the text input
* in native and in JavaScript. This is necessary due to the asynchronous nature
* of text input events.
*/
function useTextInputStateSynchronization({
props,
mostRecentEventCount,
selection,
inputRef,
text,
viewCommands,
}: {
props: TextInputProps,
mostRecentEventCount: number,
selection: ?Selection,
inputRef: React.RefObject<null | TextInputInstance>,
text?: string,
viewCommands: ViewCommands,
}): {
setLastNativeText: string => void,
setLastNativeSelection: LastNativeSelection => void,
} {
const [lastNativeText, setLastNativeText] = useState<?Stringish>(props.value);
const [lastNativeSelectionState, setLastNativeSelection] =
useState<LastNativeSelection>({
selection: {start: -1, end: -1},
mostRecentEventCount: mostRecentEventCount,
});
const lastNativeSelection = lastNativeSelectionState.selection;
// This is necessary in case native updates the text and JS decides
// that the update should be ignored and we should stick with the value
// that we have in JS.
useLayoutEffect(() => {
const nativeUpdate: {text?: string, selection?: Selection} = {};
if (lastNativeText !== props.value && typeof props.value === 'string') {
nativeUpdate.text = props.value;
setLastNativeText(props.value);
}
if (
selection &&
lastNativeSelection &&
(lastNativeSelection.start !== selection.start ||
lastNativeSelection.end !== selection.end)
) {
nativeUpdate.selection = selection;
setLastNativeSelection({selection, mostRecentEventCount});
}
if (Object.keys(nativeUpdate).length === 0) {
return;
}
if (inputRef.current != null) {
viewCommands.setTextAndSelection(
inputRef.current,
mostRecentEventCount,
text,
selection?.start ?? -1,
selection?.end ?? -1,
);
}
}, [
mostRecentEventCount,
inputRef,
props.value,
props.defaultValue,
lastNativeText,
selection,
lastNativeSelection,
text,
viewCommands,
]);
return {setLastNativeText, setLastNativeSelection};
}
/**
* A foundational component for inputting text into the app via a
* keyboard. Props provide configurability for several features, such as
* auto-correction, auto-capitalization, placeholder text, and different keyboard
* types, such as a numeric keypad.
*
* The simplest use case is to plop down a `TextInput` and subscribe to the
* `onChangeText` events to read the user input. There are also other events,
* such as `onSubmitEditing` and `onFocus` that can be subscribed to. A simple
* example:
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react';
* import { AppRegistry, TextInput } from 'react-native';
*
* export default class UselessTextInput extends Component {
* constructor(props) {
* super(props);
* this.state = { text: 'Useless Placeholder' };
* }
*
* render() {
* return (
* <TextInput
* style={{height: 40, borderColor: 'gray', borderWidth: 1}}
* onChangeText={(text) => this.setState({text})}
* value={this.state.text}
* />
* );
* }
* }
*
* // skip this line if using Create React Native App
* AppRegistry.registerComponent('AwesomeProject', () => UselessTextInput);
* ```
*
* Two methods exposed via the native element are .focus() and .blur() that
* will focus or blur the TextInput programmatically.
*
* Note that some props are only available with `multiline={true/false}`.
* Additionally, border styles that apply to only one side of the element
* (e.g., `borderBottomColor`, `borderLeftWidth`, etc.) will not be applied if
* `multiline=false`. To achieve the same effect, you can wrap your `TextInput`
* in a `View`:
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react';
* import { AppRegistry, View, TextInput } from 'react-native';
*
* class UselessTextInput extends Component {
* render() {
* return (
* <TextInput
* {...this.props} // Inherit any props passed to it; e.g., multiline, numberOfLines below
* editable={true}
* maxLength={40}
* />
* );
* }
* }
*
* export default class UselessTextInputMultiline extends Component {
* constructor(props) {
* super(props);
* this.state = {
* text: 'Useless Multiline Placeholder',
* };
* }
*
* // If you type something in the text box that is a color, the background will change to that
* // color.
* render() {
* return (
* <View style={{
* backgroundColor: this.state.text,
* borderBottomColor: '#000000',
* borderBottomWidth: 1 }}
* >
* <UselessTextInput
* multiline={true}
* numberOfLines={4}
* onChangeText={(text) => this.setState({text})}
* value={this.state.text}
* />
* </View>
* );
* }
* }
*
* // skip these lines if using Create React Native App
* AppRegistry.registerComponent(
* 'AwesomeProject',
* () => UselessTextInputMultiline
* );
* ```
*
* `TextInput` has by default a border at the bottom of its view. This border
* has its padding set by the background image provided by the system, and it
* cannot be changed. Solutions to avoid this is to either not set height
* explicitly, case in which the system will take care of displaying the border
* in the correct position, or to not display the border by setting
* `underlineColorAndroid` to transparent.
*
* Note that on Android performing text selection in input can change
* app's activity `windowSoftInputMode` param to `adjustResize`.
* This may cause issues with components that have position: 'absolute'
* while keyboard is active. To avoid this behavior either specify `windowSoftInputMode`
* in AndroidManifest.xml ( https://developer.android.com/guide/topics/manifest/activity-element.html )
* or control this param programmatically with native code.
*
*/
function InternalTextInput(props: TextInputProps): React.Node {
const {
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-selected': ariaSelected,
accessibilityState,
id,
tabIndex,
selection: propsSelection,
selectionColor,
selectionHandleColor,
cursorColor,
...otherProps
} = props;
const inputRef = useRef<null | TextInputInstance>(null);
const selection: ?Selection =
propsSelection == null
? null
: {
start: propsSelection.start,
end: propsSelection.end ?? propsSelection.start,
};
const text =
typeof props.value === 'string'
? props.value
: typeof props.defaultValue === 'string'
? props.defaultValue
: undefined;
const viewCommands =
AndroidTextInputCommands ||
(props.multiline === true
? RCTMultilineTextInputNativeCommands
: RCTSinglelineTextInputNativeCommands);
const [mostRecentEventCount, setMostRecentEventCount] = useState<number>(0);
const {setLastNativeText, setLastNativeSelection} =
useTextInputStateSynchronization({
props,
inputRef,
mostRecentEventCount,
selection,
text,
viewCommands,
});
useLayoutEffect(() => {
const inputRefValue = inputRef.current;
if (inputRefValue != null) {
TextInputState.registerInput(inputRefValue);
return () => {
TextInputState.unregisterInput(inputRefValue);
if (TextInputState.currentlyFocusedInput() === inputRefValue) {
nullthrows(inputRefValue).blur();
}
};
}
}, []);
const setLocalRef = useCallback(
(instance: HostInstance | null) => {
// $FlowExpectedError[incompatible-type]
inputRef.current = instance;
/*
Hi reader from the future. I'm sorry for this.
This is a hack. Ideally we would forwardRef to the underlying
host component. However, since TextInput has it's own methods that can be
called as well, if we used the standard forwardRef then these
methods wouldn't be accessible and thus be a breaking change.
We have a couple of options of how to handle this:
- Return a new ref with everything we methods from both. This is problematic
because we need React to also know it is a host component which requires
internals of the class implementation of the ref.
- Break the API and have some other way to call one set of the methods or
the other. This is our long term approach as we want to eventually
get the methods on host components off the ref. So instead of calling
ref.measure() you might call ReactNative.measure(ref). This would hopefully
let the ref for TextInput then have the methods like `.clear`. Or we do it
the other way and make it TextInput.clear(textInputRef) which would be fine
too. Either way though is a breaking change that is longer term.
- Mutate this ref. :( Gross, but accomplishes what we need in the meantime
before we can get to the long term breaking change.
*/
if (instance != null) {
// Register the input immediately when the ref is set so that focus()
// can be called from ref callbacks
// Double registering during useLayoutEffect is fine, because the underlying
// state is a Set.
TextInputState.registerInput(instance);
// $FlowFixMe[prop-missing] - See the explanation above.
// $FlowFixMe[unsafe-object-assign]
Object.assign(instance, {
clear(): void {
if (inputRef.current != null) {
viewCommands.setTextAndSelection(
inputRef.current,
mostRecentEventCount,
'',
0,
0,
);
}
},
// TODO: Fix this returning true on null === null, when no input is focused
isFocused(): boolean {
return TextInputState.currentlyFocusedInput() === inputRef.current;
},
getNativeRef(): ?TextInputInstance {
return inputRef.current;
},
setSelection(start: number, end: number): void {
if (inputRef.current != null) {
viewCommands.setTextAndSelection(
inputRef.current,
mostRecentEventCount,
null,
start,
end,
);
}
},
});
}
},
[mostRecentEventCount, viewCommands],
);
// $FlowExpectedError[incompatible-type]
const ref = useMergeRefs<HostInstance>(setLocalRef, props.forwardedRef);
const _onChange = (event: TextInputChangeEvent) => {
const currentText = event.nativeEvent.text;
props.onChange && props.onChange(event);
props.onChangeText && props.onChangeText(currentText);
if (inputRef.current == null) {
// calling `props.onChange` or `props.onChangeText`
// may clean up the input itself. Exits here.
return;
}
setLastNativeText(currentText);
// This must happen last, after we call setLastNativeText.
// Different ordering can cause bugs when editing AndroidTextInputs
// with multiple Fragments.
// We must update this so that controlled input updates work.
setMostRecentEventCount(event.nativeEvent.eventCount);
};
const _onSelectionChange = (event: TextInputSelectionChangeEvent) => {
props.onSelectionChange && props.onSelectionChange(event);
if (inputRef.current == null) {
// calling `props.onSelectionChange`
// may clean up the input itself. Exits here.
return;
}
setLastNativeSelection({
selection: event.nativeEvent.selection,
mostRecentEventCount,
});
};
const _onFocus = (event: FocusEvent) => {
TextInputState.focusInput(inputRef.current);
if (props.onFocus) {
props.onFocus(event);
}
};
const _onBlur = (event: BlurEvent) => {
TextInputState.blurInput(inputRef.current);
if (props.onBlur) {
props.onBlur(event);
}
};
const _onScroll = (event: ScrollEvent) => {
props.onScroll && props.onScroll(event);
};
let textInput = null;
const multiline = props.multiline ?? false;
let submitBehavior: SubmitBehavior;
if (props.submitBehavior != null) {
// `submitBehavior` is set explicitly
if (!multiline && props.submitBehavior === 'newline') {
// For single line text inputs, `'newline'` is not a valid option
submitBehavior = 'blurAndSubmit';
} else {
submitBehavior = props.submitBehavior;
}
} else if (multiline) {
if (props.blurOnSubmit === true) {
submitBehavior = 'blurAndSubmit';
} else {
submitBehavior = 'newline';
}
} else {
// Single line
if (props.blurOnSubmit !== false) {
submitBehavior = 'blurAndSubmit';
} else {
submitBehavior = 'submit';
}
}
const accessible = props.accessible !== false;
const focusable = props.focusable !== false;
const {
editable,
hitSlop,
onPress,
onPressIn,
onPressOut,
rejectResponderTermination,
} = props;
const config = useMemo(
() => ({
hitSlop,
onPress: (event: GestureResponderEvent) => {
onPress?.(event);
if (editable !== false) {
if (inputRef.current != null) {
inputRef.current.focus();
}
}
},
onPressIn: onPressIn,
onPressOut: onPressOut,
cancelable: Platform.OS === 'ios' ? !rejectResponderTermination : null,
}),
[
editable,
hitSlop,
onPress,
onPressIn,
onPressOut,
rejectResponderTermination,
],
);
// Hide caret during test runs due to a flashing caret
// makes screenshot tests flakey
let caretHidden = props.caretHidden;
if (Platform.isTesting) {
caretHidden = true;
}
// TextInput handles onBlur and onFocus events
// so omitting onBlur and onFocus pressability handlers here.
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
const _accessibilityLabel =
props?.['aria-label'] ?? props?.accessibilityLabel;
let _accessibilityState;
if (
accessibilityState != null ||
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
_accessibilityState = {
busy: ariaBusy ?? accessibilityState?.busy,
checked: ariaChecked ?? accessibilityState?.checked,
disabled: ariaDisabled ?? accessibilityState?.disabled,
expanded: ariaExpanded ?? accessibilityState?.expanded,
selected: ariaSelected ?? accessibilityState?.selected,
};
}
// Keep the original (potentially nested) style when possible, as React can diff these more efficiently
let _style = props.style;
const flattenedStyle = flattenStyle<TextStyleProp>(props.style);
if (flattenedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof flattenedStyle?.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-type]
(flattenedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
}
if (flattenedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[flattenedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
if (Platform.OS === 'ios') {
const RCTTextInputView =
props.multiline === true
? RCTMultilineTextInputView
: RCTSinglelineTextInputView;
const useMultilineDefaultStyle =
props.multiline === true &&
(flattenedStyle == null ||
(flattenedStyle.padding == null &&
flattenedStyle.paddingVertical == null &&
flattenedStyle.paddingTop == null));
const _accessibilityElementsHidden =
props['aria-hidden'] ?? props.accessibilityElementsHidden;
textInput = (
<RCTTextInputView
// Figure out imperative + forward refs.
ref={(ref: $FlowFixMe)}
{...otherProps}
{...eventHandlers}
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessibilityElementsHidden={_accessibilityElementsHidden}
accessible={accessible}
submitBehavior={submitBehavior}
caretHidden={caretHidden}
dataDetectorTypes={props.dataDetectorTypes}
focusable={tabIndex !== undefined ? !tabIndex : focusable}
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
onBlur={_onBlur}
onChange={_onChange}
onContentSizeChange={props.onContentSizeChange}
onFocus={_onFocus}
onScroll={_onScroll}
onSelectionChange={_onSelectionChange}
onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue}
selection={selection}
selectionColor={selectionColor}
style={StyleSheet.compose(
useMultilineDefaultStyle ? styles.multilineDefault : null,
_style,
)}
text={text}
/>
);
} else if (Platform.OS === 'android') {
const autoCapitalize = props.autoCapitalize || 'sentences';
const _accessibilityLabelledBy =
props?.['aria-labelledby'] ?? props?.accessibilityLabelledBy;
const _importantForAccessibility =
props['aria-hidden'] === true
? ('no-hide-descendants' as const)
: undefined;
const placeholder = props.placeholder ?? '';
let children = props.children;
const childCount = React.Children.count(children);
invariant(
!(props.value != null && childCount),
'Cannot specify both value and children.',
);
if (childCount > 1) {
children = <Text>{children}</Text>;
}
// For consistency with iOS set cursor/selectionHandle color as selectionColor
const colorProps = {
selectionColor,
selectionHandleColor:
selectionHandleColor === undefined
? selectionColor
: selectionHandleColor,
cursorColor: cursorColor === undefined ? selectionColor : cursorColor,
};
textInput = (
/* $FlowFixMe[prop-missing] the types for AndroidTextInput don't match up
* exactly with the props for TextInput. This will need to get fixed */
/* $FlowFixMe[incompatible-type] the types for AndroidTextInput don't
* match up exactly with the props for TextInput. This will need to get
* fixed */
/* $FlowFixMe[incompatible-type-arg] the types for AndroidTextInput don't
* match up exactly with the props for TextInput. This will need to get
* fixed */
<AndroidTextInput
// Figure out imperative + forward refs.
ref={(ref: $FlowFixMe)}
{...otherProps}
{...colorProps}
{...eventHandlers}
accessibilityLabel={_accessibilityLabel}
accessibilityLabelledBy={_accessibilityLabelledBy}
accessibilityState={_accessibilityState}
accessible={accessible}
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
autoCapitalize={autoCapitalize}
submitBehavior={submitBehavior}
caretHidden={caretHidden}
children={children}
disableFullscreenUI={props.disableFullscreenUI}
focusable={tabIndex !== undefined ? !tabIndex : focusable}
importantForAccessibility={_importantForAccessibility}
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
onBlur={_onBlur}
onChange={_onChange}
onFocus={_onFocus}
/* $FlowFixMe[prop-missing] the types for AndroidTextInput don't match
* up exactly with the props for TextInput. This will need to get fixed
*/
/* $FlowFixMe[incompatible-type] the types for AndroidTextInput
* don't match up exactly with the props for TextInput. This will need
* to get fixed */
onScroll={_onScroll}
onSelectionChange={_onSelectionChange}
placeholder={placeholder}
style={_style}
text={text}
textBreakStrategy={props.textBreakStrategy}
/>
);
}
return <TextAncestorContext value={true}>{textInput}</TextAncestorContext>;
}
const enterKeyHintToReturnTypeMap = {
enter: 'default',
done: 'done',
go: 'go',
next: 'next',
previous: 'previous',
search: 'search',
send: 'send',
} as const;
const inputModeToKeyboardTypeMap = {
none: 'default',
text: 'default',
decimal: 'decimal-pad',
numeric: 'number-pad',
tel: 'phone-pad',
search:
Platform.OS === 'ios' ? ('web-search' as const) : ('default' as const),
email: 'email-address',
url: 'url',
} as const;
// Map HTML autocomplete values to Android autoComplete values
const autoCompleteWebToAutoCompleteAndroidMap = {
'address-line1': 'postal-address-region',
'address-line2': 'postal-address-locality',
bday: 'birthdate-full',
'bday-day': 'birthdate-day',
'bday-month': 'birthdate-month',
'bday-year': 'birthdate-year',
'cc-csc': 'cc-csc',
'cc-exp': 'cc-exp',
'cc-exp-month': 'cc-exp-month',
'cc-exp-year': 'cc-exp-year',
'cc-number': 'cc-number',
country: 'postal-address-country',
'current-password': 'password',
email: 'email',
'honorific-prefix': 'name-prefix',
'honorific-suffix': 'name-suffix',
name: 'name',
'additional-name': 'name-middle',
'family-name': 'name-family',
'given-name': 'name-given',
'new-password': 'password-new',
off: 'off',
'one-time-code': 'sms-otp',
'postal-code': 'postal-code',
sex: 'gender',
'street-address': 'street-address',
tel: 'tel',
'tel-country-code': 'tel-country-code',
'tel-national': 'tel-national',
username: 'username',
} as const;
// Map HTML autocomplete values to iOS textContentType values
const autoCompleteWebToTextContentTypeMap = {
'address-line1': 'streetAddressLine1',
'address-line2': 'streetAddressLine2',
bday: 'birthdate',
'bday-day': 'birthdateDay',
'bday-month': 'birthdateMonth',
'bday-year': 'birthdateYear',
'cc-csc': 'creditCardSecurityCode',
'cc-exp-month': 'creditCardExpirationMonth',
'cc-exp-year': 'creditCardExpirationYear',
'cc-exp': 'creditCardExpiration',
'cc-given-name': 'creditCardGivenName',
'cc-additional-name': 'creditCardMiddleName',
'cc-family-name': 'creditCardFamilyName',
'cc-name': 'creditCardName',
'cc-number': 'creditCardNumber',
'cc-type': 'creditCardType',
'current-password': 'password',
country: 'countryName',
email: 'emailAddress',
name: 'name',
'additional-name': 'middleName',
'family-name': 'familyName',
'given-name': 'givenName',
nickname: 'nickname',
'honorific-prefix': 'namePrefix',
'honorific-suffix': 'nameSuffix',
'new-password': 'newPassword',
off: 'none',
'one-time-code': 'oneTimeCode',
organization: 'organizationName',
'organization-title': 'jobTitle',
'postal-code': 'postalCode',
'street-address': 'fullStreetAddress',
tel: 'telephoneNumber',
url: 'URL',
username: 'username',
} as const;
const TextInput: component(
ref?: React.RefSetter<TextInputInstance>,
...props: React.ElementConfig<typeof InternalTextInput>
) = function TextInput({
ref: forwardedRef,
allowFontScaling = true,
rejectResponderTermination = true,
underlineColorAndroid = 'transparent',
autoComplete,
textContentType,
readOnly,
editable,
enterKeyHint,
returnKeyType,
inputMode,
showSoftInputOnFocus,
keyboardType,
...restProps
}: {
ref?: React.RefSetter<TextInputInstance>,
...React.ElementConfig<typeof InternalTextInput>,
}) {
return (
<InternalTextInput
allowFontScaling={allowFontScaling}
rejectResponderTermination={rejectResponderTermination}
underlineColorAndroid={underlineColorAndroid}
editable={readOnly !== undefined ? !readOnly : editable}
returnKeyType={
enterKeyHint ? enterKeyHintToReturnTypeMap[enterKeyHint] : returnKeyType
}
keyboardType={
inputMode ? inputModeToKeyboardTypeMap[inputMode] : keyboardType
}
showSoftInputOnFocus={
inputMode == null ? showSoftInputOnFocus : inputMode !== 'none'
}
autoComplete={
Platform.OS === 'android'
? // $FlowFixMe[invalid-computed-prop]
// $FlowFixMe[prop-missing]
(autoCompleteWebToAutoCompleteAndroidMap[autoComplete] ??
autoComplete)
: undefined
}
textContentType={
textContentType != null
? textContentType
: Platform.OS === 'ios' &&
autoComplete &&
autoComplete in autoCompleteWebToTextContentTypeMap
? // $FlowFixMe[prop-missing]
autoCompleteWebToTextContentTypeMap[autoComplete]
: textContentType
}
{...restProps}
forwardedRef={forwardedRef}
/>
);
};
TextInput.displayName = 'TextInput';
// $FlowFixMe[prop-missing]
TextInput.State = {
currentlyFocusedInput: TextInputState.currentlyFocusedInput,
currentlyFocusedField: TextInputState.currentlyFocusedField,
focusTextInput: TextInputState.focusTextInput,
blurTextInput: TextInputState.blurTextInput,
};
export type TextInputComponentStatics = $ReadOnly<{
State: TextInputStateType,
}>;
const styles = StyleSheet.create({
multilineDefault: {
// This default top inset makes RCTMultilineTextInputView seem as close as possible
// to single-line RCTSinglelineTextInputView defaults, using the system defaults
// of font size 17 and a height of 31 points.
paddingTop: 5,
},
});
const verticalAlignToTextAlignVerticalMap = {
auto: 'auto',
top: 'top',
bottom: 'bottom',
middle: 'center',
} as const;
// $FlowFixMe[unclear-type] Unclear type. Using `any` type is not safe.
export default TextInput as any as TextInputType;

View File

@@ -0,0 +1,29 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import type {Int32} from '../../Types/CodegenTypes';
import * as React from 'react';
export interface TextInputNativeCommands<T> {
+focus: (viewRef: React.ElementRef<T>) => void;
+blur: (viewRef: React.ElementRef<T>) => void;
+setTextAndSelection: (
viewRef: React.ElementRef<T>,
mostRecentEventCount: Int32,
value: ?string, // in theory this is nullable
start: Int32,
end: Int32,
) => void;
}
const supportedCommands = ['focus', 'blur', 'setTextAndSelection'] as string[];
export default supportedCommands;

View File

@@ -0,0 +1,198 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
// This class is responsible for coordinating the "focused" state for
// TextInputs. All calls relating to the keyboard should be funneled
// through here.
import type {HostInstance} from '../../../src/private/types/HostInstance';
import {Commands as AndroidTextInputCommands} from '../../Components/TextInput/AndroidTextInputNativeComponent';
import {Commands as iOSTextInputCommands} from '../../Components/TextInput/RCTSingelineTextInputNativeComponent';
const {findNodeHandle} = require('../../ReactNative/RendererProxy');
const Platform = require('../../Utilities/Platform').default;
let currentlyFocusedInputRef: ?HostInstance = null;
const inputs = new Set<HostInstance>();
function currentlyFocusedInput(): ?HostInstance {
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<$FlowFixMe>(currentlyFocusedInputRef);
}
function focusInput(textField: ?HostInstance): void {
if (currentlyFocusedInputRef !== textField && textField != null) {
currentlyFocusedInputRef = textField;
}
}
function blurInput(textField: ?HostInstance): 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: ?HostInstance) {
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[prop-missing] - `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: ?HostInstance) {
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: HostInstance) {
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: HostInstance) {
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: HostInstance): 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);
}
const TextInputState = {
currentlyFocusedInput,
focusInput,
blurInput,
currentlyFocusedField,
focusField,
blurField,
focusTextInput,
blurTextInput,
registerInput,
unregisterInput,
isTextInput,
};
export default TextInputState;