import React, { forwardRef, useImperativeHandle } from 'react' import { TypeGuards } from '@codeleap/types' import { TextInput as NativeTextInput } from 'react-native' import { InputBase, selectInputBaseProps } from '../InputBase' import { Touchable } from '../Touchable' import { MaskedTextInput } from '../../modules/textInputMask' import { AnyRecord, AppIcon, IJSX, StyledComponentProps, StyledComponentWithProps } from '@codeleap/styles' import { TextInputProps } from './types' import { MobileStyleRegistry } from '../../Registry' import { useStylesFor } from '../../hooks' import { useTextInput } from './useTextInput' import { useInputBasePartialStyles } from '../InputBase/useInputBasePartialStyles' export * from './styles' export * from './types' export const TextInput = forwardRef((props, ref) => { const allProps = { ...TextInput.defaultProps, ...props, } const { inputBaseProps, others, } = selectInputBaseProps(allProps) const { debugName, visibilityToggle, masking, secure, field, onChangeMask, onPress, visibleIcon, hiddenIcon, style, autoAdjustSelection, selectionStart, forceError, multiline, onLayout, ...textInputProps } = others const styles = useStylesFor(TextInput.styleRegistryName, style) const { validation, inputValue, onInputValueChange, innerInputRef, wrapperRef, isFocused, secureTextEntry, currentSelection, hasMultipleLines, hasValue, hasError, toggleSecureTextEntry, handleMaskChange, handleBlur, handleFocus, } = useTextInput(allProps) useImperativeHandle(ref, () => { if (!innerInputRef.current) return null return { ...innerInputRef.current, } as NativeTextInput }) /** Swaps the underlying element to the masked variant; the ref forwarding also changes — `refInput` is used instead of `ref` because MaskedTextInput does not forward refs the standard way. */ const InputElement = masking ? MaskedTextInput : NativeTextInput const isPressable = TypeGuards.isFunction(onPress) const isDisabled = !!inputBaseProps.disabled /** `placeholderTextColor` and `selectionColor` are scalar RN props that cannot accept style arrays, so their colors are extracted as plain values here rather than in the style array passed to the element. */ const partialStyles = useInputBasePartialStyles(styles, ['placeholder', 'selection'], { disabled: isDisabled, error: !!hasError, focus: isFocused || textInputProps?.focused, }) const visibilityToggleProps = visibilityToggle ? { onPress: toggleSecureTextEntry, icon: (secureTextEntry ? hiddenIcon : visibleIcon) as AppIcon, debugName: `${debugName} toggle visibility`, } : null const rightIcon = inputBaseProps?.rightIcon ?? visibilityToggleProps const maskingExtraProps = masking ? { onChangeText: handleMaskChange, ref: null, refInput: (inputRef) => { if (!!inputRef) innerInputRef.current = inputRef }, ...masking, } : { onChangeText: onInputValueChange, } /** When `onPress` is supplied the input acts as a button: editing and the caret are disabled so the native keyboard never opens, and `pointerEvents="none"` prevents touch from reaching the input itself. */ const buttonModeProps = isPressable ? { editable: false, caretHidden: true, } : {} return }) as StyledComponentWithProps TextInput.styleRegistryName = 'TextInput' TextInput.elements = [...InputBase.elements, 'input', 'placeholder', 'selection'] TextInput.rootElement = 'wrapper' TextInput.withVariantTypes = (styles: S) => { return TextInput as (props: StyledComponentProps) => IJSX } TextInput.defaultProps = { hiddenIcon: 'input-visiblity:hidden' as AppIcon, visibleIcon: 'input-visiblity:visible' as AppIcon, visibilityToggle: false, autoAdjustSelection: false, selectionStart: 0, secure: false, } as Partial MobileStyleRegistry.registerComponent(TextInput)