diff --git a/patches/react-native+0.75.2+018+Add-regex-to-TextInput.patch b/patches/react-native+0.75.2+018+Add-regex-to-TextInput.patch deleted file mode 100644 index 6c511d8cbec1d..0000000000000 --- a/patches/react-native+0.75.2+018+Add-regex-to-TextInput.patch +++ /dev/null @@ -1,299 +0,0 @@ -diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -index 770dfee..73e439b 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -@@ -329,6 +329,12 @@ export type NativeProps = $ReadOnly<{| - */ - returnKeyType?: WithDefault, - -+ /** -+ * Restricts the text value to match the specified regular expression. Use this -+ * instead of implementing the logic in JS to avoid flicker. -+ */ -+ regex?: ?string, -+ - /** - * Limits the maximum number of characters that can be entered. Use this - * instead of implementing the logic in JS to avoid flicker. -@@ -699,6 +705,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = { - process: require('../../StyleSheet/processColor').default, - }, - maxLength: true, -+ regex: true, - selectTextOnFocus: true, - textShadowRadius: true, - underlineColorAndroid: { -diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -index dbfe5d5..1f359ba 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -@@ -151,6 +151,7 @@ const RCTTextInputViewConfig = { - autoFocus: true, - lineBreakStrategyIOS: true, - smartInsertDelete: true, -+ regex: true, - ...ConditionallyIgnoredEventHandlers({ - onClear: true, - onChange: true, -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -index 20501f7..76f30b9 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -@@ -701,6 +701,12 @@ export interface TextInputProps - */ - inputMode?: InputModeOptions | undefined; - -+ /** -+ * Restricts the text value to match the specified regular expression. Use this -+ * instead of implementing the logic in JS to avoid flicker. -+ */ -+ regex?: string | undefined; -+ - /** - * Limits the maximum number of characters that can be entered. - * Use this instead of implementing the logic in JS to avoid flicker. -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -index 2f35731..5bb94bc 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -@@ -697,6 +697,12 @@ export type Props = $ReadOnly<{| - */ - maxFontSizeMultiplier?: ?number, - -+ /** -+ * Restricts the text value to match the specified regular expression. Use this -+ * instead of implementing the logic in JS to avoid flicker. -+ */ -+ regex?: ?string, -+ - /** - * Limits the maximum number of characters that can be entered. Use this - * instead of implementing the logic in JS to avoid flicker. -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -index 8cfde15..4f3345c 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -@@ -731,6 +731,12 @@ export type Props = $ReadOnly<{| - */ - maxFontSizeMultiplier?: ?number, - -+ /** -+ * Restricts the text value to match the specified regular expression. Use this -+ * instead of implementing the logic in JS to avoid flicker. -+ */ -+ regex?: ?string, -+ - /** - * Limits the maximum number of characters that can be entered. Use this - * instead of implementing the logic in JS to avoid flicker. -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm -index e367394..95f21f2 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm -@@ -59,6 +59,7 @@ @implementation RCTBaseTextInputViewManager { - RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewID, NSString) - RCT_EXPORT_VIEW_PROPERTY(textContentType, NSString) - RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString) -+RCT_EXPORT_VIEW_PROPERTY(regex, NSString) - - RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) - RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock) -diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm -index db7cba4..f85f95a 100644 ---- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm -+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm -@@ -34,6 +34,7 @@ @implementation RCTTextInputComponentView { - UIView *_backedTextInputView; - NSUInteger _mostRecentEventCount; - NSAttributedString *_lastStringStateWasUpdatedWith; -+ NSRegularExpression *_regex; - - /* - * UIKit uses either UITextField or UITextView as its UIKit element for . UITextField is for single line -@@ -224,6 +225,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & - if (newTextInputProps.inputAccessoryViewID != oldTextInputProps.inputAccessoryViewID) { - _backedTextInputView.inputAccessoryViewID = RCTNSStringFromString(newTextInputProps.inputAccessoryViewID); - } -+ -+ if (newTextInputProps.regex != oldTextInputProps.regex) { -+ _regex = [NSRegularExpression regularExpressionWithPattern:RCTNSStringFromString(newTextInputProps.regex) -+ options:0 -+ error:nil]; -+ } -+ - [super updateProps:props oldProps:oldProps]; - - [self setDefaultInputAccessoryView]; -@@ -359,6 +367,14 @@ - (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range - } - } - -+ if (_regex) { -+ NSMutableString *newString = [_backedTextInputView.attributedText.string mutableCopy]; -+ [newString replaceCharactersInRange:range withString:text]; -+ if ([_regex numberOfMatchesInString:newString options:0 range:NSMakeRange(0, newString.length)] == 0) { -+ return nil; -+ } -+ } -+ - if (props.maxLength) { - NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length; - -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -index 2cceb14..8fdc0c1 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -@@ -824,6 +824,47 @@ public class ReactTextInputManager extends BaseViewManager 0) { -+ LinkedList list = new LinkedList<>(); -+ for (InputFilter currentFilter : currentFilters) { -+ if (!(currentFilter instanceof RegexFilter)) { -+ list.add(currentFilter); -+ } -+ } -+ if (!list.isEmpty()) { -+ newFilters = (InputFilter[]) list.toArray(new InputFilter[list.size()]); -+ } -+ } -+ } else { -+ if (currentFilters.length > 0) { -+ newFilters = currentFilters; -+ boolean replaced = false; -+ for (int i = 0; i < currentFilters.length; i++) { -+ if (currentFilters[i] instanceof RegexFilter) { -+ currentFilters[i] = new RegexFilter(regex); -+ replaced = true; -+ } -+ } -+ if (!replaced) { -+ newFilters = new InputFilter[currentFilters.length + 1]; -+ System.arraycopy(currentFilters, 0, newFilters, 0, currentFilters.length); -+ newFilters[currentFilters.length] = new RegexFilter(regex); -+ } -+ } else { -+ newFilters = new InputFilter[1]; -+ newFilters[0] = new RegexFilter(regex); -+ } -+ } -+ -+ view.setFilters(newFilters); -+ } -+ - @ReactProp(name = "maxLength") - public void setMaxLength(ReactEditText view, @Nullable Integer maxLength) { - InputFilter[] currentFilters = view.getFilters(); -@@ -854,7 +895,7 @@ public class ReactTextInputManager extends BaseViewManager MoneyRequestUtils.amountRegex(decimals, amountMaxLength), [decimals, amountMaxLength]); const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -262,7 +261,6 @@ function AmountForm( keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD} inputMode={CONST.INPUT_MODE.DECIMAL} errorText={errorText} - regex={regex} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> @@ -302,7 +300,6 @@ function AmountForm( isCurrencyPressable={isCurrencyPressable} style={[styles.iouAmountTextInput]} containerStyle={[styles.iouAmountTextInputContainer]} - regex={regex} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> diff --git a/src/components/AmountTextInput.tsx b/src/components/AmountTextInput.tsx index 2e0d3e62afa04..52c32ce1f584a 100644 --- a/src/components/AmountTextInput.tsx +++ b/src/components/AmountTextInput.tsx @@ -39,7 +39,7 @@ type AmountTextInputProps = { /** Hide the focus styles on TextInput */ hideFocusedState?: boolean; -} & Pick; +} & Pick; function AmountTextInput( { diff --git a/src/components/AmountWithoutCurrencyForm.tsx b/src/components/AmountWithoutCurrencyForm.tsx index 6a9fc22f68f8f..78b7c84ecb542 100644 --- a/src/components/AmountWithoutCurrencyForm.tsx +++ b/src/components/AmountWithoutCurrencyForm.tsx @@ -1,8 +1,7 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useMemo} from 'react'; import type {ForwardedRef} from 'react'; -import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; import useLocalize from '@hooks/useLocalize'; -import {addLeadingZero, amountRegex, replaceAllDigits, replaceCommasWithPeriod, stripSpacesFromAmount, validateAmount} from '@libs/MoneyRequestUtils'; +import {addLeadingZero, replaceAllDigits, replaceCommasWithPeriod, stripSpacesFromAmount, validateAmount} from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; import TextInput from './TextInput'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; @@ -22,11 +21,6 @@ function AmountWithoutCurrencyForm( const {toLocaleDigit} = useLocalize(); const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]); - const [selection, setSelection] = useState({ - start: currentAmount.length, - end: currentAmount.length, - }); - const decimals = 2; /** * Sets the selection and the amount accordingly to the value passed to the input @@ -39,10 +33,7 @@ function AmountWithoutCurrencyForm( const newAmountWithoutSpaces = stripSpacesFromAmount(newAmount); const replacedCommasAmount = replaceCommasWithPeriod(newAmountWithoutSpaces); const withLeadingZero = addLeadingZero(replacedCommasAmount); - if (!validateAmount(withLeadingZero, decimals)) { - // Use a shallow copy of selection to trigger setSelection - // More info: https://github.com/Expensify/App/issues/16385 - setSelection((prevSelection) => ({...prevSelection})); + if (!validateAmount(withLeadingZero, 2)) { return; } onInputChange?.(withLeadingZero); @@ -50,17 +41,12 @@ function AmountWithoutCurrencyForm( [onInputChange], ); - const regex = useMemo(() => amountRegex(decimals), [decimals]); const formattedAmount = replaceAllDigits(currentAmount, toLocaleDigit); return ( ) => { - setSelection(e.nativeEvent.selection); - }} inputID={inputID} name={name} label={label} @@ -69,7 +55,6 @@ function AmountWithoutCurrencyForm( role={role} ref={ref} keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD} - regex={regex} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 7eddd718a9f48..9ef33900bb009 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import {useMouseContext} from '@hooks/useMouseContext'; @@ -274,7 +274,6 @@ function MoneyRequestAmountInput( }); }, [amount, currency, onFormatAmount, formatAmountOnBlur, maxLength]); - const regex = useMemo(() => MoneyRequestUtils.amountRegex(decimals), [decimals]); const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); const {setMouseDown, setMouseUp} = useMouseContext(); @@ -338,7 +337,6 @@ function MoneyRequestAmountInput( onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} contentWidth={contentWidth} - regex={regex} /> ); } diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts index b1f4e2fb64702..401af75b16cd6 100644 --- a/src/components/TextInputWithCurrencySymbol/types.ts +++ b/src/components/TextInputWithCurrencySymbol/types.ts @@ -77,7 +77,7 @@ type BaseTextInputWithCurrencySymbolProps = { /** Hide the focus styles on TextInput */ hideFocusedState?: boolean; -} & Pick; +} & Pick; type TextInputWithCurrencySymbolProps = Omit & { onSelectionChange?: (start: number, end: number) => void; diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 25c8509efaad0..206bb8509af65 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -38,21 +38,15 @@ function addLeadingZero(amount: string): string { } /** - * Get amount regex string - */ -function amountRegex(decimals: number, amountMaxLength: number = CONST.IOU.AMOUNT_MAX_LENGTH): string { - return decimals === 0 - ? `^\\d{0,${amountMaxLength}}$` // Don't allow decimal point if decimals === 0 - : `^\\d{0,${amountMaxLength}}(?:(?:\\.|\\,)\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point -} - -/** - * Check if string is a valid amount + * Check if amount is a decimal up to 3 digits */ function validateAmount(amount: string, decimals: number, amountMaxLength: number = CONST.IOU.AMOUNT_MAX_LENGTH): boolean { - const regexString = amountRegex(decimals, amountMaxLength); - const decimalNumberRegex = new RegExp(regexString); - return decimalNumberRegex.test(amount); + const regexString = + decimals === 0 + ? `^\\d{1,${amountMaxLength}}$` // Don't allow decimal point if decimals === 0 + : `^\\d{1,${amountMaxLength}}(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point + const decimalNumberRegex = new RegExp(regexString, 'i'); + return amount === '' || decimalNumberRegex.test(amount); } /** @@ -104,7 +98,6 @@ export { stripDecimalsFromAmount, stripSpacesFromAmount, replaceCommasWithPeriod, - amountRegex, validateAmount, validatePercentage, };