12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208 |
- /**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- * @format
- */
- 'use strict';
- const DeprecatedTextInputPropTypes = require('../../DeprecatedPropTypes/DeprecatedTextInputPropTypes');
- const Platform = require('../../Utilities/Platform');
- const React = require('react');
- const ReactNative = require('../../Renderer/shims/ReactNative');
- const StyleSheet = require('../../StyleSheet/StyleSheet');
- const Text = require('../../Text/Text');
- const TextAncestor = require('../../Text/TextAncestor');
- const TextInputState = require('./TextInputState');
- const TouchableWithoutFeedback = require('../Touchable/TouchableWithoutFeedback');
- const invariant = require('invariant');
- const nullthrows = require('nullthrows');
- const setAndForwardRef = require('../../Utilities/setAndForwardRef');
- import type {TextStyleProp, ViewStyleProp} from '../../StyleSheet/StyleSheet';
- import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
- import type {ViewProps} from '../View/ViewPropTypes';
- import type {SyntheticEvent, ScrollEvent} from '../../Types/CoreEventTypes';
- import type {PressEvent} from '../../Types/CoreEventTypes';
- import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
- import type {TextInputNativeCommands} from './TextInputNativeCommands';
- const {useEffect, useRef, useState} = React;
- type ReactRefSetter<T> = {current: null | T, ...} | ((ref: null | T) => mixed);
- 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 ChangeEvent = SyntheticEvent<
- $ReadOnly<{|
- eventCount: number,
- target: number,
- text: string,
- |}>,
- >;
- export type TextInputEvent = SyntheticEvent<
- $ReadOnly<{|
- eventCount: number,
- previousText: string,
- range: $ReadOnly<{|
- start: number,
- end: number,
- |}>,
- target: number,
- text: string,
- |}>,
- >;
- export type ContentSizeChangeEvent = SyntheticEvent<
- $ReadOnly<{|
- target: number,
- contentSize: $ReadOnly<{|
- width: number,
- height: number,
- |}>,
- |}>,
- >;
- type TargetEvent = SyntheticEvent<
- $ReadOnly<{|
- target: number,
- |}>,
- >;
- export type BlurEvent = TargetEvent;
- export type FocusEvent = TargetEvent;
- type Selection = $ReadOnly<{|
- start: number,
- end: number,
- |}>;
- export type SelectionChangeEvent = SyntheticEvent<
- $ReadOnly<{|
- selection: Selection,
- target: number,
- |}>,
- >;
- export type KeyPressEvent = SyntheticEvent<
- $ReadOnly<{|
- key: string,
- target?: ?number,
- eventCount?: ?number,
- |}>,
- >;
- export type EditingEvent = SyntheticEvent<
- $ReadOnly<{|
- eventCount: number,
- text: string,
- target: number,
- |}>,
- >;
- type DataDetectorTypesType =
- | 'phoneNumber'
- | 'link'
- | 'address'
- | 'calendarEvent'
- | 'none'
- | 'all';
- export type KeyboardType =
- // Cross Platform
- | 'default'
- | 'email-address'
- | 'numeric'
- | 'phone-pad'
- | 'number-pad'
- | 'decimal-pad'
- // iOS-only
- | 'ascii-capable'
- | 'numbers-and-punctuation'
- | 'url'
- | 'name-phone-pad'
- | 'twitter'
- | 'web-search'
- // iOS 10+ only
- | 'ascii-capable-number-pad'
- // 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 AutoCapitalize = 'none' | 'sentences' | 'words' | 'characters';
- export type TextContentType =
- | 'none'
- | 'URL'
- | 'addressCity'
- | 'addressCityAndState'
- | 'addressState'
- | 'countryName'
- | 'creditCardNumber'
- | 'emailAddress'
- | 'familyName'
- | 'fullStreetAddress'
- | 'givenName'
- | 'jobTitle'
- | 'location'
- | 'middleName'
- | 'name'
- | 'namePrefix'
- | 'nameSuffix'
- | 'nickname'
- | 'organizationName'
- | 'postalCode'
- | 'streetAddressLine1'
- | 'streetAddressLine2'
- | 'sublocality'
- | 'telephoneNumber'
- | 'username'
- | 'password'
- | 'newPassword'
- | 'oneTimeCode';
- type PasswordRules = string;
- type IOSProps = $ReadOnly<{|
- /**
- * If `false`, disables spell-check style (i.e. red underlines).
- * The default value is inherited from `autoCorrect`.
- * @platform ios
- */
- spellCheck?: ?boolean,
- /**
- * Determines the color of the keyboard.
- * @platform ios
- */
- keyboardAppearance?: ?('default' | 'light' | 'dark'),
- /**
- * If `true`, the keyboard disables the return key when there is no text and
- * automatically enables it when there is text. The default value is `false`.
- * @platform ios
- */
- enablesReturnKeyAutomatically?: ?boolean,
- /**
- * When the clear button should appear on the right side of the text view.
- * This property is supported only for single-line TextInput component.
- * @platform ios
- */
- clearButtonMode?: ?('never' | 'while-editing' | 'unless-editing' | 'always'),
- /**
- * If `true`, clears the text field automatically when editing begins.
- * @platform ios
- */
- clearTextOnFocus?: ?boolean,
- /**
- * Determines the types of data converted to clickable URLs in the text input.
- * Only valid if `multiline={true}` and `editable={false}`.
- * By default no data types are detected.
- *
- * You can provide one type or an array of many types.
- *
- * Possible values for `dataDetectorTypes` are:
- *
- * - `'phoneNumber'`
- * - `'link'`
- * - `'address'`
- * - `'calendarEvent'`
- * - `'none'`
- * - `'all'`
- *
- * @platform ios
- */
- dataDetectorTypes?:
- | ?DataDetectorTypesType
- | $ReadOnlyArray<DataDetectorTypesType>,
- /**
- * An optional identifier which links a custom InputAccessoryView to
- * this text input. The InputAccessoryView is rendered above the
- * keyboard when this text input is focused.
- * @platform ios
- */
- inputAccessoryViewID?: ?string,
- /**
- * Give the keyboard and the system information about the
- * expected semantic meaning for the content that users enter.
- * @platform ios
- */
- textContentType?: ?TextContentType,
- /**
- * Provide rules for your password.
- * For example, say you want to require a password with at least eight characters consisting of a mix of uppercase and lowercase letters, at least one number, and at most two consecutive characters.
- * "required: upper; required: lower; required: digit; max-consecutive: 2; minlength: 8;"
- * @platform ios
- */
- passwordRules?: ?PasswordRules,
- /*
- * If `true`, allows TextInput to pass touch events to the parent component.
- * This allows components to be swipeable from the TextInput on iOS,
- * as is the case on Android by default.
- * If `false`, TextInput always asks to handle the input (except when disabled).
- * @platform ios
- */
- rejectResponderTermination?: ?boolean,
- /**
- * If `false`, scrolling of the text view will be disabled.
- * The default value is `true`. Does only work with 'multiline={true}'.
- * @platform ios
- */
- scrollEnabled?: ?boolean,
- |}>;
- type AndroidProps = $ReadOnly<{|
- /**
- * Determines which content to suggest on auto complete, e.g.`username`.
- * To disable auto complete, use `off`.
- *
- * *Android Only*
- *
- * The following values work on Android only:
- *
- * - `username`
- * - `password`
- * - `email`
- * - `name`
- * - `tel`
- * - `street-address`
- * - `postal-code`
- * - `cc-number`
- * - `cc-csc`
- * - `cc-exp`
- * - `cc-exp-month`
- * - `cc-exp-year`
- * - `off`
- *
- * @platform android
- */
- autoCompleteType?: ?(
- | 'cc-csc'
- | 'cc-exp'
- | 'cc-exp-month'
- | 'cc-exp-year'
- | 'cc-number'
- | 'email'
- | 'name'
- | 'password'
- | 'postal-code'
- | 'street-address'
- | 'tel'
- | 'username'
- | '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?: ?number,
- /**
- * 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?: ?('simple' | 'highQuality' | 'balanced'),
- /**
- * 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?: ?number,
- importantForAutofill?: ?(
- | 'auto'
- | 'no'
- | 'noExcludeDescendants'
- | 'yes'
- | 'yesExcludeDescendants'
- ),
- /**
- * When `false`, it will prevent the soft keyboard from showing when the field is focused.
- * Defaults to `true`.
- * @platform android
- */
- showSoftInputOnFocus?: ?boolean,
- |}>;
- export type Props = $ReadOnly<{|
- ...$Diff<ViewProps, $ReadOnly<{|style: ?ViewStyleProp|}>>,
- ...IOSProps,
- ...AndroidProps,
- /**
- * 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?: ?AutoCapitalize,
- /**
- * 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?: ?number,
- /**
- * 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`
- *
- * *iOS Only*
- *
- * The following values work on iOS only:
- *
- * - `ascii-capable`
- * - `numbers-and-punctuation`
- * - `url`
- * - `name-phone-pad`
- * - `twitter`
- * - `web-search`
- *
- * *Android Only*
- *
- * The following values work on Android only:
- *
- * - `visible-password`
- *
- * On Android devices manufactured by Xiaomi with Android Q, 'email-address'
- * type will be replaced in native by 'default' to prevent a system related crash.
- */
- keyboardType?: ?KeyboardType,
- /**
- * 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`
- *
- * *iOS Only*
- *
- * The following values work on iOS only:
- *
- * - `default`
- * - `emergency-call`
- * - `google`
- * - `join`
- * - `route`
- * - `yahoo`
- */
- returnKeyType?: ?ReturnKeyType,
- /**
- * Limits the maximum number of characters that can be entered. Use this
- * instead of implementing the logic in JS to avoid flicker.
- */
- maxLength?: ?number,
- /**
- * 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.
- */
- onBlur?: ?(e: BlurEvent) => mixed,
- /**
- * Callback that is called when the text input is focused.
- */
- onFocus?: ?(e: FocusEvent) => mixed,
- /**
- * Callback that is called when the text input's text changes.
- */
- onChange?: ?(e: ChangeEvent) => mixed,
- /**
- * Callback that is called when the text input's text changes.
- * Changed text is passed as an argument to the callback handler.
- */
- onChangeText?: ?(text: string) => mixed,
- /**
- * 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?: ?(e: ContentSizeChangeEvent) => mixed,
- /**
- * Callback that is called when text input ends.
- */
- onEndEditing?: ?(e: EditingEvent) => mixed,
- /**
- * Callback that is called when the text input selection is changed.
- * This will be called with
- * `{ nativeEvent: { selection: { start, end } } }`.
- */
- onSelectionChange?: ?(e: SelectionChangeEvent) => mixed,
- /**
- * Callback that is called when the text input's submit button is pressed.
- * Invalid if `multiline={true}` is specified.
- */
- onSubmitEditing?: ?(e: EditingEvent) => mixed,
- /**
- * 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?: ?(e: KeyPressEvent) => mixed,
- /**
- * 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?: ?(e: ScrollEvent) => mixed,
- /**
- * 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 start and end of the text input's selection. Set start and end to
- * the same value to position the cursor.
- */
- selection?: ?$ReadOnly<{|
- start: number,
- end?: ?number,
- |}>,
- /**
- * 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?: ?Stringish,
- /**
- * 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?: ?Stringish,
- /**
- * 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.
- */
- blurOnSubmit?: ?boolean,
- /**
- * 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)
- */
- 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,
- forwardedRef?: ?ReactRefSetter<
- React.ElementRef<HostComponent<mixed>> & ImperativeMethods,
- >,
- |}>;
- type ImperativeMethods = $ReadOnly<{|
- clear: () => void,
- isFocused: () => boolean,
- getNativeRef: () => ?React.ElementRef<HostComponent<mixed>>,
- |}>;
- const emptyFunctionThatReturnsTrue = () => true;
- /**
- * 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: Props): React.Node {
- const inputRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);
- // Android sends a "onTextChanged" event followed by a "onSelectionChanged" event, for
- // the same "most recent event count".
- // For controlled selection, that means that immediately after text is updated,
- // a controlled component will pass in the *previous* selection, even if the controlled
- // component didn't mean to modify the selection at all.
- // Therefore, we ignore selections and pass them through until the selection event has
- // been sent.
- // Note that this mitigation is NOT needed for Fabric.
- let selection: ?Selection =
- props.selection == null
- ? null
- : {
- start: props.selection.start,
- end: props.selection.end ?? props.selection.start,
- };
- const [mostRecentEventCount, setMostRecentEventCount] = useState<number>(0);
- const [lastNativeText, setLastNativeText] = useState<?Stringish>(props.value);
- const [lastNativeSelectionState, setLastNativeSelection] = useState<{|
- selection: ?Selection,
- mostRecentEventCount: number,
- |}>({selection, mostRecentEventCount});
- const lastNativeSelection = lastNativeSelectionState.selection;
- const lastNativeSelectionEventCount =
- lastNativeSelectionState.mostRecentEventCount;
- if (lastNativeSelectionEventCount < mostRecentEventCount) {
- selection = null;
- }
- let viewCommands: TextInputNativeCommands<HostComponent<any>>;
- if (AndroidTextInputCommands) {
- viewCommands = AndroidTextInputCommands;
- } else {
- viewCommands = props.multiline
- ? RCTMultilineTextInputNativeCommands
- : RCTSinglelineTextInputNativeCommands;
- }
- const text =
- typeof props.value === 'string'
- ? props.value
- : typeof props.defaultValue === 'string'
- ? props.defaultValue
- : '';
- // 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.
- useEffect(() => {
- const nativeUpdate = {};
- 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,
- ]);
- useEffect(() => {
- const inputRefValue = inputRef.current;
- if (inputRefValue != null) {
- TextInputState.registerInput(inputRefValue);
- return () => {
- TextInputState.unregisterInput(inputRefValue);
- };
- }
- }, [inputRef]);
- useEffect(() => {
- // When unmounting we need to blur the input
- return () => {
- if (isFocused()) {
- nullthrows(inputRef.current).blur();
- }
- };
- }, [inputRef]);
- function 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
- function isFocused(): boolean {
- return TextInputState.currentlyFocusedInput() === inputRef.current;
- }
- function getNativeRef(): ?React.ElementRef<HostComponent<mixed>> {
- return inputRef.current;
- }
- const _setNativeRef = setAndForwardRef({
- getForwardedRef: () => props.forwardedRef,
- setLocalRef: ref => {
- inputRef.current = ref;
- /*
- 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 (ref) {
- ref.clear = clear;
- ref.isFocused = isFocused;
- ref.getNativeRef = getNativeRef;
- }
- },
- });
- const _onPress = (event: PressEvent) => {
- if (props.editable || props.editable === undefined) {
- nullthrows(inputRef.current).focus();
- }
- };
- const _onChange = (event: ChangeEvent) => {
- const text = event.nativeEvent.text;
- props.onChange && props.onChange(event);
- props.onChangeText && props.onChangeText(text);
- if (inputRef.current == null) {
- // calling `props.onChange` or `props.onChangeText`
- // may clean up the input itself. Exits here.
- return;
- }
- setLastNativeText(text);
- // 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: SelectionChangeEvent) => {
- 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;
- let additionalTouchableProps: {|
- rejectResponderTermination?: $PropertyType<
- Props,
- 'rejectResponderTermination',
- >,
- // This is a hack to let Flow know we want an exact object
- |} = {...null};
- if (Platform.OS === 'ios') {
- const RCTTextInputView = props.multiline
- ? RCTMultilineTextInputView
- : RCTSinglelineTextInputView;
- const style = props.multiline
- ? [styles.multilineInput, props.style]
- : props.style;
- additionalTouchableProps.rejectResponderTermination =
- props.rejectResponderTermination;
- textInput = (
- <RCTTextInputView
- ref={_setNativeRef}
- {...props}
- dataDetectorTypes={props.dataDetectorTypes}
- mostRecentEventCount={mostRecentEventCount}
- onBlur={_onBlur}
- onChange={_onChange}
- onContentSizeChange={props.onContentSizeChange}
- onFocus={_onFocus}
- onScroll={_onScroll}
- onSelectionChange={_onSelectionChange}
- onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue}
- selection={selection}
- style={style}
- text={text}
- />
- );
- } else if (Platform.OS === 'android') {
- const style = [props.style];
- const autoCapitalize = props.autoCapitalize || 'sentences';
- let children = props.children;
- let childCount = 0;
- React.Children.forEach(children, () => ++childCount);
- invariant(
- !(props.value && childCount),
- 'Cannot specify both value and children.',
- );
- if (childCount > 1) {
- children = <Text>{children}</Text>;
- }
- textInput = (
- /* $FlowFixMe the types for AndroidTextInput don't match up exactly with
- the props for TextInput. This will need to get fixed */
- <AndroidTextInput
- ref={_setNativeRef}
- {...props}
- autoCapitalize={autoCapitalize}
- children={children}
- disableFullscreenUI={props.disableFullscreenUI}
- mostRecentEventCount={mostRecentEventCount}
- onBlur={_onBlur}
- onChange={_onChange}
- onFocus={_onFocus}
- /* $FlowFixMe the types for AndroidTextInput don't match up exactly
- * with the props for TextInput. This will need to get fixed */
- onScroll={_onScroll}
- onSelectionChange={_onSelectionChange}
- selection={selection}
- style={style}
- text={text}
- textBreakStrategy={props.textBreakStrategy}
- />
- );
- }
- return (
- <TextAncestor.Provider value={true}>
- <TouchableWithoutFeedback
- onLayout={props.onLayout}
- onPress={_onPress}
- accessible={props.accessible}
- accessibilityLabel={props.accessibilityLabel}
- accessibilityRole={props.accessibilityRole}
- accessibilityState={props.accessibilityState}
- nativeID={props.nativeID}
- testID={props.testID}
- {...additionalTouchableProps}>
- {textInput}
- </TouchableWithoutFeedback>
- </TextAncestor.Provider>
- );
- }
- const ExportedForwardRef: React.AbstractComponent<
- React.ElementConfig<typeof InternalTextInput>,
- React.ElementRef<HostComponent<mixed>> & ImperativeMethods,
- > = React.forwardRef(function TextInput(
- props,
- forwardedRef: ReactRefSetter<
- React.ElementRef<HostComponent<mixed>> & ImperativeMethods,
- >,
- ) {
- return <InternalTextInput {...props} forwardedRef={forwardedRef} />;
- });
- // $FlowFixMe
- ExportedForwardRef.defaultProps = {
- allowFontScaling: true,
- rejectResponderTermination: true,
- underlineColorAndroid: 'transparent',
- };
- // TODO: Deprecate this
- // $FlowFixMe
- ExportedForwardRef.propTypes = DeprecatedTextInputPropTypes;
- // $FlowFixMe
- ExportedForwardRef.State = {
- currentlyFocusedInput: TextInputState.currentlyFocusedInput,
- currentlyFocusedField: TextInputState.currentlyFocusedField,
- focusTextInput: TextInputState.focusTextInput,
- blurTextInput: TextInputState.blurTextInput,
- };
- type TextInputComponentStatics = $ReadOnly<{|
- State: $ReadOnly<{|
- currentlyFocusedInput: typeof TextInputState.currentlyFocusedInput,
- currentlyFocusedField: typeof TextInputState.currentlyFocusedField,
- focusTextInput: typeof TextInputState.focusTextInput,
- blurTextInput: typeof TextInputState.blurTextInput,
- |}>,
- propTypes: typeof DeprecatedTextInputPropTypes,
- |}>;
- const styles = StyleSheet.create({
- multilineInput: {
- // 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,
- },
- });
- module.exports = ((ExportedForwardRef: any): React.AbstractComponent<
- React.ElementConfig<typeof InternalTextInput>,
- $ReadOnly<{|
- ...React.ElementRef<HostComponent<mixed>>,
- ...ImperativeMethods,
- |}>,
- > &
- TextInputComponentStatics);
|