diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 2a170150d23da..5d35cf7263a8f 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2176,6 +2176,7 @@ const CONST = { KEYBOARD_TYPE: { VISIBLE_PASSWORD: 'visible-password', ASCII_CAPABLE: 'ascii-capable', + PHONE_PAD: 'phone-pad', NUMBER_PAD: 'number-pad', DECIMAL_PAD: 'decimal-pad', NUMBERS_AND_PUNCTUATION: 'numbers-and-punctuation', diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 36817b2c4659c..ac87061e7cdf3 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -24,6 +24,7 @@ import KeyboardUtils from '@src/utils/keyboard'; import type {RegisterInput} from './FormContext'; import FormContext from './FormContext'; import FormWrapper from './FormWrapper'; +import isNumericKeyboard from './isNumericKeyboard'; import type {FormInputErrors, FormOnyxValues, FormProps, FormRef, FormWrapperRef, InputComponentBaseProps, InputRefs, ValueTypeKey} from './types'; // In order to prevent Checkbox focus loss when the user are focusing a TextInput and proceeds to toggle a CheckBox in web and mobile web. @@ -351,6 +352,8 @@ function FormProvider({ const inputRef = inputProps.ref; + const hasNumericKeyboard = isNumericKeyboard(inputProps); + return { ...inputProps, ...(shouldSubmitForm && { @@ -359,7 +362,7 @@ function FormProvider({ inputProps.onSubmitEditing?.(event); }, - returnKeyType: 'go', + ...(!hasNumericKeyboard && {returnKeyType: 'go' as const}), }), ref: typeof inputRef === 'function' diff --git a/src/components/Form/isNumericKeyboard.ts b/src/components/Form/isNumericKeyboard.ts new file mode 100644 index 0000000000000..eaa60a7207358 --- /dev/null +++ b/src/components/Form/isNumericKeyboard.ts @@ -0,0 +1,23 @@ +import CONST from '@src/CONST'; +import type {InputComponentBaseProps} from './types'; + +type NumericKeyboardProps = Pick; + +/** + * Determines whether the given input props indicate a numeric or phone pad keyboard type. + * Numeric/phone pad keyboards on iOS don't have a built-in return key. Setting returnKeyType + * on these creates a visible toolbar button (e.g. "Go") that duplicates the form's submit button. + */ +function isNumericKeyboard(inputProps: NumericKeyboardProps): boolean { + return ( + inputProps.inputMode === CONST.INPUT_MODE.TEL || + inputProps.inputMode === CONST.INPUT_MODE.NUMERIC || + inputProps.inputMode === CONST.INPUT_MODE.DECIMAL || + inputProps.keyboardType === CONST.KEYBOARD_TYPE.PHONE_PAD || + inputProps.keyboardType === CONST.KEYBOARD_TYPE.NUMBER_PAD || + inputProps.keyboardType === CONST.KEYBOARD_TYPE.DECIMAL_PAD + ); +} + +export default isNumericKeyboard; +export type {NumericKeyboardProps}; diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 70e5acbdc2a28..1b97835a9e6cd 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -1,5 +1,5 @@ import type {ComponentRef, ComponentType, FocusEvent, Key, ReactNode, Ref, RefObject} from 'react'; -import type {GestureResponderEvent, HostComponent, StyleProp, SubmitBehavior, TextInputSubmitEditingEvent, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, HostComponent, InputModeOptions, KeyboardTypeOptions, StyleProp, SubmitBehavior, TextInputSubmitEditingEvent, ViewStyle} from 'react-native'; import type {ValueOf} from 'type-fest'; import type AddPlaidBankAccount from '@components/AddPlaidBankAccount'; import type AddressSearch from '@components/AddressSearch'; @@ -125,6 +125,8 @@ type InputComponentBaseProps = Input submitBehavior?: SubmitBehavior; shouldSubmitForm?: boolean; uncontrolled?: boolean; + inputMode?: InputModeOptions; + keyboardType?: KeyboardTypeOptions; getNativeRef?: () => ComponentRef> & RefObject; }; diff --git a/tests/unit/isNumericKeyboardTest.ts b/tests/unit/isNumericKeyboardTest.ts new file mode 100644 index 0000000000000..8a95fccdad048 --- /dev/null +++ b/tests/unit/isNumericKeyboardTest.ts @@ -0,0 +1,58 @@ +import isNumericKeyboard from '@components/Form/isNumericKeyboard'; +import type {NumericKeyboardProps} from '@components/Form/isNumericKeyboard'; +import CONST from '@src/CONST'; + +describe('isNumericKeyboard', () => { + it('returns true for inputMode tel', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.TEL})).toBe(true); + }); + + it('returns true for inputMode numeric', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.NUMERIC})).toBe(true); + }); + + it('returns true for inputMode decimal', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.DECIMAL})).toBe(true); + }); + + it('returns true for keyboardType phone-pad', () => { + expect(isNumericKeyboard({keyboardType: CONST.KEYBOARD_TYPE.PHONE_PAD})).toBe(true); + }); + + it('returns true for keyboardType number-pad', () => { + expect(isNumericKeyboard({keyboardType: CONST.KEYBOARD_TYPE.NUMBER_PAD})).toBe(true); + }); + + it('returns true for keyboardType decimal-pad', () => { + expect(isNumericKeyboard({keyboardType: CONST.KEYBOARD_TYPE.DECIMAL_PAD})).toBe(true); + }); + + it('returns false for inputMode text', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.TEXT})).toBe(false); + }); + + it('returns false for inputMode email', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.EMAIL})).toBe(false); + }); + + it('returns false for keyboardType ascii-capable', () => { + expect(isNumericKeyboard({keyboardType: CONST.KEYBOARD_TYPE.ASCII_CAPABLE})).toBe(false); + }); + + it('returns false for empty props', () => { + expect(isNumericKeyboard({})).toBe(false); + }); + + it('returns false when neither inputMode nor keyboardType is set', () => { + const props: NumericKeyboardProps = {}; + expect(isNumericKeyboard(props)).toBe(false); + }); + + it('returns true when inputMode is numeric even if keyboardType is non-numeric', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.NUMERIC, keyboardType: 'default'})).toBe(true); + }); + + it('returns true when keyboardType is phone-pad even if inputMode is text', () => { + expect(isNumericKeyboard({inputMode: CONST.INPUT_MODE.TEXT, keyboardType: CONST.KEYBOARD_TYPE.PHONE_PAD})).toBe(true); + }); +});